From 704efa55d45ea66ffa34e521095cbcbb97cfb457 Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Wed, 8 Jul 2026 23:48:00 +0200 Subject: [PATCH 1/2] Push libs --- .github/workflows/publish-registry.yml | 160 +++++ .github/workflows/test-registry.yml | 125 ++++ .gitignore | 4 + .prettierignore | 4 + .prettierrc.json | 4 + {empty-template => @types/jaculus}/.gitignore | 0 {empty-template => @types/jaculus}/README.md | 0 @types/jaculus/blocks/adc.jacly.json | 82 +++ .../jaculus/blocks/communication.jacly.json | 12 + @types/jaculus/blocks/control.jacly.json | 50 ++ @types/jaculus/blocks/gpio.jacly.json | 271 ++++++++ @types/jaculus/blocks/i2c.jacly.json | 89 +++ @types/jaculus/blocks/keyvalue.jacly.json | 263 ++++++++ @types/jaculus/blocks/ledc.jacly.json | 260 ++++++++ @types/jaculus/blocks/motor.jacly.json | 525 +++++++++++++++ @types/jaculus/blocks/movement.jacly.json | 12 + @types/jaculus/blocks/peripherals.jacly.json | 12 + @types/jaculus/blocks/protocols.jacly.json | 12 + @types/jaculus/blocks/pulseCounter.jacly.json | 185 ++++++ @types/jaculus/blocks/simpleradio.jacly.json | 335 ++++++++++ @types/jaculus/blocks/smartled.jacly.json | 194 ++++++ @types/jaculus/blocks/spi.jacly.json | 239 +++++++ @types/jaculus/blocks/stream.jacly.json | 117 ++++ .../jaculus/blocks/translations/cs.lang.json | 322 +++++++++ .../jaculus/blocks/translations/en.lang.json | 326 +++++++++ @types/jaculus/blocks/udp.jacly.json | 144 ++++ @types/jaculus/blocks/wifi.jacly.json | 49 ++ @types/jaculus/package.json | 27 + @types/jaculus/src/types/adc.d.ts | 23 + @types/jaculus/src/types/basicStream.d.ts | 22 + @types/jaculus/src/types/fs.d.ts | 80 +++ @types/jaculus/src/types/globalTypes.d.ts | 1 + @types/jaculus/src/types/gpio.d.ts | 53 ++ @types/jaculus/src/types/gridui.d.ts | 470 +++++++++++++ @types/jaculus/src/types/i2c.d.ts | 49 ++ @types/jaculus/src/types/index.d.ts | 23 + @types/jaculus/src/types/keyvalue.d.ts | 67 ++ @types/jaculus/src/types/ledc.d.ts | 53 ++ @types/jaculus/src/types/misc.d.ts | 5 + @types/jaculus/src/types/motor.d.ts | 121 ++++ @types/jaculus/src/types/onewire.d.ts | 59 ++ @types/jaculus/src/types/path.d.ts | 36 + @types/jaculus/src/types/platform.d.ts | 6 + @types/jaculus/src/types/pulseCounter.d.ts | 64 ++ @types/jaculus/src/types/simpleradio.d.ts | 110 +++ @types/jaculus/src/types/smartled.d.ts | 50 ++ @types/jaculus/src/types/spi.d.ts | 56 ++ @types/jaculus/src/types/stdio.d.ts | 13 + @types/jaculus/src/types/timers.d.ts | 31 + @types/jaculus/src/types/timestamp.d.ts | 11 + @types/jaculus/src/types/udpSocket.d.ts | 39 ++ @types/jaculus/src/types/wifi.d.ts | 16 + @types/jaculus/tsconfig.json | 14 + LICENSE | 2 +- README.md | 1 - algorithms/.gitignore | 2 + algorithms/README.md | 266 ++++++++ algorithms/blocks/algorithms.jacly.json | 232 +++++++ algorithms/blocks/translations/cs.lang.json | 16 + algorithms/blocks/translations/en.lang.json | 16 + algorithms/package.json | 25 + algorithms/src/index.ts | 93 +++ algorithms/tsconfig.json | 14 + basic/.gitignore | 2 + basic/README.md | 4 + basic/blocks/basic.jacly.json | 254 +++++++ basic/blocks/basic_ext.jacly.json | 29 + basic/blocks/translations/cs.lang.json | 48 ++ basic/blocks/translations/en.lang.json | 49 ++ basic/package.json | 28 + basic/src/index.ts | 14 + {empty-template => basic}/tsconfig.json | 4 +- bq2589/.gitignore | 2 + bq2589/README.md | 1 + bq2589/blocks/bq2589.jacly.json | 51 ++ bq2589/blocks/translations/cs.lang.json | 10 + bq2589/blocks/translations/en.lang.json | 10 + bq2589/package.json | 28 + bq2589/src/index.ts | 68 ++ bq2589/tsconfig.json | 15 + button/.gitignore | 2 + button/README.md | 1 + button/blocks/button.jacly.json | 164 +++++ button/blocks/translations/cs.lang.json | 32 + button/blocks/translations/en.lang.json | 32 + button/package.json | 28 + button/src/index.ts | 142 ++++ button/tsconfig.json | 15 + colors/.gitignore | 2 + colors/README.md | 1 + colors/blocks/colors.jacly.json | 31 + colors/blocks/translations/cs.lang.json | 24 + colors/blocks/translations/en.lang.json | 24 + colors/package.json | 25 + colors/src/index.ts | 177 +++++ colors/tsconfig.json | 14 + differential-drive/.gitignore | 2 + differential-drive/README.md | 1 + .../blocks/differential_drive.jacly.json | 355 ++++++++++ .../blocks/translations/cs.lang.json | 40 ++ .../blocks/translations/en.lang.json | 40 ++ differential-drive/package.json | 28 + differential-drive/src/index.ts | 155 +++++ differential-drive/tsconfig.json | 15 + ds18b20/.gitignore | 2 + ds18b20/README.md | 1 + ds18b20/blocks/ds18b20.jacly.json | 94 +++ ds18b20/blocks/translations/cs.lang.json | 20 + ds18b20/blocks/translations/en.lang.json | 20 + ds18b20/package.json | 28 + ds18b20/src/index.ts | 134 ++++ ds18b20/tsconfig.json | 15 + ds3231/.gitignore | 2 + ds3231/README.md | 1 + ds3231/blocks/ds3231.jacly.json | 216 ++++++ ds3231/blocks/translations/cs.lang.json | 23 + ds3231/blocks/translations/en.lang.json | 23 + ds3231/package.json | 28 + ds3231/src/index.ts | 133 ++++ ds3231/tsconfig.json | 15 + elks/.gitignore | 2 + elks/README.md | 7 + elks/blocks/elks_pins.jacly.json | 230 +++++++ elks/blocks/translations/cs.lang.json | 75 +++ elks/blocks/translations/en.lang.json | 75 +++ elks/package.json | 28 + elks/src/index.ts | 49 ++ elks/tsconfig.json | 14 + empty-template/blocks/template.json | 1 - empty-template/package.json | 17 - empty-template/src/index.ts | 3 - hd44780/.gitignore | 2 + hd44780/README.md | 3 + hd44780/blocks/hd44780.jacly.json | 178 +++++ hd44780/blocks/translations/cs.lang.json | 18 + hd44780/blocks/translations/en.lang.json | 18 + hd44780/package.json | 28 + hd44780/src/index.ts | 68 ++ hd44780/tsconfig.json | 15 + jacly-meta-blocks/.gitignore | 2 + jacly-meta-blocks/README.md | 4 + jacly-meta-blocks/blocks/functions.jacly.json | 11 + jacly-meta-blocks/blocks/json.jacly.json | 251 +++++++ jacly-meta-blocks/blocks/lists.jacly.json | 109 +++ jacly-meta-blocks/blocks/logic.jacly.json | 145 ++++ jacly-meta-blocks/blocks/loops.jacly.json | 78 +++ jacly-meta-blocks/blocks/math.jacly.json | 454 +++++++++++++ jacly-meta-blocks/blocks/text.jacly.json | 157 +++++ jacly-meta-blocks/blocks/text_ext.jacly.json | 203 ++++++ .../blocks/translations/cs.lang.json | 100 +++ .../blocks/translations/en.lang.json | 100 +++ jacly-meta-blocks/blocks/variables.jacly.json | 11 + jacly-meta-blocks/package.json | 18 + jacly-meta-blocks/tsconfig.json | 15 + lm75a/.gitignore | 2 + lm75a/README.md | 3 + lm75a/blocks/lm75a.jacly.json | 54 ++ lm75a/blocks/translations/cs.lang.json | 8 + lm75a/blocks/translations/en.lang.json | 8 + lm75a/package.json | 28 + lm75a/src/index.ts | 55 ++ lm75a/tsconfig.json | 15 + package.json | 34 +- pnpm-lock.yaml | 336 +++++++++- pnpm-workspace.yaml | 3 +- r-encoder/.gitignore | 2 + r-encoder/README.md | 1 + r-encoder/blocks/r_encoder.jacly.json | 143 ++++ r-encoder/blocks/translations/cs.lang.json | 20 + r-encoder/blocks/translations/en.lang.json | 20 + r-encoder/package.json | 28 + r-encoder/src/index.ts | 23 + r-encoder/tsconfig.json | 15 + readline/.gitignore | 2 + readline/README.md | 1 + readline/blocks/readline.jacly.json | 95 +++ readline/blocks/translations/cs.lang.json | 20 + readline/blocks/translations/en.lang.json | 20 + readline/package.json | 28 + readline/src/index.ts | 85 +++ readline/tsconfig.json | 15 + robutek/.gitignore | 2 + robutek/README.md | 3 + robutek/blocks/robutek2.jacly.json | 232 +++++++ robutek/blocks/robutek2_pins.jacly.json | 164 +++++ robutek/blocks/translations/cs.lang.json | 80 +++ robutek/blocks/translations/en.lang.json | 80 +++ robutek/package.json | 31 + robutek/src/index.ts | 303 +++++++++ robutek/tsconfig.json | 15 + servo/.gitignore | 2 + servo/README.md | 1 + servo/blocks/servo.jacly.json | 99 +++ servo/blocks/translations/cs.lang.json | 11 + servo/blocks/translations/en.lang.json | 11 + servo/package.json | 28 + servo/src/index.ts | 34 + servo/tsconfig.json | 15 + smartled-ext/.gitignore | 2 + smartled-ext/README.md | 1 + smartled-ext/blocks/smartled_ext.jacly.json | 233 +++++++ smartled-ext/blocks/translations/cs.lang.json | 29 + smartled-ext/blocks/translations/en.lang.json | 29 + smartled-ext/package.json | 29 + smartled-ext/src/index.ts | 124 ++++ smartled-ext/tsconfig.json | 15 + template-jacly-elks/.gitignore | 2 + template-jacly-elks/package.json | 25 + template-jacly-elks/src/index.jacly | 490 ++++++++++++++ template-jacly-robutek2/.gitignore | 2 + template-jacly-robutek2/package.json | 25 + template-jacly-robutek2/src/index.jacly | 625 ++++++++++++++++++ template-jacly/.gitignore | 2 + template-jacly/package.json | 24 + template-jacly/src/index.jacly | 19 + template-jaculus-robutek2/.gitignore | 2 + template-jaculus-robutek2/package.json | 23 + template-jaculus-robutek2/src/index.ts | 16 + template-jaculus-robutek2/tsconfig.json | 12 + template-jaculus-smartled/.gitignore | 2 + template-jaculus-smartled/package.json | 22 + template-jaculus-smartled/src/index.ts | 8 + template-jaculus-smartled/tsconfig.json | 12 + template-jaculus/.gitignore | 2 + template-jaculus/package.json | 22 + template-jaculus/src/index.ts | 16 + template-jaculus/tsconfig.json | 12 + utils-i2c/.gitignore | 2 + utils-i2c/README.md | 1 + utils-i2c/blocks/translations/cs.lang.json | 10 + utils-i2c/blocks/translations/en.lang.json | 10 + utils-i2c/blocks/utils_i2c.jacly.json | 47 ++ utils-i2c/package.json | 24 + utils-i2c/src/index.ts | 40 ++ utils-i2c/tsconfig.json | 15 + utils/.gitignore | 2 + utils/README.md | 104 +++ utils/blocks/translations/cs.lang.json | 10 + utils/blocks/translations/en.lang.json | 10 + utils/blocks/utils.jacly.json | 163 +++++ utils/package.json | 25 + utils/src/index.ts | 45 ++ utils/tsconfig.json | 14 + vl53l0x/.gitignore | 2 + vl53l0x/README.md | 1 + vl53l0x/blocks/translations/cs.lang.json | 14 + vl53l0x/blocks/translations/en.lang.json | 14 + vl53l0x/blocks/vl53l0x.jacly.json | 86 +++ vl53l0x/package.json | 28 + vl53l0x/src/index.ts | 95 +++ vl53l0x/tsconfig.json | 15 + zscs2016c/.gitignore | 2 + zscs2016c/README.md | 1 + zscs2016c/blocks/translations/cs.lang.json | 32 + zscs2016c/blocks/translations/en.lang.json | 32 + zscs2016c/blocks/zscs2016c.jacly.json | 180 +++++ zscs2016c/package.json | 31 + zscs2016c/src/index.ts | 297 +++++++++ zscs2016c/tsconfig.json | 15 + 259 files changed, 16491 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/publish-registry.yml create mode 100644 .github/workflows/test-registry.yml create mode 100644 .prettierignore create mode 100644 .prettierrc.json rename {empty-template => @types/jaculus}/.gitignore (100%) rename {empty-template => @types/jaculus}/README.md (100%) create mode 100644 @types/jaculus/blocks/adc.jacly.json create mode 100644 @types/jaculus/blocks/communication.jacly.json create mode 100644 @types/jaculus/blocks/control.jacly.json create mode 100644 @types/jaculus/blocks/gpio.jacly.json create mode 100644 @types/jaculus/blocks/i2c.jacly.json create mode 100644 @types/jaculus/blocks/keyvalue.jacly.json create mode 100644 @types/jaculus/blocks/ledc.jacly.json create mode 100644 @types/jaculus/blocks/motor.jacly.json create mode 100644 @types/jaculus/blocks/movement.jacly.json create mode 100644 @types/jaculus/blocks/peripherals.jacly.json create mode 100644 @types/jaculus/blocks/protocols.jacly.json create mode 100644 @types/jaculus/blocks/pulseCounter.jacly.json create mode 100644 @types/jaculus/blocks/simpleradio.jacly.json create mode 100644 @types/jaculus/blocks/smartled.jacly.json create mode 100644 @types/jaculus/blocks/spi.jacly.json create mode 100644 @types/jaculus/blocks/stream.jacly.json create mode 100644 @types/jaculus/blocks/translations/cs.lang.json create mode 100644 @types/jaculus/blocks/translations/en.lang.json create mode 100644 @types/jaculus/blocks/udp.jacly.json create mode 100644 @types/jaculus/blocks/wifi.jacly.json create mode 100644 @types/jaculus/package.json create mode 100644 @types/jaculus/src/types/adc.d.ts create mode 100644 @types/jaculus/src/types/basicStream.d.ts create mode 100644 @types/jaculus/src/types/fs.d.ts create mode 100644 @types/jaculus/src/types/globalTypes.d.ts create mode 100644 @types/jaculus/src/types/gpio.d.ts create mode 100644 @types/jaculus/src/types/gridui.d.ts create mode 100644 @types/jaculus/src/types/i2c.d.ts create mode 100644 @types/jaculus/src/types/index.d.ts create mode 100644 @types/jaculus/src/types/keyvalue.d.ts create mode 100644 @types/jaculus/src/types/ledc.d.ts create mode 100644 @types/jaculus/src/types/misc.d.ts create mode 100644 @types/jaculus/src/types/motor.d.ts create mode 100644 @types/jaculus/src/types/onewire.d.ts create mode 100644 @types/jaculus/src/types/path.d.ts create mode 100644 @types/jaculus/src/types/platform.d.ts create mode 100644 @types/jaculus/src/types/pulseCounter.d.ts create mode 100644 @types/jaculus/src/types/simpleradio.d.ts create mode 100644 @types/jaculus/src/types/smartled.d.ts create mode 100644 @types/jaculus/src/types/spi.d.ts create mode 100644 @types/jaculus/src/types/stdio.d.ts create mode 100644 @types/jaculus/src/types/timers.d.ts create mode 100644 @types/jaculus/src/types/timestamp.d.ts create mode 100644 @types/jaculus/src/types/udpSocket.d.ts create mode 100644 @types/jaculus/src/types/wifi.d.ts create mode 100644 @types/jaculus/tsconfig.json create mode 100644 algorithms/.gitignore create mode 100644 algorithms/README.md create mode 100644 algorithms/blocks/algorithms.jacly.json create mode 100644 algorithms/blocks/translations/cs.lang.json create mode 100644 algorithms/blocks/translations/en.lang.json create mode 100644 algorithms/package.json create mode 100644 algorithms/src/index.ts create mode 100644 algorithms/tsconfig.json create mode 100644 basic/.gitignore create mode 100644 basic/README.md create mode 100644 basic/blocks/basic.jacly.json create mode 100644 basic/blocks/basic_ext.jacly.json create mode 100644 basic/blocks/translations/cs.lang.json create mode 100644 basic/blocks/translations/en.lang.json create mode 100644 basic/package.json create mode 100644 basic/src/index.ts rename {empty-template => basic}/tsconfig.json (85%) create mode 100644 bq2589/.gitignore create mode 100644 bq2589/README.md create mode 100644 bq2589/blocks/bq2589.jacly.json create mode 100644 bq2589/blocks/translations/cs.lang.json create mode 100644 bq2589/blocks/translations/en.lang.json create mode 100644 bq2589/package.json create mode 100644 bq2589/src/index.ts create mode 100644 bq2589/tsconfig.json create mode 100644 button/.gitignore create mode 100644 button/README.md create mode 100644 button/blocks/button.jacly.json create mode 100644 button/blocks/translations/cs.lang.json create mode 100644 button/blocks/translations/en.lang.json create mode 100644 button/package.json create mode 100644 button/src/index.ts create mode 100644 button/tsconfig.json create mode 100644 colors/.gitignore create mode 100644 colors/README.md create mode 100644 colors/blocks/colors.jacly.json create mode 100644 colors/blocks/translations/cs.lang.json create mode 100644 colors/blocks/translations/en.lang.json create mode 100644 colors/package.json create mode 100644 colors/src/index.ts create mode 100644 colors/tsconfig.json create mode 100644 differential-drive/.gitignore create mode 100644 differential-drive/README.md create mode 100644 differential-drive/blocks/differential_drive.jacly.json create mode 100644 differential-drive/blocks/translations/cs.lang.json create mode 100644 differential-drive/blocks/translations/en.lang.json create mode 100644 differential-drive/package.json create mode 100644 differential-drive/src/index.ts create mode 100644 differential-drive/tsconfig.json create mode 100644 ds18b20/.gitignore create mode 100644 ds18b20/README.md create mode 100644 ds18b20/blocks/ds18b20.jacly.json create mode 100644 ds18b20/blocks/translations/cs.lang.json create mode 100644 ds18b20/blocks/translations/en.lang.json create mode 100644 ds18b20/package.json create mode 100644 ds18b20/src/index.ts create mode 100644 ds18b20/tsconfig.json create mode 100644 ds3231/.gitignore create mode 100644 ds3231/README.md create mode 100644 ds3231/blocks/ds3231.jacly.json create mode 100644 ds3231/blocks/translations/cs.lang.json create mode 100644 ds3231/blocks/translations/en.lang.json create mode 100644 ds3231/package.json create mode 100644 ds3231/src/index.ts create mode 100644 ds3231/tsconfig.json create mode 100644 elks/.gitignore create mode 100644 elks/README.md create mode 100644 elks/blocks/elks_pins.jacly.json create mode 100644 elks/blocks/translations/cs.lang.json create mode 100644 elks/blocks/translations/en.lang.json create mode 100644 elks/package.json create mode 100644 elks/src/index.ts create mode 100644 elks/tsconfig.json delete mode 100644 empty-template/blocks/template.json delete mode 100644 empty-template/package.json delete mode 100644 empty-template/src/index.ts create mode 100644 hd44780/.gitignore create mode 100644 hd44780/README.md create mode 100644 hd44780/blocks/hd44780.jacly.json create mode 100644 hd44780/blocks/translations/cs.lang.json create mode 100644 hd44780/blocks/translations/en.lang.json create mode 100644 hd44780/package.json create mode 100644 hd44780/src/index.ts create mode 100644 hd44780/tsconfig.json create mode 100644 jacly-meta-blocks/.gitignore create mode 100644 jacly-meta-blocks/README.md create mode 100644 jacly-meta-blocks/blocks/functions.jacly.json create mode 100644 jacly-meta-blocks/blocks/json.jacly.json create mode 100644 jacly-meta-blocks/blocks/lists.jacly.json create mode 100644 jacly-meta-blocks/blocks/logic.jacly.json create mode 100644 jacly-meta-blocks/blocks/loops.jacly.json create mode 100644 jacly-meta-blocks/blocks/math.jacly.json create mode 100644 jacly-meta-blocks/blocks/text.jacly.json create mode 100644 jacly-meta-blocks/blocks/text_ext.jacly.json create mode 100644 jacly-meta-blocks/blocks/translations/cs.lang.json create mode 100644 jacly-meta-blocks/blocks/translations/en.lang.json create mode 100644 jacly-meta-blocks/blocks/variables.jacly.json create mode 100644 jacly-meta-blocks/package.json create mode 100644 jacly-meta-blocks/tsconfig.json create mode 100644 lm75a/.gitignore create mode 100644 lm75a/README.md create mode 100644 lm75a/blocks/lm75a.jacly.json create mode 100644 lm75a/blocks/translations/cs.lang.json create mode 100644 lm75a/blocks/translations/en.lang.json create mode 100644 lm75a/package.json create mode 100644 lm75a/src/index.ts create mode 100644 lm75a/tsconfig.json create mode 100644 r-encoder/.gitignore create mode 100644 r-encoder/README.md create mode 100644 r-encoder/blocks/r_encoder.jacly.json create mode 100644 r-encoder/blocks/translations/cs.lang.json create mode 100644 r-encoder/blocks/translations/en.lang.json create mode 100644 r-encoder/package.json create mode 100644 r-encoder/src/index.ts create mode 100644 r-encoder/tsconfig.json create mode 100644 readline/.gitignore create mode 100644 readline/README.md create mode 100644 readline/blocks/readline.jacly.json create mode 100644 readline/blocks/translations/cs.lang.json create mode 100644 readline/blocks/translations/en.lang.json create mode 100644 readline/package.json create mode 100644 readline/src/index.ts create mode 100644 readline/tsconfig.json create mode 100644 robutek/.gitignore create mode 100644 robutek/README.md create mode 100644 robutek/blocks/robutek2.jacly.json create mode 100644 robutek/blocks/robutek2_pins.jacly.json create mode 100644 robutek/blocks/translations/cs.lang.json create mode 100644 robutek/blocks/translations/en.lang.json create mode 100644 robutek/package.json create mode 100644 robutek/src/index.ts create mode 100644 robutek/tsconfig.json create mode 100644 servo/.gitignore create mode 100644 servo/README.md create mode 100644 servo/blocks/servo.jacly.json create mode 100644 servo/blocks/translations/cs.lang.json create mode 100644 servo/blocks/translations/en.lang.json create mode 100644 servo/package.json create mode 100644 servo/src/index.ts create mode 100644 servo/tsconfig.json create mode 100644 smartled-ext/.gitignore create mode 100644 smartled-ext/README.md create mode 100644 smartled-ext/blocks/smartled_ext.jacly.json create mode 100644 smartled-ext/blocks/translations/cs.lang.json create mode 100644 smartled-ext/blocks/translations/en.lang.json create mode 100644 smartled-ext/package.json create mode 100644 smartled-ext/src/index.ts create mode 100644 smartled-ext/tsconfig.json create mode 100644 template-jacly-elks/.gitignore create mode 100644 template-jacly-elks/package.json create mode 100644 template-jacly-elks/src/index.jacly create mode 100644 template-jacly-robutek2/.gitignore create mode 100644 template-jacly-robutek2/package.json create mode 100644 template-jacly-robutek2/src/index.jacly create mode 100644 template-jacly/.gitignore create mode 100644 template-jacly/package.json create mode 100644 template-jacly/src/index.jacly create mode 100644 template-jaculus-robutek2/.gitignore create mode 100644 template-jaculus-robutek2/package.json create mode 100644 template-jaculus-robutek2/src/index.ts create mode 100644 template-jaculus-robutek2/tsconfig.json create mode 100644 template-jaculus-smartled/.gitignore create mode 100644 template-jaculus-smartled/package.json create mode 100644 template-jaculus-smartled/src/index.ts create mode 100644 template-jaculus-smartled/tsconfig.json create mode 100644 template-jaculus/.gitignore create mode 100644 template-jaculus/package.json create mode 100644 template-jaculus/src/index.ts create mode 100644 template-jaculus/tsconfig.json create mode 100644 utils-i2c/.gitignore create mode 100644 utils-i2c/README.md create mode 100644 utils-i2c/blocks/translations/cs.lang.json create mode 100644 utils-i2c/blocks/translations/en.lang.json create mode 100644 utils-i2c/blocks/utils_i2c.jacly.json create mode 100644 utils-i2c/package.json create mode 100644 utils-i2c/src/index.ts create mode 100644 utils-i2c/tsconfig.json create mode 100644 utils/.gitignore create mode 100644 utils/README.md create mode 100644 utils/blocks/translations/cs.lang.json create mode 100644 utils/blocks/translations/en.lang.json create mode 100644 utils/blocks/utils.jacly.json create mode 100644 utils/package.json create mode 100644 utils/src/index.ts create mode 100644 utils/tsconfig.json create mode 100644 vl53l0x/.gitignore create mode 100644 vl53l0x/README.md create mode 100644 vl53l0x/blocks/translations/cs.lang.json create mode 100644 vl53l0x/blocks/translations/en.lang.json create mode 100644 vl53l0x/blocks/vl53l0x.jacly.json create mode 100644 vl53l0x/package.json create mode 100644 vl53l0x/src/index.ts create mode 100644 vl53l0x/tsconfig.json create mode 100644 zscs2016c/.gitignore create mode 100644 zscs2016c/README.md create mode 100644 zscs2016c/blocks/translations/cs.lang.json create mode 100644 zscs2016c/blocks/translations/en.lang.json create mode 100644 zscs2016c/blocks/zscs2016c.jacly.json create mode 100644 zscs2016c/package.json create mode 100644 zscs2016c/src/index.ts create mode 100644 zscs2016c/tsconfig.json diff --git a/.github/workflows/publish-registry.yml b/.github/workflows/publish-registry.yml new file mode 100644 index 0000000..58d7abc --- /dev/null +++ b/.github/workflows/publish-registry.yml @@ -0,0 +1,160 @@ +name: Publish Registry + +on: + push: + branches: + - main + - master + workflow_dispatch: + +permissions: + contents: write + +jobs: + publish: + runs-on: ubuntu-latest + env: + TZ: Europe/Prague + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Set to 'true' to allow the workflow to push updates to the registry branch + PUSH_REGISTRY: "true" + # Set to 'true' to override whole branch with generated content (keeps just one commit history) + OVERRIDE_BRANCH: "false" + # Default registry repo URL; derived from the current repository + REGISTRY_GIT_URL: https://github.com/${{ github.repository }}.git + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 # Full history for tags + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: "24.x" + + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: latest + + - name: Configure command PATH + run: | + echo "PNPM_HOME=$HOME/.local/share/pnpm" >> "$GITHUB_ENV" + mkdir -p "$HOME/.local/share/pnpm/bin" + echo "$HOME/.local/share/pnpm/bin" >> "$GITHUB_PATH" + pnpm config set global-bin-dir "$HOME/.local/share/pnpm/bin" + + - name: Get pnpm store directory + id: pnpm-cache + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT + + - name: Setup pnpm cache + uses: actions/cache@v4 + with: + path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + run: pnpm install + + - name: Checkout Jaculus-tools + run: | + git clone https://github.com/jaculus-org/Jaculus-tools.git --branch master ../Jaculus-tools + cd ../Jaculus-tools + pnpm install + pnpm build + cd packages/tools + pnpm add -g . + + - name: Clone and Build Registry Tool + run: | + git clone https://github.com/jaculus-org/Jaculus-registry.git ../jaculus-registry + cd ../jaculus-registry + pnpm install + # Link local jaculus packages (if any) from the previously cloned Jaculus-tools + for pkg in ../Jaculus-tools/packages/*; do + if [ -f "$pkg/package.json" ]; then + echo "Linking local package: $pkg" + pnpm link "$pkg" || true + fi + done + pnpm build || true + + - name: Clone target registry repo (dist) + run: | + git clone $REGISTRY_GIT_URL ../jaculus-registry-dist + cd ../jaculus-registry-dist + git fetch --all + git checkout registry || git checkout -b registry + cd - + + - name: Start local registry server + run: | + pnpm --dir ../jaculus-registry run cli -- serve "$GITHUB_WORKSPACE" "$GITHUB_WORKSPACE/../jaculus-registry-dist" & + echo "REGISTRY_PID=$!" >> $GITHUB_ENV + # Wait for server to be ready + for i in $(seq 1 10); do + if curl -sf http://127.0.0.1:3737/; then + exit 0 + fi + sleep 1 + done + echo "Registry server did not become ready in time" >&2 + exit 1 + + - name: Prepare filtered registry source and Generate registry + run: | + # Use the previously cloned dist repo as the destination + pnpm --dir ../jaculus-registry run cli -- generate "$GITHUB_WORKSPACE" "$GITHUB_WORKSPACE/../jaculus-registry-dist" + + - name: Stop local registry server + if: always() + run: | + if [ -n "${REGISTRY_PID:-}" ]; then + kill "$REGISTRY_PID" || true + fi + + - name: Copy dist to accessible location + run: | + mkdir -p jaculus-registry-artifact + cp -r ../jaculus-registry-dist/* jaculus-registry-artifact/ + + - name: Upload registry dist artifact + uses: actions/upload-artifact@v7 + with: + name: registry-dist + path: jaculus-registry-artifact/ + + - name: Push registry branch (optional) + if: ${{ env.PUSH_REGISTRY == 'true' && github.event_name == 'push' }} + run: | + set -euo pipefail + cd ../jaculus-registry-dist + + # Configure git (email is optional - GitHub Actions provides defaults) + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # Set up authentication + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + git checkout registry || git checkout -b registry + + # If OVERRIDE_BRANCH is enabled, reset to single commit, otherwise just add commit + if [ "${{ env.OVERRIDE_BRANCH }}" = "true" ]; then + # Remove all history and keep only current state + git add -A + git commit --allow-empty -m "Update registry via GitHub Actions (${GITHUB_SHA})" + git reset --soft $(git rev-list --max-parents=0 HEAD) + git commit --amend -m "Registry build from $(git rev-parse --short HEAD:../Jaculus-libraries/.git/HEAD || echo 'main')" + git push --force origin registry + else + # Regular commit and push + git add -A + git commit -m "Update registry via GitHub Actions (${GITHUB_SHA})" || echo "No changes to commit" + git push origin registry + fi diff --git a/.github/workflows/test-registry.yml b/.github/workflows/test-registry.yml new file mode 100644 index 0000000..821bc16 --- /dev/null +++ b/.github/workflows/test-registry.yml @@ -0,0 +1,125 @@ +name: Test Registry + +on: + pull_request: + branches: + - main + - master + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + env: + TZ: Europe/Prague + # Default registry repo URL; derived from the current repository + REGISTRY_GIT_URL: https://github.com/${{ github.repository }}.git + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 # Full history for tags + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: "24.x" + + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: latest + + - name: Configure command PATH + run: | + echo "PNPM_HOME=$HOME/.local/share/pnpm" >> "$GITHUB_ENV" + mkdir -p "$HOME/.local/share/pnpm/bin" + echo "$HOME/.local/share/pnpm/bin" >> $GITHUB_PATH + pnpm config set global-bin-dir "$HOME/.local/share/pnpm/bin" + + - name: Get pnpm store directory + id: pnpm-cache + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT + + - name: Setup pnpm cache + uses: actions/cache@v4 + with: + path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + run: pnpm install + + - name: Checkout Jaculus-tools + run: | + git clone https://github.com/jaculus-org/Jaculus-tools.git --branch master ../Jaculus-tools + cd ../Jaculus-tools + pnpm install + pnpm build + cd packages/tools + pnpm add -g . + + - name: Clone and Build Registry Tool + run: | + git clone https://github.com/jaculus-org/Jaculus-registry.git ../jaculus-registry + cd ../jaculus-registry + pnpm install + # Link local jaculus packages (if any) from the previously cloned Jaculus-tools + for pkg in ../Jaculus-tools/packages/*; do + if [ -f "$pkg/package.json" ]; then + echo "Linking local package: $pkg" + pnpm link "$pkg" || true + fi + done + pnpm build || true + + - name: Clone target registry repo (dist) + run: | + git clone $REGISTRY_GIT_URL ../jaculus-registry-dist + cd ../jaculus-registry-dist + git fetch --all + git checkout registry || git checkout -b registry + cd - + + - name: Start local registry server + run: | + pnpm --dir ../jaculus-registry run cli -- serve "$GITHUB_WORKSPACE" "$GITHUB_WORKSPACE/../jaculus-registry-dist" & + echo "REGISTRY_PID=$!" >> $GITHUB_ENV + # Wait for server to be ready + for i in $(seq 1 10); do + if curl -sf http://127.0.0.1:3737/; then + exit 0 + fi + sleep 1 + done + echo "Registry server did not become ready in time" >&2 + exit 1 + + - name: Prepare filtered registry source and Generate registry + run: | + # Use the previously cloned dist repo as the destination + pnpm --dir ../jaculus-registry run cli -- generate "$GITHUB_WORKSPACE" "$GITHUB_WORKSPACE/../jaculus-registry-dist" + + - name: Stop local registry server + if: always() + run: | + if [ -n "${REGISTRY_PID:-}" ]; then + kill "$REGISTRY_PID" || true + fi + + - name: Copy dist to accessible location + run: | + mkdir -p jaculus-registry-artifact + cp -r ../jaculus-registry-dist/* jaculus-registry-artifact/ + + - name: Upload registry dist artifact + uses: actions/upload-artifact@v7 + with: + name: registry-dist + path: jaculus-registry-artifact/ diff --git a/.gitignore b/.gitignore index f06235c..a961af8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ node_modules dist + +.jaculus-libs-dist/ +.DS_Store +tsconfig.tsbuildinfo diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..1881214 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +bin/ +**/pnpm-lock.yaml diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..55ea2b2 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,4 @@ +{ + "tabWidth": 4, + "useTabs": false +} diff --git a/empty-template/.gitignore b/@types/jaculus/.gitignore similarity index 100% rename from empty-template/.gitignore rename to @types/jaculus/.gitignore diff --git a/empty-template/README.md b/@types/jaculus/README.md similarity index 100% rename from empty-template/README.md rename to @types/jaculus/README.md diff --git a/@types/jaculus/blocks/adc.jacly.json b/@types/jaculus/blocks/adc.jacly.json new file mode 100644 index 0000000..7638725 --- /dev/null +++ b/@types/jaculus/blocks/adc.jacly.json @@ -0,0 +1,82 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "@types/jaculus", + "category": "adc", + "name": "%T%", + "description": "%T%", + "colour": "#9C27B0", + "icon": "AudioWaveform", + "priority": 105, + "import": ["import * as adc from \"adc\";"], + "contents": [ + { + "kind": "label", + "text": "%SETUP%" + }, + { + "kind": "block", + "type": "adc_configure", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "PIN", + "check": "Number" + }, + { + "type": "field_dropdown", + "name": "ATTEN", + "options": [ + ["12 dB", "adc.Attenuation.Db12"], + ["0 dB", "adc.Attenuation.Db0"], + ["2.5 dB", "adc.Attenuation.Db2_5"], + ["6 dB", "adc.Attenuation.Db6"] + ] + } + ], + "inputs": { + "PIN": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + } + }, + "tooltip": "%T%", + "code": "adc.configure($[PIN], $[ATTEN]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%READ%" + }, + { + "kind": "block", + "type": "adc_read", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "PIN", + "check": "Number" + } + ], + "inputs": { + "PIN": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + } + }, + "output": "Number", + "tooltip": "%T%", + "code": "adc.read($[PIN])" + } + ] +} diff --git a/@types/jaculus/blocks/communication.jacly.json b/@types/jaculus/blocks/communication.jacly.json new file mode 100644 index 0000000..ba9d0bb --- /dev/null +++ b/@types/jaculus/blocks/communication.jacly.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "@types/jaculus", + "category": "communication", + "name": "%T%", + "description": "%T%", + "colour": "#007A4B", + "icon": "Cable", + "priority": 118, + "import": [], + "contents": [] +} diff --git a/@types/jaculus/blocks/control.jacly.json b/@types/jaculus/blocks/control.jacly.json new file mode 100644 index 0000000..5bab1a3 --- /dev/null +++ b/@types/jaculus/blocks/control.jacly.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "@types/jaculus", + "category": "control", + "name": "%T%", + "description": "%T%", + "colour": "#5C2D91", + "icon": "Bolt", + "priority": 125, + "contents": [ + { + "kind": "label", + "text": "%EXIT%" + }, + { + "kind": "block", + "type": "control_exit", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "EXIT_CODE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + } + ], + "previousStatement": null, + "nextStatement": null, + "tooltip": "%T%", + "code": "exit($[EXIT_CODE]);" + }, + { + "kind": "label", + "text": "%DATE%" + }, + { + "kind": "block", + "type": "control_dateNow", + "message0": "%T%", + "output": "Number", + "tooltip": "%T%", + "code": "Date.now()" + } + ] +} diff --git a/@types/jaculus/blocks/gpio.jacly.json b/@types/jaculus/blocks/gpio.jacly.json new file mode 100644 index 0000000..b7f6d8b --- /dev/null +++ b/@types/jaculus/blocks/gpio.jacly.json @@ -0,0 +1,271 @@ +{ + "package": "@types/jaculus", + "category": "gpio", + "name": "%T%", + "description": "%T%", + "colour": "#c58914", + "icon": "Cable", + "priority": 100, + "import": ["import * as gpio from \"gpio\";"], + "examples": [ + { + "kind": "label", + "text": "%EXAMPLE_WRITE%" + }, + { + "kind": "block", + "type": "gpio_pinMode", + "next": { + "block": { + "type": "gpio_write" + } + } + }, + { + "kind": "label", + "text": "%EXAMPLE_READ%" + }, + { + "kind": "block", + "type": "gpio_pinMode", + "fields": { + "MODE": "gpio.PinMode.INPUT" + }, + "next": { + "block": { + "type": "basic_console_log", + "inputs": { + "TEXT": { + "block": { + "type": "gpio_read" + } + } + } + } + } + }, + { + "kind": "label", + "text": "%EXAMPLE_EVENT%" + }, + { + "kind": "block", + "type": "gpio_pinMode", + "fields": { + "MODE": "gpio.PinMode.INPUT" + } + }, + { + "kind": "block", + "type": "gpio_onEvent", + "inputs": { + "CODE": { + "block": { + "type": "basic_console_log" + } + } + } + } + ], + "contents": [ + { + "kind": "label", + "text": "%SETUP%" + }, + { + "kind": "block", + "type": "gpio_pinMode", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "PIN", + "check": "Number" + }, + { + "type": "field_dropdown", + "name": "MODE", + "options": [ + ["%OUTPUT%", "gpio.PinMode.OUTPUT"], + ["%INPUT%", "gpio.PinMode.INPUT"], + ["%INPUT_PULLUP%", "gpio.PinMode.INPUT_PULLUP"], + ["%INPUT_PULLDOWN%", "gpio.PinMode.INPUT_PULLDOWN"], + ["%DISABLE%", "gpio.PinMode.DISABLE"] + ] + } + ], + "inputs": { + "PIN": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 5 + } + } + } + }, + "tooltip": "%T%", + "code": "gpio.pinMode($[PIN], $[MODE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%READ_WRITE%" + }, + { + "kind": "block", + "type": "gpio_write", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "PIN", + "check": "Number" + }, + { + "type": "input_value", + "name": "VALUE", + "check": "Number" + } + ], + "inputs": { + "PIN": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 5 + } + } + }, + "VALUE": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + } + }, + "tooltip": "%T%", + "inputsInline": true, + "code": "gpio.write($[PIN], $[VALUE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "gpio_read", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "PIN", + "check": "Number" + } + ], + "inputs": { + "PIN": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 5 + } + } + } + }, + "tooltip": "%T%", + "output": "Number", + "code": "gpio.read($[PIN])" + }, + { + "kind": "label", + "text": "%EVENTS%" + }, + { + "kind": "block", + "type": "gpio_onEvent", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "EVENT", + "options": [ + ["%FALLING%", "\"falling\""], + ["%RISING%", "\"rising\""], + ["%CHANGE%", "\"change\""] + ] + }, + { + "type": "input_value", + "name": "PIN", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 5 + } + } + }, + { + "type": "input_statement", + "name": "CODE", + "check": "Function" + } + ], + "callbackVars": [ + { + "identifier": "millis", + "message": "%MILLIS%", + "type": "Number", + "codeName": "info.timestamp.millis()" + }, + { + "identifier": "micros", + "message": "%MICROS%", + "type": "Number", + "codeName": "info.timestamp.micros()" + } + ], + "inputsInline": true, + "isProgramStart": true, + "tooltip": "%T%", + "code": "gpio.on($[EVENT], $[PIN], async function(info) {\n$[CODE]\n});" + }, + { + "kind": "block", + "type": "gpio_offEvent", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "EVENT", + "options": [ + ["%RISING%", "\"rising\""], + ["%FALLING%", "\"falling\""], + ["%CHANGE%", "\"change\""] + ] + }, + { + "type": "input_value", + "name": "PIN", + "check": "Number" + } + ], + "inputs": { + "PIN": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 5 + } + } + } + }, + "tooltip": "%T%", + "code": "gpio.off($[EVENT], $[PIN]);", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/@types/jaculus/blocks/i2c.jacly.json b/@types/jaculus/blocks/i2c.jacly.json new file mode 100644 index 0000000..40bdd4c --- /dev/null +++ b/@types/jaculus/blocks/i2c.jacly.json @@ -0,0 +1,89 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "@types/jaculus", + "category": "i2c", + "parentCategory": "protocols", + "name": "%T%", + "description": "%T%", + "colour": "#0F5ACA", + "icon": "Cable", + "priority": 130, + "import": ["import { I2C1, I2C2 } from \"i2c\";\n"], + "contents": [ + { + "kind": "label", + "text": "%SETUP%" + }, + { + "kind": "block", + "type": "i2c_constructor", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "i2c_inst_?" + }, + { + "type": "field_dropdown", + "name": "I2C", + "options": [ + ["%I2C1%", "I2C1"], + ["%I2C2_MAYBE%", "I2C2"] + ] + }, + { + "type": "input_value", + "name": "SDA", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 42 + } + } + }, + { + "type": "input_value", + "name": "SCL", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 8 + } + } + }, + { + "type": "input_value", + "name": "BITRATE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 100000 + } + } + } + ], + "constructs": "i2c_inst", + "tooltip": "%T%", + "codeConditionals": [ + { + "condition": [{ "$[I2C]": "I2C1" }], + "code": "I2C1.setup({ sda: $[SDA], scl: $[SCL], bitrate: $[BITRATE] });\n$[CONSTRUCTED_VAR_NAME] = I2C1;" + }, + { + "condition": [{ "$[I2C]": "I2C2" }], + "code": "I2C2.setup({ sda: $[SDA], scl: $[SCL], bitrate: $[BITRATE] });\n$[CONSTRUCTED_VAR_NAME] = I2C2;" + } + ], + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%IN_FUTURE_RELEASES%" + } + ] +} diff --git a/@types/jaculus/blocks/keyvalue.jacly.json b/@types/jaculus/blocks/keyvalue.jacly.json new file mode 100644 index 0000000..56497a5 --- /dev/null +++ b/@types/jaculus/blocks/keyvalue.jacly.json @@ -0,0 +1,263 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "@types/jaculus", + "category": "keyvalue", + "name": "%T%", + "description": "%T%", + "colour": "#751F5C", + "icon": "Key", + "priority": 120, + "import": ["import * as keyvalue from \"keyvalue\";"], + "contents": [ + { + "kind": "label", + "text": "%OPEN%" + }, + { + "kind": "block", + "type": "kv_open", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "keyValue_?" + }, + { + "type": "input_value", + "name": "NAMESPACE", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "default" + } + } + } + ], + "constructs": "keyValue", + "tooltip": "%T%", + "code": "$[CONSTRUCTED_VAR_NAME] = keyvalue.open($[NAMESPACE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%SET%" + }, + { + "kind": "block", + "type": "kv_set_string", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "KV_INSTANCE", + "instanceof": "keyValue" + }, + { + "type": "input_value", + "name": "KEY", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "my_key" + } + } + }, + { + "type": "input_value", + "name": "VALUE", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "my_value" + } + } + } + ], + "tooltip": "%T%", + "code": "$[KV_INSTANCE].set($[KEY], $[VALUE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "kv_set_number", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "KV_INSTANCE", + "instanceof": "keyValue" + }, + { + "type": "input_value", + "name": "KEY", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "my_key" + } + } + }, + { + "type": "input_value", + "name": "VALUE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 42 + } + } + } + ], + "tooltip": "%T%", + "code": "$[KV_INSTANCE].set($[KEY], $[VALUE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%GET%" + }, + { + "kind": "block", + "type": "kv_get_string", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "KV_INSTANCE", + "instanceof": "keyValue" + }, + { + "type": "input_value", + "name": "KEY", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "my_key" + } + } + } + ], + "inputsInline": true, + "tooltip": "%T%", + "code": "$[KV_INSTANCE].getString($[KEY])", + "output": "String" + }, + { + "kind": "block", + "type": "kv_get_number", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "KV_INSTANCE", + "instanceof": "keyValue" + }, + { + "type": "input_value", + "name": "KEY", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "my_key" + } + } + } + ], + "tooltip": "%T%", + "code": "$[KV_INSTANCE].getNumber($[KEY])", + "output": "Number" + }, + { + "kind": "label", + "text": "%ERASE%" + }, + { + "kind": "block", + "type": "kv_erase", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "KV_INSTANCE", + "instanceof": "keyValue" + }, + { + "type": "input_value", + "name": "KEY", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "my_key" + } + } + } + ], + "tooltip": "%T%", + "code": "$[KV_INSTANCE].erase($[KEY]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%EXISTS%" + }, + { + "kind": "block", + "type": "kv_exists", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "KV_INSTANCE", + "instanceof": "keyValue" + }, + { + "type": "input_value", + "name": "KEY", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "my_key" + } + } + } + ], + "tooltip": "%T%", + "code": "$[KV_INSTANCE].exists($[KEY])", + "output": "Boolean" + }, + { + "kind": "label", + "text": "%COMMIT%" + }, + { + "kind": "block", + "type": "kv_commit", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "KV_INSTANCE", + "instanceof": "keyValue" + } + ], + "tooltip": "%T%", + "code": "$[KV_INSTANCE].commit();", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/@types/jaculus/blocks/ledc.jacly.json b/@types/jaculus/blocks/ledc.jacly.json new file mode 100644 index 0000000..877e930 --- /dev/null +++ b/@types/jaculus/blocks/ledc.jacly.json @@ -0,0 +1,260 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "@types/jaculus", + "category": "ledc", + "name": "%T%", + "description": "%T%", + "colour": "#65c277", + "icon": "LampDesk", + "priority": 105, + "import": ["import * as ledc from \"ledc\";"], + "contents": [ + { + "kind": "label", + "text": "%SETUP%" + }, + { + "kind": "block", + "type": "ledc_configureTimer", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "TIMER", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "FREQUENCY", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1000 + } + } + }, + { + "type": "input_value", + "name": "RESOLUTION", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 10 + } + } + } + ], + "tooltip": "%T%", + "code": "ledc.configureTimer($[TIMER], $[FREQUENCY], $[RESOLUTION]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "ledc_configureChannel", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "CHANNEL", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "PIN", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 5 + } + } + }, + { + "type": "input_value", + "name": "TIMER", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "DUTY", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 512 + } + } + } + ], + "tooltip": "%T%", + "code": "ledc.configureChannel($[CHANNEL], $[PIN], $[TIMER], $[DUTY]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%SET_FREQUENCY%" + }, + { + "kind": "block", + "type": "ledc_setFrequency", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "TIMER", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "FREQUENCY", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1000 + } + } + } + ], + "tooltip": "%T%", + "code": "ledc.setFrequency($[TIMER], $[FREQUENCY]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%SET_DUTY%" + }, + { + "kind": "block", + "type": "ledc_setDuty", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "CHANNEL", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "DUTY", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 512 + } + } + } + ], + "tooltip": "%T%", + "code": "ledc.setDuty($[CHANNEL], $[DUTY]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%STOP%" + }, + { + "kind": "label", + "text": "%STOP2%" + }, + { + "kind": "block", + "type": "ledc_stopTimer", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "TIMER", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + } + ], + "tooltip": "%T%", + "code": "ledc.stopTimer($[TIMER]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "ledc_stopChannel", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "CHANNEL", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + } + ], + "tooltip": "%T%", + "code": "ledc.stopChannel($[CHANNEL]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%EXAMPLE_LED%" + }, + { + "kind": "block", + "type": "ledc_configureTimer", + "next": { + "block": { + "type": "ledc_configureChannel", + "next": { + "block": { + "type": "ledc_setDuty" + } + } + } + } + } + ] +} diff --git a/@types/jaculus/blocks/motor.jacly.json b/@types/jaculus/blocks/motor.jacly.json new file mode 100644 index 0000000..f86a6d2 --- /dev/null +++ b/@types/jaculus/blocks/motor.jacly.json @@ -0,0 +1,525 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "@types/jaculus", + "category": "motor", + "parentCategory": "movement", + "name": "%T%", + "description": "%T%", + "colour": "#AC0F0C", + "icon": "RefreshCcwDot", + "priority": 140, + "import": ["import { Motor } from \"motor\";"], + "contents": [ + { + "kind": "label", + "text": "%SETUP%" + }, + { + "kind": "block", + "type": "motor_constructor_pins", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "MOT_A", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 11 + } + } + }, + { + "type": "input_value", + "name": "MOT_B", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 12 + } + } + }, + { + "type": "input_value", + "name": "ENC_A", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 40 + } + } + }, + { + "type": "input_value", + "name": "ENC_B", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 39 + } + } + } + ], + "hideInToolbox": true, + "inputsInline": true, + "tooltip": "%T%", + "code": "{ motA: $[MOT_A], motB: $[MOT_B], encA: $[ENC_A], encB: $[ENC_B] }", + "output": "MotorPins" + }, + { + "kind": "block", + "type": "motor_constructor_ledc", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "TIMER", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "CHANNEL_A", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "CHANNEL_B", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + } + ], + "hideInToolbox": true, + "inputsInline": true, + "tooltip": "%T%", + "code": "{ timer: $[TIMER], channelA: $[CHANNEL_A], channelB: $[CHANNEL_B] }", + "output": "LedcConfig" + }, + { + "kind": "block", + "type": "motor_constructor_regparams", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "KP", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 7000 + } + } + }, + { + "type": "input_value", + "name": "KI", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 350 + } + } + }, + { + "type": "input_value", + "name": "KD", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 400 + } + } + }, + { + "type": "input_value", + "name": "KV", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 166 + } + } + }, + { + "type": "input_value", + "name": "KA", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 11000 + } + } + }, + { + "type": "input_value", + "name": "KC", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 72 + } + } + }, + { + "type": "input_value", + "name": "MAX_IOUT", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1023 + } + } + }, + { + "type": "input_value", + "name": "UNWIND_FACTOR", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + } + ], + "hideInToolbox": true, + "tooltip": "%T%", + "code": "{ kp: $[KP], ki: $[KI], kd: $[KD], kv: $[KV], ka: $[KA], kc: $[KC], maxIOut: $[MAX_IOUT], unwindFactor: $[UNWIND_FACTOR] }", + "output": "RegParams" + }, + { + "kind": "block", + "type": "motor_constructor", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "motor_?" + }, + { + "type": "input_value", + "name": "PINS", + "check": "MotorPins", + "block": { + "type": "motor_constructor_pins" + } + }, + { + "type": "input_value", + "name": "LEDC", + "check": "LedcConfig", + "block": { + "type": "motor_constructor_ledc" + } + }, + { + "type": "input_value", + "name": "REG_PARAMS", + "check": "RegParams", + "block": { + "type": "motor_constructor_regparams" + } + }, + { + "type": "input_value", + "name": "ENC_TICKS", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 560 + } + } + }, + { + "type": "input_value", + "name": "CIRCUMFERENCE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 104.6 + } + } + } + ], + "hideInToolbox": true, + "constructs": "motor", + "tooltip": "%T%", + "code": "$[CONSTRUCTED_VAR_NAME] = new Motor({ pins: $[PINS], ledc: $[LEDC], reg: $[REG_PARAMS], encTicks: $[ENC_TICKS], circumference: $[CIRCUMFERENCE] });", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "ledc_configureTimer", + "inputs": { + "FREQUENCY": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 20000 + } + } + } + }, + "next": { + "block": { + "type": "motor_constructor", + "next": { + "block": { + "type": "motor_setSpeed", + "next": { + "block": { + "type": "motor_setRamp" + } + } + } + } + } + } + }, + { + "kind": "label", + "text": "%REQUIRED_CONFIG%" + }, + { + "kind": "block", + "type": "motor_setSpeed", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "MOTOR_INSTANCE", + "instanceof": "motor" + }, + { + "type": "input_value", + "name": "SPEED", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 100 + } + } + } + ], + "tooltip": "%T%", + "code": "$[MOTOR_INSTANCE].setSpeed($[SPEED]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "motor_setRamp", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "MOTOR_INSTANCE", + "instanceof": "motor" + }, + { + "type": "input_value", + "name": "RAMP", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 2000 + } + } + } + ], + "tooltip": "%T%", + "code": "$[MOTOR_INSTANCE].setRamp($[RAMP]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%OPERATIONS%" + }, + { + "kind": "block", + "type": "motor_move_distance", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "MOTOR_INSTANCE", + "instanceof": "motor" + }, + { + "type": "input_value", + "name": "DISTANCE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 100 + } + } + } + ], + "tooltip": "%T%", + "code": "await $[MOTOR_INSTANCE].move({ distance: $[DISTANCE] });", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "motor_move_time", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "MOTOR_INSTANCE", + "instanceof": "motor" + }, + { + "type": "input_value", + "name": "DURATION", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 100 + } + } + } + ], + "tooltip": "%T%", + "code": "await $[MOTOR_INSTANCE].move({ time: $[DURATION] });", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "motor_move_infinite", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "MOTOR_INSTANCE", + "instanceof": "motor" + } + ], + "tooltip": "%T%", + "code": "$[MOTOR_INSTANCE].move();", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "motor_setRaw", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "MOTOR_INSTANCE", + "instanceof": "motor" + }, + { + "type": "input_value", + "name": "POWER", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + } + ], + "tooltip": "%T%", + "code": "$[MOTOR_INSTANCE].setRaw($[POWER]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "motor_stop", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "MOTOR_INSTANCE", + "instanceof": "motor" + }, + { + "type": "field_dropdown", + "name": "BRAKE", + "options": [ + ["%TRUE%", "true"], + ["%FALSE%", "false"] + ] + } + ], + "tooltip": "%T%", + "code": "await $[MOTOR_INSTANCE].stop($[BRAKE] === 'true');", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "motor_getPosition", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "MOTOR_INSTANCE", + "instanceof": "motor" + } + ], + "tooltip": "%T%", + "code": "$[MOTOR_INSTANCE].getPosition()", + "output": "Number" + }, + { + "kind": "block", + "type": "motor_close", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "MOTOR_INSTANCE", + "instanceof": "motor" + } + ], + "tooltip": "%T%", + "code": "$[MOTOR_INSTANCE].close();", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/@types/jaculus/blocks/movement.jacly.json b/@types/jaculus/blocks/movement.jacly.json new file mode 100644 index 0000000..beff823 --- /dev/null +++ b/@types/jaculus/blocks/movement.jacly.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "@types/jaculus", + "category": "movement", + "name": "%T%", + "description": "%T%", + "colour": "#2D54CF", + "icon": "Cable", + "priority": 118, + "import": [], + "contents": [] +} diff --git a/@types/jaculus/blocks/peripherals.jacly.json b/@types/jaculus/blocks/peripherals.jacly.json new file mode 100644 index 0000000..58dd33d --- /dev/null +++ b/@types/jaculus/blocks/peripherals.jacly.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "@types/jaculus", + "category": "peripherals", + "name": "%T%", + "description": "%T%", + "colour": "#2D54CF", + "icon": "Cable", + "priority": 118, + "import": [], + "contents": [] +} diff --git a/@types/jaculus/blocks/protocols.jacly.json b/@types/jaculus/blocks/protocols.jacly.json new file mode 100644 index 0000000..92b17b7 --- /dev/null +++ b/@types/jaculus/blocks/protocols.jacly.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "@types/jaculus", + "category": "protocols", + "name": "%T%", + "description": "%T%", + "colour": "#5A4BE8", + "icon": "Cable", + "priority": 118, + "import": [], + "contents": [] +} diff --git a/@types/jaculus/blocks/pulseCounter.jacly.json b/@types/jaculus/blocks/pulseCounter.jacly.json new file mode 100644 index 0000000..8c920ec --- /dev/null +++ b/@types/jaculus/blocks/pulseCounter.jacly.json @@ -0,0 +1,185 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "@types/jaculus", + "category": "pulseCounter", + "name": "%T%", + "description": "%T%", + "colour": "#051F5C", + "icon": "Activity", + "priority": 135, + "import": [ + "import { PulseCounter, LevelAction, EdgeAction } from \"pulseCounter\";" + ], + "contents": [ + { + "kind": "label", + "text": "%SETUP%" + }, + { + "kind": "label", + "text": "%SETUP_DETAILS%" + }, + { + "kind": "block", + "type": "pc_constructor", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "pCounter_?" + }, + { + "type": "input_value", + "name": "PIN_LEVEL", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 12 + } + } + }, + { + "type": "input_value", + "name": "PIN_EDGE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 13 + } + } + }, + { + "type": "field_dropdown", + "name": "LEVEL_LOW_ACTION", + "options": [ + ["%KEEP%", "LevelAction.Keep"], + ["%INVERSE%", "LevelAction.Inverse"], + ["%HOLD%", "LevelAction.Hold"] + ] + }, + { + "type": "field_dropdown", + "name": "LEVEL_HIGH_ACTION", + "options": [ + ["%KEEP%", "LevelAction.Keep"], + ["%INVERSE%", "LevelAction.Inverse"], + ["%HOLD%", "LevelAction.Hold"] + ] + }, + { + "type": "field_dropdown", + "name": "EDGE_POS_ACTION", + "options": [ + ["%HOLD%", "EdgeAction.Hold"], + ["%INCREASE%", "EdgeAction.Increase"], + ["%DECREASE%", "EdgeAction.Decrease"] + ] + }, + { + "type": "field_dropdown", + "name": "EDGE_NEG_ACTION", + "options": [ + ["%HOLD%", "EdgeAction.Hold"], + ["%INCREASE%", "EdgeAction.Increase"], + ["%DECREASE%", "EdgeAction.Decrease"] + ] + } + ], + "constructs": "pCounter", + "tooltip": "%T%", + "code": "$[CONSTRUCTED_VAR_NAME] = new PulseCounter({ pinLevel: $[PIN_LEVEL], pinEdge: $[PIN_EDGE], levelMode: { low: $[LEVEL_LOW_ACTION], high: $[LEVEL_HIGH_ACTION] }, edgeMode: { pos: $[EDGE_POS_ACTION], neg: $[EDGE_NEG_ACTION] } });", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%READ%" + }, + { + "kind": "block", + "type": "pc_read", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PC_INSTANCE", + "instanceof": "pCounter" + } + ], + "tooltip": "%T%", + "output": "Number", + "code": "$[PC_INSTANCE].read()" + }, + { + "kind": "label", + "text": "%OTHER%" + }, + { + "kind": "block", + "type": "pc_clear", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PC_INSTANCE", + "instanceof": "pCounter" + } + ], + "tooltip": "%T%", + "code": "$[PC_INSTANCE].clear();", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "pc_start", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PC_INSTANCE", + "instanceof": "pCounter" + } + ], + "tooltip": "%T%", + "code": "$[PC_INSTANCE].start();", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "pc_stop", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PC_INSTANCE", + "instanceof": "pCounter" + } + ], + "tooltip": "%T%", + "code": "$[PC_INSTANCE].stop();", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "pc_close", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PC_INSTANCE", + "instanceof": "pCounter" + } + ], + "tooltip": "%T%", + "code": "$[PC_INSTANCE].close();", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/@types/jaculus/blocks/simpleradio.jacly.json b/@types/jaculus/blocks/simpleradio.jacly.json new file mode 100644 index 0000000..304e55c --- /dev/null +++ b/@types/jaculus/blocks/simpleradio.jacly.json @@ -0,0 +1,335 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "@types/jaculus", + "category": "simpleradio", + "parentCategory": "communication", + "name": "%T%", + "description": "%T%", + "colour": "#9C27B0", + "icon": "RadioTower", + "priority": 105, + "import": ["import * as simpleradio from \"simpleradio\";"], + "contents": [ + { + "kind": "label", + "text": "%BEGIN%" + }, + { + "kind": "block", + "type": "sr_begin", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "SR_GROUP", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + } + ], + "tooltip": "%T%", + "code": "simpleradio.begin($[SR_GROUP]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%SEND%" + }, + { + "kind": "block", + "type": "sr_sendString", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "SR_STRING", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "Hello, World!" + } + } + } + ], + "tooltip": "%T%", + "code": "simpleradio.sendString($[SR_STRING]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "sr_sendNumber", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "SR_NUMBER", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 42 + } + } + } + ], + "tooltip": "%T%", + "code": "simpleradio.sendNumber($[SR_NUMBER]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "sr_sendKeyValue", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "SR_KV_KEY", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "my_key" + } + } + }, + { + "type": "input_value", + "name": "SR_KV_VALUE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 42 + } + } + } + ], + "inputsInline": true, + "tooltip": "%T%", + "code": "simpleradio.sendKeyValue($[SR_KV_KEY], $[SR_KV_VALUE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "sr_sendJson", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "SR_JSON", + "check": "JsonObject" + } + ], + "tooltip": "%T%", + "code": "simpleradio.sendString(JSON.stringify($[SR_JSON]));", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%RECEIVE%" + }, + { + "kind": "block", + "type": "sr_onReceiveNumber", + "message0": "%T%", + "args0": [ + { + "type": "input_statement", + "name": "SR_CODE" + } + ], + "callbackVars": [ + { + "identifier": "receivedNumber", + "message": "%receivedNumber%", + "type": "Number", + "codeName": "num" + }, + { + "identifier": "senderAddress", + "message": "%senderAddress%", + "type": "String", + "codeName": "info.address" + } + ], + "isProgramStart": true, + "tooltip": "%T%", + "code": "simpleradio.on(\"number\", function(num, info) {\n$[SR_CODE]\n});" + }, + { + "kind": "block", + "type": "sr_onReceiveString", + "message0": "%T%", + "args0": [ + { + "type": "input_statement", + "name": "SR_CODE" + } + ], + "callbackVars": [ + { + "identifier": "receivedString", + "message": "%receivedString%", + "type": "String", + "codeName": "str" + }, + { + "identifier": "senderAddress", + "message": "%senderAddress%", + "type": "String", + "codeName": "info.address" + } + ], + "isProgramStart": true, + "tooltip": "%T%", + "code": "simpleradio.on(\"string\", function(str, info) {\n$[SR_CODE]\n});" + }, + { + "kind": "block", + "type": "sr_onReceiveJson", + "message0": "%T%", + "args0": [ + { + "type": "input_statement", + "name": "SR_CODE" + } + ], + "callbackVars": [ + { + "identifier": "receivedJson", + "message": "%receivedJson%", + "type": "JsonObject", + "codeName": "obj" + }, + { + "identifier": "senderAddress", + "message": "%senderAddress%", + "type": "String", + "codeName": "info.address" + } + ], + "isProgramStart": true, + "tooltip": "%T%", + "code": "simpleradio.on(\"string\", function(str, info) {\n\tconst obj = JSON.parse(str);\n$[SR_CODE]\n});" + }, + { + "kind": "block", + "type": "sr_onReceiveKeyValue", + "message0": "%T%", + "args0": [ + { + "type": "input_statement", + "name": "SR_CODE" + } + ], + "callbackVars": [ + { + "identifier": "receivedKey", + "message": "%receivedKey%", + "type": "String", + "codeName": "key" + }, + { + "identifier": "receivedValue", + "message": "%receivedValue%", + "type": "Number", + "codeName": "value" + }, + { + "identifier": "senderAddress", + "message": "%senderAddress%", + "type": "String", + "codeName": "info.address" + } + ], + "isProgramStart": true, + "tooltip": "%T%", + "code": "simpleradio.on(\"keyvalue\", function(key, value, info) {\n$[SR_CODE]\n});" + }, + { + "kind": "label", + "text": "%END_RECEIVE%" + }, + { + "kind": "block", + "type": "sr_offReceive", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SR_TYPE", + "options": [ + ["%NUMBER%", "number"], + ["%STRING%", "string"], + ["%KEY_VALUE%", "keyvalue"] + ] + } + ], + "tooltip": "%T%", + "code": "simpleradio.off($[SR_TYPE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%OTHER%" + }, + { + "kind": "block", + "type": "sr_setGroup", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "SR_GROUP", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + } + ], + "tooltip": "%T%", + "code": "simpleradio.setGroup($[SR_GROUP]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "sr_getGroup", + "message0": "%T%", + "tooltip": "%T%", + "code": "simpleradio.group()", + "output": "Number" + }, + { + "kind": "block", + "type": "sr_getAddress", + "message0": "%T%", + "tooltip": "%T%", + "code": "simpleradio.address()", + "output": "String" + }, + { + "kind": "block", + "type": "sr_end", + "message0": "%T%", + "tooltip": "%T%", + "code": "simpleradio.end();", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/@types/jaculus/blocks/smartled.jacly.json b/@types/jaculus/blocks/smartled.jacly.json new file mode 100644 index 0000000..5513608 --- /dev/null +++ b/@types/jaculus/blocks/smartled.jacly.json @@ -0,0 +1,194 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "@types/jaculus", + "category": "smartled", + "name": "%T%", + "description": "%T%", + "colour": "#00A4A6", + "icon": "Lightbulb", + "priority": 130, + "import": [ + "import { SmartLed, LED_WS2812, LED_WS2812B, LED_WS2812B_2020, LED_SK6812, LED_WS2813 } from \"smartled\";", + "import * as colors from \"colors\";" + ], + "contents": [ + { + "kind": "label", + "text": "%EXAMPLE%" + }, + { + "kind": "block", + "type": "strip_constructor", + "next": { + "block": { + "type": "strip_set", + "next": { + "block": { + "type": "strip_show" + } + } + } + } + }, + { + "kind": "label", + "text": "%SETUP%" + }, + { + "kind": "block", + "type": "strip_constructor", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "strip_?" + }, + { + "type": "input_value", + "name": "LED_PIN", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 48 + } + } + }, + { + "type": "input_value", + "name": "LED_COUNT", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 8 + } + } + }, + { + "type": "field_dropdown", + "name": "LED_TYPE", + "options": [ + ["%LED_WS2812%", "LED_WS2812"], + ["%LED_WS2812B%", "LED_WS2812B"], + ["%LED_WS2812B_2020%", "LED_WS2812B_2020"], + ["%LED_SK6812%", "LED_SK6812"], + ["%LED_WS2813%", "LED_WS2813"] + ] + } + ], + "constructs": "strip", + "tooltip": "%T%", + "code": "$[CONSTRUCTED_VAR_NAME] = new SmartLed($[LED_PIN], $[LED_COUNT], $[LED_TYPE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%SET%" + }, + { + "kind": "block", + "type": "strip_set", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "STRIP_INSTANCE", + "instanceof": "strip" + }, + { + "type": "input_value", + "name": "INDEX", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "COLOR_RGB", + "check": "Color", + "block": { + "type": "dynamic_color_rgb" + } + } + ], + "tooltip": "%T%", + "inputsInline": true, + "code": "$[STRIP_INSTANCE].set($[INDEX], $[COLOR_RGB]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%SHOW%" + }, + { + "kind": "block", + "type": "strip_show", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "STRIP_INSTANCE", + "instanceof": "strip" + } + ], + "tooltip": "%T%", + "code": "$[STRIP_INSTANCE].show();", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%OTHERS%" + }, + { + "kind": "block", + "type": "strip_get", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "STRIP_INSTANCE", + "instanceof": "strip" + }, + { + "type": "input_value", + "name": "INDEX", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + } + ], + "tooltip": "%T%", + "code": "$[STRIP_INSTANCE].get($[INDEX])", + "output": "Color" + }, + { + "kind": "block", + "type": "strip_clear", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "STRIP_INSTANCE", + "instanceof": "strip" + } + ], + "tooltip": "%T%", + "code": "$[STRIP_INSTANCE].clear();", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/@types/jaculus/blocks/spi.jacly.json b/@types/jaculus/blocks/spi.jacly.json new file mode 100644 index 0000000..50727f0 --- /dev/null +++ b/@types/jaculus/blocks/spi.jacly.json @@ -0,0 +1,239 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "@types/jaculus", + "category": "spi", + "parentCategory": "protocols", + "name": "%T%", + "description": "%T%", + "colour": "#E67E22", + "icon": "Cable", + "priority": 118, + "import": ["import { SPI1, SPI2, SPI3 } from \"spi\";"], + "contents": [ + { + "kind": "label", + "text": "%SETUP%" + }, + { + "kind": "block", + "type": "spi_setup", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "spi_?" + }, + { + "type": "field_dropdown", + "name": "SPI", + "options": [ + ["%SPI1%", "SPI1"], + ["%SPI2%", "SPI2"], + ["%SPI3_MAYBE%", "SPI3"] + ] + }, + { + "type": "input_value", + "name": "SCK", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 18 + } + } + }, + { + "type": "input_value", + "name": "MOSI", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 23 + } + } + }, + { + "type": "input_value", + "name": "MISO", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 19 + } + } + }, + { + "type": "input_value", + "name": "BAUD", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1000000 + } + } + }, + { + "type": "input_value", + "name": "MODE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "field_dropdown", + "name": "ORDER", + "options": [ + ["%MSB%", "\"msb\""], + ["%LSB%", "\"lsb\""] + ] + } + ], + "constructs": "spi", + "tooltip": "%T%", + "code": "$[SPI].setup({ sck: $[SCK], mosi: $[MOSI], miso: $[MISO], baud: $[BAUD], mode: $[MODE], order: $[ORDER] });\n$[CONSTRUCTED_VAR_NAME] = $[SPI];", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%TRANSACTIONS%" + }, + { + "kind": "block", + "type": "spi_send", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SPI_INSTANCE", + "instanceof": "spi" + }, + { + "type": "input_value", + "name": "DATA", + "shadow": { + "type": "text", + "fields": { + "TEXT": "hello" + } + } + }, + { + "type": "input_value", + "name": "CS", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 5 + } + } + } + ], + "tooltip": "%T%", + "code": "$[SPI_INSTANCE].send($[DATA], $[CS])", + "output": "ArrayBuffer" + }, + { + "kind": "block", + "type": "spi_write", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SPI_INSTANCE", + "instanceof": "spi" + }, + { + "type": "input_value", + "name": "DATA", + "shadow": { + "type": "text", + "fields": { + "TEXT": "hello" + } + } + }, + { + "type": "input_value", + "name": "CS", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 5 + } + } + } + ], + "tooltip": "%T%", + "code": "$[SPI_INSTANCE].write($[DATA], $[CS]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "spi_transfer", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SPI_INSTANCE", + "instanceof": "spi" + }, + { + "type": "input_value", + "name": "DATA", + "shadow": { + "type": "text", + "fields": { + "TEXT": "hello" + } + } + }, + { + "type": "input_value", + "name": "CS", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 5 + } + } + }, + { + "type": "input_value", + "name": "RX_LENGTH", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + }, + { + "type": "field_dropdown", + "name": "QIO", + "options": [ + ["%FALSE%", "false"], + ["%TRUE%", "true"] + ] + } + ], + "tooltip": "%T%", + "code": "$[SPI_INSTANCE].transfer($[DATA], $[CS], $[RX_LENGTH], $[QIO])", + "output": "ArrayBuffer" + } + ] +} diff --git a/@types/jaculus/blocks/stream.jacly.json b/@types/jaculus/blocks/stream.jacly.json new file mode 100644 index 0000000..605cad0 --- /dev/null +++ b/@types/jaculus/blocks/stream.jacly.json @@ -0,0 +1,117 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "@types/jaculus", + "category": "stream", + "parentCategory": "communication", + "name": "%T%", + "description": "%T%", + "colour": "#14B8A6", + "icon": "TextCursorInput", + "priority": 108, + "import": [ + "import { readline } from \"@types/jaculus\";", + "import { stdin, stdout, stderr } from \"stdio\";" + ], + "contents": [ + { + "kind": "block", + "type": "stream_stdout_write", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "TEXT", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "" + } + } + } + ], + "tooltip": "%T%", + "code": "stdout.write($[TEXT]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "stream_stderr_write", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "TEXT", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "" + } + } + } + ], + "tooltip": "%T%", + "code": "stderr.write($[TEXT]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "stream_onChar", + "message0": "%T%", + "args0": [ + { + "type": "input_statement", + "name": "CODE" + } + ], + "callbackVars": [ + { + "identifier": "charText", + "message": "%CHAR_TEXT%", + "type": "String", + "codeName": "char" + }, + { + "identifier": "charLength", + "message": "%CHAR_LENGTH%", + "type": "Number", + "codeName": "char.length" + } + ], + "isProgramStart": true, + "tooltip": "%T%", + "code": "{\n\tasync function streamOnCharLoop() {\n\t\ttry {\n\t\t\tconst char = await stdin.get();\n$[CODE]\n\t\t\tstreamOnCharLoop();\n\t\t} catch (_reason) {\n\t\t\tconsole.warn(\"stdin character read failed:\", _reason);\n\t\t\tstreamOnCharLoop();\n\t\t}\n\t}\n\tstreamOnCharLoop();\n}" + }, + { + "kind": "block", + "type": "stream_onChunk", + "message0": "%T%", + "args0": [ + { + "type": "input_statement", + "name": "CODE" + } + ], + "callbackVars": [ + { + "identifier": "chunkText", + "message": "%CHUNK_TEXT%", + "type": "String", + "codeName": "chunk" + }, + { + "identifier": "chunkLength", + "message": "%CHUNK_LENGTH%", + "type": "Number", + "codeName": "chunk.length" + } + ], + "isProgramStart": true, + "tooltip": "%T%", + "code": "{\n\tasync function streamOnChunkLoop() {\n\t\ttry {\n\t\t\tconst chunk = await stdin.read();\n$[CODE]\n\t\t\tstreamOnChunkLoop();\n\t\t} catch (_reason) {\n\t\t\tconsole.warn(\"stdin chunk read failed:\", _reason);\n\t\t\tstreamOnChunkLoop();\n\t\t}\n\t}\n\tstreamOnChunkLoop();\n}" + } + ] +} diff --git a/@types/jaculus/blocks/translations/cs.lang.json b/@types/jaculus/blocks/translations/cs.lang.json new file mode 100644 index 0000000..d065e91 --- /dev/null +++ b/@types/jaculus/blocks/translations/cs.lang.json @@ -0,0 +1,322 @@ +{ + "ADC_NAME": "ADC", + "ADC_DESCRIPTION": "Bloky pro analogově-digitální převodník (ADC).", + "ADC_LABEL_SETUP": "Nastavení (je potřeba pro použití bloku pro čtení ADC)", + "ADC_LABEL_READ": "Čtení ADC", + + "ADC_CONFIGURE_MESSAGE0": "konfigurovat ADC na pinu $[PIN] s útlumem $[ATTEN]", + "ADC_CONFIGURE_TOOLTIP": "Nakonfiguruje ADC na zadaném pinu s daným útlumem", + "ADC_READ_MESSAGE0": "přečíst hodnotu ADC z pinu $[PIN]", + "ADC_READ_TOOLTIP": "Přečte hodnotu ADC ze zadaného pinu", + + "CONTROL_NAME": "Řízení programu", + "CONTROL_DESCRIPTION": "Řídící bloky", + "CONTROL_LABEL_EXIT": "Ukončit program s konkrétním kódem", + "CONTROL_LABEL_DATE": "Získat aktuální čas v milisekundách od spuštění programu", + + "CONTROL_EXIT_MESSAGE0": "ukončit program s kódem $[EXIT_CODE]", + "CONTROL_EXIT_TOOLTIP": "Ukončí program se zadaným návratovým kódem", + "CONTROL_DATENOW_MESSAGE0": "získat aktuální čas v milisekundách", + "CONTROL_DATENOW_TOOLTIP": "Získá aktuální čas v milisekundách od spuštění programu", + + "GPIO_NAME": "GPIO", + "GPIO_DESCRIPTION": "Bloky pro obecné vstupně-výstupní piny (GPIO).", + + "GPIO_LABEL_SETUP": "Nastavení (je potřeba pro použití ostatních GPIO bloků)", + "GPIO_LABEL_READ_WRITE": "Čtení/Zápis", + "GPIO_LABEL_EVENTS": "Události", + + "GPIO_PINMODE_MESSAGE0": "nastavit režim pinu $[PIN] na $[MODE]", + "GPIO_PINMODE_TOOLTIP": "Nastaví režim zadaného pinu", + "GPIO_PINMODE_ARGS0_FIELD_DROPDOWN_OUTPUT": "VÝSTUP", + "GPIO_PINMODE_ARGS0_FIELD_DROPDOWN_INPUT": "VSTUP", + "GPIO_PINMODE_ARGS0_FIELD_DROPDOWN_INPUT_PULLUP": "VSTUP S PULLUP", + "GPIO_PINMODE_ARGS0_FIELD_DROPDOWN_INPUT_PULLDOWN": "VSTUP S PULLDOWN", + "GPIO_PINMODE_ARGS0_FIELD_DROPDOWN_DISABLE": "VYPNUTO", + + "GPIO_WRITE_MESSAGE0": "zapsat na pin $[PIN] digitální hodnotu $[VALUE]", + "GPIO_WRITE_TOOLTIP": "Zapíše digitální hodnotu na zadaný pin", + "GPIO_READ_MESSAGE0": "přečíst digitální hodnotu z pinu $[PIN]", + "GPIO_READ_TOOLTIP": "Přečte digitální hodnotu ze zadaného pinu", + "GPIO_ONEVENT_MESSAGE0": "při události $[EVENT] na pinu $[PIN] proveď\n", + "GPIO_ONEVENT_TOOLTIP": "Nastaví obsluhu události pro zadaný pin a typ události", + "GPIO_ONEVENT_ARGS0_FIELD_DROPDOWN_FALLING": "sestupná hrana", + "GPIO_ONEVENT_ARGS0_FIELD_DROPDOWN_RISING": "vzestupná hrana", + "GPIO_ONEVENT_ARGS0_FIELD_DROPDOWN_CHANGE": "změna", + "GPIO_ONEVENT_CALLBACK_MILLIS": "časová známka v milisekundách", + "GPIO_ONEVENT_CALLBACK_MICROS": "časová známka v mikrosekundách % 1000", + + "GPIO_OFFEVENT_MESSAGE0": "odstranit obsluhu události $[EVENT] z pinu $[PIN]", + "GPIO_OFFEVENT_TOOLTIP": "Odstraní obsluhu události pro zadaný pin a typ události", + "GPIO_OFFEVENT_ARGS0_FIELD_DROPDOWN_FALLING": "sestupná hrana", + "GPIO_OFFEVENT_ARGS0_FIELD_DROPDOWN_RISING": "vzestupná hrana", + "GPIO_OFFEVENT_ARGS0_FIELD_DROPDOWN_CHANGE": "změna", + + "I2C_NAME": "I2C", + "I2C_DESCRIPTION": "Bloky pro I2C komunikaci.", + "I2C_LABEL_SETUP": "Nastavení (je potřeba pro použití ostatních I2C bloků)", + "I2C_CONSTRUCTOR_MESSAGE0": "vytvořit $[CONSTRUCTED_VAR_NAME] na $[I2C] s\n pin SDA $[SDA] pin SCL $[SCL]\n přenosovou rychlostí $[BITRATE]", + "I2C_CONSTRUCTOR_TOOLTIP": "Nastaví I2C sběrnici se zadanými SDA a SCL piny a přenosovou rychlostí", + "I2C_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_I2C1": "I2C1", + "I2C_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_I2C2_MAYBE": "I2C2 (nemusí existovat)", + "I2C_LABEL_IN_FUTURE_RELEASES": "I2C čtení/zápis bude přidáno v budoucích verzích.", + + "KEYVALUE_NAME": "Úložiště klíč-hodnota", + "KEYVALUE_DESCRIPTION": "Bloky pro ukládání párů klíč-hodnota do trvalého úložiště.", + "KEYVALUE_LABEL_OPEN": "Otvření jmenného prostoru", + "KEYVALUE_LABEL_SET": "Nastavení hodnoty", + "KEYVALUE_LABEL_GET": "Získání hodnoty", + "KEYVALUE_LABEL_ERASE": "Mazání klíče", + "KEYVALUE_LABEL_EXISTS": "Kontrola existence klíče", + "KEYVALUE_LABEL_COMMIT": "Uložení změn do úložiště (bez tohoto se žádné změny neuloží)", + + "KV_OPEN_MESSAGE0": "otevřít $[CONSTRUCTED_VAR_NAME] jmenný prostor $[NAMESPACE]", + "KV_OPEN_TOOLTIP": "Inicializuje jmenný prostor úložiště klíč-hodnota", + "KV_SET_STRING_MESSAGE0": "nastavit KV $[KV_INSTANCE] klíč $[KEY] na textovou hodnotu $[VALUE]", + "KV_SET_STRING_TOOLTIP": "Nastaví textovou hodnotu v NVS pro zadaný klíč", + "KV_SET_NUMBER_MESSAGE0": "nastavit KV $[KV_INSTANCE] klíč $[KEY] na číselnou hodnotu $[VALUE]", + "KV_SET_NUMBER_TOOLTIP": "Nastaví číselnou hodnotu v NVS pro zadaný klíč", + "KV_GET_STRING_MESSAGE0": "$[KV_INSTANCE] získat textovou hodnotu pro klíč $[KEY]", + "KV_GET_STRING_TOOLTIP": "Získá textovou hodnotu z NVS pro zadaný klíč", + "KV_GET_NUMBER_MESSAGE0": "$[KV_INSTANCE] získat číselnou hodnotu z klíče $[KEY]", + "KV_GET_NUMBER_TOOLTIP": "Získá číselnou hodnotu z NVS pro zadaný klíč", + "KV_ERASE_MESSAGE0": "$[KV_INSTANCE] smazat klíč $[KEY]", + "KV_ERASE_TOOLTIP": "Smaže zadaný klíč z NVS", + "KV_EXISTS_MESSAGE0": "$[KV_INSTANCE] má klíč $[KEY]", + "KV_EXISTS_TOOLTIP": "Zkontroluje, zda zadaný klíč existuje v NVS", + "KV_COMMIT_MESSAGE0": "$[KV_INSTANCE] uložit změny", + "KV_COMMIT_TOOLTIP": "Uloží všechny čekající změny do NVS", + + "LEDC_NAME": "LED kontrolér", + "LEDC_DESCRIPTION": "Bloky pro LED kontrolér (LEDC).", + "LEDC_LABEL_SETUP": "Nastavení (je potřeba pro použití ostatních LEDC bloků)", + "LEDC_LABEL_SET_FREQUENCY": "Nastavení frekvence mění frekvenci PWM signálu.\nTo ovlivní všechny kanály používající stejný časovač.", + "LEDC_LABEL_SET_DUTY": "Nastavení střídy mění pracovní cyklus PWM signálu pro konkrétní kanál.\nTo ovlivní pouze zadaný kanál.", + "LEDC_LABEL_STOP": "Bloky Stop zastaví časovač nebo kanál, čímž zastaví PWM signál.", + "LEDC_LABEL_STOP2": "Pozor: časovač lze zastavit pouze tehdy, pokud ho nepoužívá žádný kanál.", + "LEDC_CONFIGURETIMER_MESSAGE0": "konfigurovat časovač $[TIMER] s frekvencí $[FREQUENCY] Hz a rozlišením $[RESOLUTION] bitů (výchozí 10 bitů)", + "LEDC_CONFIGURETIMER_TOOLTIP": "Nakonfiguruje daný časovač se zadanou frekvencí a rozlišením", + "LEDC_CONFIGURECHANNEL_MESSAGE0": "konfigurovat kanál $[CHANNEL] na pinu $[PIN] s časovačem $[TIMER] a střídou $[DUTY] (0-1023)", + "LEDC_CONFIGURECHANNEL_TOOLTIP": "Nakonfiguruje daný kanál na zadaném pinu s časovačem a střídou", + "LEDC_SETFREQUENCY_MESSAGE0": "nastavit frekvenci časovače $[TIMER] na $[FREQUENCY] Hz", + "LEDC_SETFREQUENCY_TOOLTIP": "Nastaví frekvenci daného časovače", + "LEDC_SETDUTY_MESSAGE0": "nastavit střídu kanálu $[CHANNEL] na $[DUTY] (0-1023)", + "LEDC_SETDUTY_TOOLTIP": "Nastaví střídu daného kanálu", + "LEDC_STOPTIMER_MESSAGE0": "zastavit časovač $[TIMER]", + "LEDC_STOPTIMER_TOOLTIP": "Zastaví daný časovač. Vyhodí chybu, pokud ho stále používá nějaký kanál.", + "LEDC_STOPCHANNEL_MESSAGE0": "zastavit kanál $[CHANNEL]", + "LEDC_STOPCHANNEL_TOOLTIP": "Zastaví daný kanál", + + "MOTOR_NAME": "Motor", + "MOTOR_DESCRIPTION": "Knihovna pro řízení motorů", + "MOTOR_LABEL_SETUP": "Vytvoření instance motoru (ukázková konfigurace z Robutek V2)", + "MOTOR_LABEL_OPERATIONS": "Operace", + "MOTOR_LABEL_REQUIRED_CONFIG": "Rychlost a konfigurace rampy jsou ve výchozím nastavení nastaveny na 0. Nakonfigurujte je...", + + "MOTOR_CONSTRUCTOR_PINS_MESSAGE0": "piny motoru motA $[MOT_A] motB $[MOT_B] encA $[ENC_A] encB $[ENC_B]", + "MOTOR_CONSTRUCTOR_PINS_TOOLTIP": "Definuje piny motoru", + "MOTOR_CONSTRUCTOR_LEDC_MESSAGE0": "LEDC řízení časovače $[TIMER] kanálA $[CHANNEL_A] kanálB $[CHANNEL_B]", + "MOTOR_CONSTRUCTOR_LEDC_TOOLTIP": "Definuje konfiguraci LEDC", + "MOTOR_CONSTRUCTOR_REGPARAMS_MESSAGE0": "parametry regulátoru kp $[KP] ki $[KI] kd $[KD]\n kv $[KV] ka $[KA] kc $[KC]\n maxIOut $[MAX_IOUT]\n unwindFactor $[UNWIND_FACTOR]", + "MOTOR_CONSTRUCTOR_REGPARAMS_TOOLTIP": "Definuje parametry regulátoru motoru", + "MOTOR_CONSTRUCTOR_MESSAGE0": "vytvořit $[CONSTRUCTED_VAR_NAME] motor s \npiny $[PINS]\nLEDC $[LEDC]\nparametry regulátoru $[REG_PARAMS]\n tiků enkodéru $[ENC_TICKS] obvod $[CIRCUMFERENCE] (mm)", + "MOTOR_CONSTRUCTOR_TOOLTIP": "Vytvoří novou instanci motoru", + "MOTOR_SETSPEED_MESSAGE0": "nastavit rychlost $[MOTOR_INSTANCE] na $[SPEED] (jednotek/s)", + "MOTOR_SETSPEED_TOOLTIP": "Nastaví rychlost motoru", + "MOTOR_SETRAMP_MESSAGE0": "nastavit rampu rychlosti $[MOTOR_INSTANCE] na $[RAMP] (jednotek/s²)", + "MOTOR_SETRAMP_TOOLTIP": "Nastaví rampu rychlosti motoru", + + "MOTOR_MOVE_DISTANCE_MESSAGE0": "pohnout $[MOTOR_INSTANCE] o vzdálenost $[DISTANCE] mm", + "MOTOR_MOVE_DISTANCE_TOOLTIP": "Pohne motorem o zadanou vzdálenost", + + "MOTOR_MOVE_TIME_MESSAGE0": "pohnout $[MOTOR_INSTANCE] po dobu $[DURATION] ms", + "MOTOR_MOVE_TIME_TOOLTIP": "Pohne motorem po zadanou dobu", + + "MOTOR_MOVE_INFINITE_MESSAGE0": "pohnout $[MOTOR_INSTANCE] dokud jiný příkaz nezastaví motor", + "MOTOR_MOVE_INFINITE_TOOLTIP": "Pohne motorem dokud jiný příkaz nezastaví motor", + + "MOTOR_SETRAW_MESSAGE0": "nastavit surový výkon $[MOTOR_INSTANCE] na $[POWER] (-1023 až 1023)", + "MOTOR_SETRAW_TOOLTIP": "Nastaví surový výkon motoru", + "MOTOR_STOP_MESSAGE0": "zastavit $[MOTOR_INSTANCE] s brzdou $[BRAKE]", + "MOTOR_STOP_TOOLTIP": "Zastaví motor", + "MOTOR_STOP_ARGS0_FIELD_DROPDOWN_TRUE": "ano", + "MOTOR_STOP_ARGS0_FIELD_DROPDOWN_FALSE": "ne", + "MOTOR_GETPOSITION_MESSAGE0": "získat pozici $[MOTOR_INSTANCE]", + "MOTOR_GETPOSITION_TOOLTIP": "Získá pozici motoru", + "MOTOR_CLOSE_MESSAGE0": "zavřít $[MOTOR_INSTANCE]", + "MOTOR_CLOSE_TOOLTIP": "Zavře motor", + + "PULSECOUNTER_NAME": "Čítač pulzů", + "PULSECOUNTER_DESCRIPTION": "Bloky pro řízení čítače pulzů.", + "PULSECOUNTER_LABEL_SETUP": "Nastavení (je potřeba pro použití ostatních bloků čítače pulzů)", + "PULSECOUNTER_LABEL_SETUP_DETAILS": "Pin hrany detekuje pulzy signálu (vzestupná/sestupná hrana). Pin úrovně upravuje chování čítání podle logické úrovně signálu.\nAkce hrany (Držet/Zvýšit/Snížit) určují, co se stane při každé hraně.\nAkce úrovně (Zachovat/Invertovat/Držet) přepisují akce hrany podle stavu pinu úrovně.\nČítač se po vytvoření automaticky spustí. Pozor: Není k dispozici na ESP32-C3.", + "PULSECOUNTER_LABEL_READ": "Čtení", + "PULSECOUNTER_LABEL_OTHER": "Ostatní operace s čítačem pulzů", + + "PC_CONSTRUCTOR_MESSAGE0": "vytvořit $[CONSTRUCTED_VAR_NAME] čítač pulzů\n na pinu úrovně $[PIN_LEVEL] pinu hrany $[PIN_EDGE]\n s akcí při nízké úrovni $[LEVEL_LOW_ACTION]\n akcí při vysoké úrovni $[LEVEL_HIGH_ACTION]\n akcí při náběžné hraně $[EDGE_POS_ACTION]\n akcí při sestupné hraně $[EDGE_NEG_ACTION]", + "PC_CONSTRUCTOR_TOOLTIP": "Vytvoří novou instanci čítače pulzů", + "PC_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_KEEP": "Zachovat", + "PC_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_INVERSE": "Invertovat", + "PC_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_HOLD": "Držet", + "PC_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_INCREASE": "Zvýšit", + "PC_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_DECREASE": "Snížit", + "PC_READ_MESSAGE0": "přečíst hodnotu z čítače pulzů $[PC_INSTANCE]", + "PC_READ_TOOLTIP": "Přečte aktuální hodnotu čítače pulzů", + "PC_CLEAR_MESSAGE0": "vynulovat čítač pulzů $[PC_INSTANCE]", + "PC_CLEAR_TOOLTIP": "Vynuluje čítač pulzů na 0", + "PC_START_MESSAGE0": "spustit čítač pulzů $[PC_INSTANCE]", + "PC_START_TOOLTIP": "Spustí čítač pulzů", + "PC_STOP_MESSAGE0": "zastavit čítač pulzů $[PC_INSTANCE]", + "PC_STOP_TOOLTIP": "Zastaví čítač pulzů", + "PC_CLOSE_MESSAGE0": "zavřít čítač pulzů $[PC_INSTANCE]", + "PC_CLOSE_TOOLTIP": "Zavře čítač pulzů", + + "SIMPLERADIO_NAME": "SimpleRadio", + "SIMPLERADIO_DESCRIPTION": "Bloky pro SimpleRadio komunikaci.", + "SIMPLERADIO_LABEL_BEGIN": "Nastavení SimpleRadia", + "SIMPLERADIO_LABEL_SEND": "Odesílání", + "SIMPLERADIO_LABEL_RECEIVE": "Příjem", + "SIMPLERADIO_LABEL_END_RECEIVE": "Ukončení příjmu", + "SIMPLERADIO_LABEL_OTHER": "Ostatní", + + "SR_BEGIN_MESSAGE0": "inicializovat SimpleRadio na skupině $[SR_GROUP]", + "SR_BEGIN_TOOLTIP": "Inicializuje SimpleRadio na zadané skupině", + "SR_SETGROUP_MESSAGE0": "nastavit skupinu SimpleRadio na $[SR_GROUP]", + "SR_SETGROUP_TOOLTIP": "Nastaví skupinu SimpleRadio", + "SR_GETGROUP_MESSAGE0": "získat skupinu SimpleRadio", + "SR_GETGROUP_TOOLTIP": "Získá aktuální skupinu SimpleRadio", + "SR_GETADDRESS_MESSAGE0": "získat adresu SimpleRadio", + "SR_GETADDRESS_TOOLTIP": "Získá lokální adresu SimpleRadio", + "SR_SENDSTRING_MESSAGE0": "SimpleRadio poslat text $[SR_STRING]", + "SR_SENDSTRING_TOOLTIP": "Odešle text přes SimpleRadio", + "SR_SENDNUMBER_MESSAGE0": "SimpleRadio poslat číslo $[SR_NUMBER]", + "SR_SENDNUMBER_TOOLTIP": "Odešle číslo přes SimpleRadio", + "SR_SENDKEYVALUE_MESSAGE0": "SimpleRadio poslat klíč $[SR_KV_KEY] s hodnotou $[SR_KV_VALUE]", + "SR_SENDKEYVALUE_TOOLTIP": "Odešle pár klíč-hodnota přes SimpleRadio", + "SR_END_MESSAGE0": "zastavit SimpleRadio", + "SR_END_TOOLTIP": "Zastaví SimpleRadio", + "SR_ONRECEIVENUMBER_MESSAGE0": "při přijetí čísla přes SimpleRadio proveď\n$[SR_CODE]", + "SR_ONRECEIVENUMBER_TOOLTIP": "Zaregistruje callback pro příjem čísel přes SimpleRadio. Použij blok receivedNumber pro přístup k hodnotě.", + "SR_ONRECEIVENUMBER_CALLBACK_RECEIVEDNUMBER": "přijaté číslo", + "SR_ONRECEIVENUMBER_CALLBACK_SENDERADDRESS": "adresa odesílatele", + + "SR_ONRECEIVESTRING_MESSAGE0": "při přijetí textu přes SimpleRadio proveď\n$[SR_CODE]", + "SR_ONRECEIVESTRING_TOOLTIP": "Zaregistruje callback pro příjem textů přes SimpleRadio. Použij blok receivedString pro přístup k hodnotě.", + "SR_ONRECEIVESTRING_CALLBACK_RECEIVEDSTRING": "přijatý text", + "SR_ONRECEIVESTRING_CALLBACK_SENDERADDRESS": "adresa odesílatele", + + "SR_ONRECEIVEKEYVALUE_MESSAGE0": "při přijetí klíč-hodnota přes SimpleRadio proveď\n$[SR_CODE]", + "SR_ONRECEIVEKEYVALUE_TOOLTIP": "Zaregistruje callback pro příjem párů klíč-hodnota přes SimpleRadio. Použij bloky receivedKey, receivedValue pro přístup k hodnotám.", + "SR_ONRECEIVEKEYVALUE_CALLBACK_RECEIVEDKEY": "přijatý klíč", + "SR_ONRECEIVEKEYVALUE_CALLBACK_RECEIVEDVALUE": "přijatá hodnota", + "SR_ONRECEIVEKEYVALUE_CALLBACK_SENDERADDRESS": "adresa odesílatele", + + "SR_OFFRECEIVE_MESSAGE0": "odstranit obsluhu příjmu SimpleRadio pro $[SR_TYPE]", + "SR_OFFRECEIVE_TOOLTIP": "Odregistruje callback pro zadaný typ paketu SimpleRadio", + "SR_OFFRECEIVE_ARGS0_FIELD_DROPDOWN_NUMBER": "číslo", + "SR_OFFRECEIVE_ARGS0_FIELD_DROPDOWN_STRING": "text", + "SR_OFFRECEIVE_ARGS0_FIELD_DROPDOWN_KEY_VALUE": "klíč-hodnota", + + "SR_SENDJSON_MESSAGE0": "odeslat JSON skrze SimpleRadio $[SR_JSON]", + "SR_SENDJSON_TOOLTIP": "Převede objekt na JSON text a odešle ho přes SimpleRadio.", + "SR_ONRECEIVEJSON_MESSAGE0": "když SimpleRadio přijme JSON proveď\n$[SR_CODE]", + "SR_ONRECEIVEJSON_TOOLTIP": "Poslouchá JSON textové pakety, zpracuje je a spustí připojený kód.", + "SR_ONRECEIVEJSON_CALLBACK_RECEIVEDJSON": "SimpleRadio přijal JSON", + "SR_ONRECEIVEJSON_CALLBACK_SENDERADDRESS": "SimpleRadio přijal adresu odesílatele", + + "STREAM_NAME": "Streamy", + "STREAM_DESCRIPTION": "Vysokoúrovňové bloky pro standardní vstup.", + "STREAM_STDOUT_WRITE_MESSAGE0": "zapsat $[TEXT] na standardní výstup", + "STREAM_STDOUT_WRITE_TOOLTIP": "Zapíše text přímo na stdout bez přidání nového řádku.", + "STREAM_STDERR_WRITE_MESSAGE0": "zapsat $[TEXT] na standardní chybový výstup", + "STREAM_STDERR_WRITE_TOOLTIP": "Zapíše text přímo na stderr bez přidání nového řádku.", + "STREAM_ONCHAR_MESSAGE0": "když se přečte znak ze standardního vstupu, proveď\n$[CODE]", + "STREAM_ONCHAR_TOOLTIP": "Průběžně čte jeden znak po druhém ze standardního vstupu a spouští připojený kód.", + "STREAM_ONCHAR_CALLBACK_CHAR_TEXT": "text znaku", + "STREAM_ONCHAR_CALLBACK_CHAR_LENGTH": "délka znaku", + "STREAM_ONCHUNK_MESSAGE0": "když se přečte blok dat ze standardního vstupu, proveď\n$[CODE]", + "STREAM_ONCHUNK_TOOLTIP": "Průběžně čte bloky dat ze standardního vstupu a spouští připojený kód.", + "STREAM_ONCHUNK_CALLBACK_CHUNK_TEXT": "text bloku", + "STREAM_ONCHUNK_CALLBACK_CHUNK_LENGTH": "délka bloku", + + "SPI_NAME": "SPI", + "SPI_DESCRIPTION": "Bloky pro SPI komunikaci.", + "SPI_LABEL_SETUP": "Nastavení (je potřeba pro použití ostatních SPI bloků)", + "SPI_LABEL_TRANSACTIONS": "Transakce", + "SPI_SETUP_MESSAGE0": "vytvořit $[CONSTRUCTED_VAR_NAME] na $[SPI] s\n pinem SCK $[SCK] pinem MOSI $[MOSI] pinem MISO $[MISO]\n baudrate $[BAUD] režimem $[MODE] pořadím $[ORDER]", + "SPI_SETUP_TOOLTIP": "Nastaví SPI instanci s vybranou sběrnicí, piny a parametry", + "SPI_SETUP_ARGS0_FIELD_DROPDOWN_SPI1": "SPI1", + "SPI_SETUP_ARGS0_FIELD_DROPDOWN_SPI2": "SPI2", + "SPI_SETUP_ARGS0_FIELD_DROPDOWN_SPI3_MAYBE": "SPI3 (nemusí existovat)", + "SPI_SETUP_ARGS0_FIELD_DROPDOWN_MSB": "MSB", + "SPI_SETUP_ARGS0_FIELD_DROPDOWN_LSB": "LSB", + "SPI_SEND_MESSAGE0": "$[SPI_INSTANCE] poslat $[DATA] s CS pinem $[CS]", + "SPI_SEND_TOOLTIP": "Odešle data přes SPI a vrátí přijaté bajty", + "SPI_WRITE_MESSAGE0": "$[SPI_INSTANCE] zapsat $[DATA] s CS pinem $[CS]", + "SPI_WRITE_TOOLTIP": "Zapíše data přes SPI", + "SPI_TRANSFER_MESSAGE0": "$[SPI_INSTANCE] přenést $[DATA] s CS pinem $[CS]\n číst délku $[RX_LENGTH] s QIO $[QIO]", + "SPI_TRANSFER_TOOLTIP": "Provede SPI transakci zápis+čtení a vrátí přečtené bajty", + "SPI_TRANSFER_ARGS0_FIELD_DROPDOWN_FALSE": "ne", + "SPI_TRANSFER_ARGS0_FIELD_DROPDOWN_TRUE": "ano", + + "UDP_NAME": "UDP Socket", + "UDP_DESCRIPTION": "Bloky pro UDP socket komunikaci.", + "UDP_LABEL_OPEN": "Otevření socketu", + "UDP_LABEL_IO": "Čtení/Zápis", + "UDP_LABEL_OTHER": "Ostatní", + "UDP_CONSTRUCTOR_MESSAGE0": "vytvořit $[CONSTRUCTED_VAR_NAME] UDP socket\n na adrese $[ADDRESS] portu $[PORT]", + "UDP_CONSTRUCTOR_TOOLTIP": "Vytvoří a naváže UDP socket na zadanou adresu a port", + "UDP_READ_MESSAGE0": "přečíst datagram z $[SOCKET_INSTANCE]", + "UDP_READ_TOOLTIP": "Přečte jeden datagram z UDP socketu (vrací null, když není dostupný)", + "UDP_WRITE_MESSAGE0": "$[SOCKET_INSTANCE] poslat $[DATA] na $[ADDRESS]:$[PORT]", + "UDP_WRITE_TOOLTIP": "Odešle jeden UDP datagram na cílovou adresu a port", + "UDP_CLOSE_MESSAGE0": "zavřít $[SOCKET_INSTANCE]", + "UDP_CLOSE_TOOLTIP": "Zavře UDP socket", + + "SMARTLED_NAME": "SmartLED", + "SMARTLED_DESCRIPTION": "Bloky pro řízení WS2812 chytrých LED.", + "SMARTLED_LABEL_EXAMPLE": "Ukázka", + "SMARTLED_LABEL_SETUP": "Nastavení (je potřeba pro použití ostatních SmartLED bloků)", + "SMARTLED_LABEL_SET": "Nastavení barvy LED", + "SMARTLED_LABEL_SHOW": "Zobrazit LED (aktualizuje pásek pro zobrazení nastavených barev)", + "SMARTLED_LABEL_OTHERS": "Ostatní operace s SmartLED", + + "STRIP_CONSTRUCTOR_MESSAGE0": "vytvořit $[CONSTRUCTED_VAR_NAME] pásek na pinu $[LED_PIN] s $[LED_COUNT] LED typu $[LED_TYPE]", + "STRIP_CONSTRUCTOR_TOOLTIP": "Vytvoří novou instanci chytrého LED pásku", + "STRIP_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_LED_WS2812": "LED_WS2812", + "STRIP_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_LED_WS2812B": "LED_WS2812B", + "STRIP_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_LED_WS2812B_2020": "LED_WS2812B_2020", + "STRIP_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_LED_SK6812": "LED_SK6812", + "STRIP_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_LED_WS2813": "LED_WS2813", + "STRIP_SET_MESSAGE0": "nastavit $[STRIP_INSTANCE] na pozici $[INDEX] na barvu $[COLOR_RGB]", + "STRIP_SET_TOOLTIP": "Nastaví LED na zadaném indexu na danou barvu", + "STRIP_SHOW_MESSAGE0": "zobrazit $[STRIP_INSTANCE]", + "STRIP_SHOW_TOOLTIP": "Aktualizuje LED pásek pro zobrazení nastavených barev", + "STRIP_GET_MESSAGE0": "získat barvu $[STRIP_INSTANCE] na pozici $[INDEX]", + "STRIP_GET_TOOLTIP": "Získá barvu LED na zadaném indexu", + "STRIP_CLEAR_MESSAGE0": "vymazat $[STRIP_INSTANCE]", + "STRIP_CLEAR_TOOLTIP": "Vymaže LED pásek (nastaví všechny LED na zhasnuté)", + + "WIFI_NAME": "WiFi", + "WIFI_DESCRIPTION": "WiFi bloky.", + "WIFI_LABEL_CURRENT_IP": "Získat aktuální IP adresu", + "WIFI_LABEL_WAIT_FOR_CONNECTION": "Čekat na WiFi připojení", + "WIFI_CURRENTIP_MESSAGE0": "získat aktuální IP adresu", + "WIFI_CURRENTIP_TOOLTIP": "Získá aktuální IP adresu WiFi připojení", + "WIFI_WAITFORCONNECTION_MESSAGE0": "čekat na WiFi připojení", + "WIFI_WAITFORCONNECTION_TOOLTIP": "Čeká, dokud se zařízení nepřipojí k WiFi síti", + "WIFI_WAITFORCONNECTION_ARGS0_FIELD_DROPDOWN_PRINT_DOTS": "tisknout tečky", + "WIFI_WAITFORCONNECTION_ARGS0_FIELD_DROPDOWN_NO_PRINT_DOTS": "netisknout tečky", + + "COMMUNICATION_NAME": "Komunikace", + "COMMUNICATION_DESCRIPTION": "Bloky pro různé komunikační protokoly a rozhraní.", + + "PROTOCOLS_NAME": "Protokoly", + "PROTOCOLS_DESCRIPTION": "Bloky pro komunikační protokoly jako SPI, I2C, atd.", + + "PERIPHERALS_NAME": "Periferie", + "PERIPHERALS_DESCRIPTION": "Bloky pro různé periferní zařízení a moduly.", + + "MOVEMENT_NAME": "Pohyb", + "MOVEMENT_DESCRIPTION": "Bloky pro řízení pohybu, jako jsou motory a serva." +} diff --git a/@types/jaculus/blocks/translations/en.lang.json b/@types/jaculus/blocks/translations/en.lang.json new file mode 100644 index 0000000..9cade10 --- /dev/null +++ b/@types/jaculus/blocks/translations/en.lang.json @@ -0,0 +1,326 @@ +{ + "ADC_NAME": "ADC", + "ADC_DESCRIPTION": "Analog to Digital Converter (ADC) blocks.", + "ADC_LABEL_SETUP": "Setup (this is required to use the ADC read block)", + "ADC_LABEL_READ": "Reading ADC", + + "ADC_CONFIGURE_MESSAGE0": "configure ADC on pin $[PIN] with attenuation $[ATTEN]", + "ADC_CONFIGURE_TOOLTIP": "Configure the ADC on the specified pin with the given attenuation", + "ADC_READ_MESSAGE0": "read ADC value from pin $[PIN]", + "ADC_READ_TOOLTIP": "Read the ADC value from the specified pin", + + "CONTROL_NAME": "Control", + "CONTROL_DESCRIPTION": "Control blocks", + "CONTROL_LABEL_EXIT": "Exit the program with a specific exit code", + "CONTROL_LABEL_DATE": "Get the current time in milliseconds since the program started", + + "CONTROL_EXIT_MESSAGE0": "exit program with code $[EXIT_CODE]", + "CONTROL_EXIT_TOOLTIP": "Exit the program with the specified exit code", + "CONTROL_DATENOW_MESSAGE0": "get current time in milliseconds", + "CONTROL_DATENOW_TOOLTIP": "Get the current time in milliseconds since the program started", + + "GPIO_NAME": "GPIO", + "GPIO_DESCRIPTION": "General Purpose Input/Output (GPIO) blocks.", + "GPIO_LABEL_EXAMPLE_WRITE": "Example: Enable pin", + "GPIO_LABEL_EXAMPLE_READ": "Example: Reading a pin state", + "GPIO_LABEL_SETUP": "Setup (this is required to use other GPIO blocks)", + "GPIO_LABEL_READ_WRITE": "Read/Write", + "GPIO_LABEL_EVENTS": "Events", + + "GPIO_PINMODE_MESSAGE0": "set pin $[PIN] mode to $[MODE]", + "GPIO_PINMODE_TOOLTIP": "Set the mode of the specified pin", + "GPIO_PINMODE_ARGS0_FIELD_DROPDOWN_OUTPUT": "OUTPUT", + "GPIO_PINMODE_ARGS0_FIELD_DROPDOWN_INPUT": "INPUT", + "GPIO_PINMODE_ARGS0_FIELD_DROPDOWN_INPUT_PULLUP": "INPUT_PULLUP", + "GPIO_PINMODE_ARGS0_FIELD_DROPDOWN_INPUT_PULLDOWN": "INPUT_PULLDOWN", + "GPIO_PINMODE_ARGS0_FIELD_DROPDOWN_DISABLE": "DISABLE", + + "GPIO_WRITE_MESSAGE0": "write to pin $[PIN] digital value $[VALUE]", + "GPIO_WRITE_TOOLTIP": "Write a digital value to the specified pin", + "GPIO_READ_MESSAGE0": "read digital value from pin $[PIN]", + "GPIO_READ_TOOLTIP": "Read the digital value from the specified pin", + "GPIO_ONEVENT_MESSAGE0": "on $[EVENT] event on pin $[PIN] do\n", + "GPIO_ONEVENT_TOOLTIP": "Set an event handler for the specified pin and event type", + "GPIO_ONEVENT_ARGS0_FIELD_DROPDOWN_FALLING": "falling", + "GPIO_ONEVENT_ARGS0_FIELD_DROPDOWN_RISING": "rising", + "GPIO_ONEVENT_ARGS0_FIELD_DROPDOWN_CHANGE": "change", + "GPIO_ONEVENT_CALLBACK_MILLIS": "received Timestamp Millis", + "GPIO_ONEVENT_CALLBACK_MICROS": "received Timestamp Micros % 1000", + + "GPIO_OFFEVENT_MESSAGE0": "remove $[EVENT] event handler from pin $[PIN]", + "GPIO_OFFEVENT_TOOLTIP": "Remove the event handler for the specified pin and event type", + "GPIO_OFFEVENT_ARGS0_FIELD_DROPDOWN_FALLING": "falling", + "GPIO_OFFEVENT_ARGS0_FIELD_DROPDOWN_RISING": "rising", + "GPIO_OFFEVENT_ARGS0_FIELD_DROPDOWN_CHANGE": "change", + + "I2C_NAME": "I2C", + "I2C_DESCRIPTION": "I2C communication blocks.", + "I2C_LABEL_SETUP": "Setup (this is required to use other I2C blocks)", + + "I2C_CONSTRUCTOR_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] on $[I2C] with\n SDA pin $[SDA] SCL pin $[SCL]\n bitrate $[BITRATE]", + "I2C_CONSTRUCTOR_TOOLTIP": "Setup the I2C bus with specified SDA and SCL pins and bitrate", + "I2C_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_I2C1": "I2C1", + "I2C_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_I2C2_MAYBE": "I2C2 (might not exist)", + "I2C_LABEL_IN_FUTURE_RELEASES": "I2C Read/Write will be added in future releases.", + + "KEYVALUE_NAME": "KeyValueStorage", + "KEYVALUE_DESCRIPTION": "Key value storage blocks for storing key-value pairs in non-volatile storage.", + "KEYVALUE_LABEL_OPEN": "Opening a KeyValueStorage instance namespace", + "KEYVALUE_LABEL_SET": "Setting a value", + "KEYVALUE_LABEL_GET": "Getting a value", + "KEYVALUE_LABEL_ERASE": "Erasing a key", + "KEYVALUE_LABEL_EXISTS": "Checking if a key exists", + "KEYVALUE_LABEL_COMMIT": "Committing changes to storage (without this, any changes will not be saved)", + + "KV_OPEN_MESSAGE0": "open $[CONSTRUCTED_VAR_NAME] namespace $[NAMESPACE]", + "KV_OPEN_TOOLTIP": "Initialize an Key Value Storage namespace", + "KV_SET_STRING_MESSAGE0": "set KV $[KV_INSTANCE] key $[KEY] to string value $[VALUE]", + "KV_SET_STRING_TOOLTIP": "Set a string value in NVS for the specified key", + "KV_SET_NUMBER_MESSAGE0": "set KV $[KV_INSTANCE] key $[KEY] to number value $[VALUE]", + "KV_SET_NUMBER_TOOLTIP": "Set a number value in NVS for the specified key", + "KV_GET_STRING_MESSAGE0": "$[KV_INSTANCE] get string value for key $[KEY]", + "KV_GET_STRING_TOOLTIP": "Get a string value from NVS for the specified key", + "KV_GET_NUMBER_MESSAGE0": "$[KV_INSTANCE] get number value from key $[KEY]", + "KV_GET_NUMBER_TOOLTIP": "Get a number value from NVS for the specified key", + "KV_ERASE_MESSAGE0": "$[KV_INSTANCE] erase key $[KEY]", + "KV_ERASE_TOOLTIP": "Erase the specified key from NVS", + "KV_EXISTS_MESSAGE0": "$[KV_INSTANCE] has key $[KEY]", + "KV_EXISTS_TOOLTIP": "Check if the specified key exists in NVS", + "KV_COMMIT_MESSAGE0": "$[KV_INSTANCE] commit changes", + "KV_COMMIT_TOOLTIP": "Commit any pending changes to NVS", + + "LEDC_NAME": "LED Controller", + "LEDC_DESCRIPTION": "LED Controller (LEDC) blocks.", + "LEDC_LABEL_SETUP": "Setup (this is required to use other LEDC blocks)", + "LEDC_LABEL_SET_FREQUENCY": "Set Frequency means changing the frequency of the PWM signal.\nThis will affect all channels using the same timer.", + "LEDC_LABEL_SET_DUTY": "Set Duty means changing the duty cycle of the PWM signal for a specific channel.\nThis will affect only the specified channel.", + "LEDC_LABEL_STOP": "Stop blocks will stop the timer or channel, which will stop the PWM signal.", + "LEDC_LABEL_STOP2": "Note: a timer can only be stopped after all its channels have been stopped.", + + "LEDC_CONFIGURETIMER_MESSAGE0": "configure timer $[TIMER] with frequency $[FREQUENCY] Hz and resolution $[RESOLUTION] bits (default 10 bits)", + "LEDC_CONFIGURETIMER_TOOLTIP": "Configure the given timer with specified frequency and resolution", + "LEDC_CONFIGURECHANNEL_MESSAGE0": "configure channel $[CHANNEL] on pin $[PIN] with timer $[TIMER] and duty $[DUTY] (0-1023)", + "LEDC_CONFIGURECHANNEL_TOOLTIP": "Configure the given channel on specified pin with timer and duty cycle", + "LEDC_SETFREQUENCY_MESSAGE0": "set frequency of timer $[TIMER] to $[FREQUENCY] Hz", + "LEDC_SETFREQUENCY_TOOLTIP": "Set the frequency of the given timer", + "LEDC_SETDUTY_MESSAGE0": "set duty of channel $[CHANNEL] to $[DUTY] (0-1023)", + "LEDC_SETDUTY_TOOLTIP": "Set the duty of the given channel", + "LEDC_STOPTIMER_MESSAGE0": "stop timer $[TIMER]", + "LEDC_STOPTIMER_TOOLTIP": "Stop the given timer. Throws an error if any channel is still using the timer.", + "LEDC_STOPCHANNEL_MESSAGE0": "stop channel $[CHANNEL]", + "LEDC_STOPCHANNEL_TOOLTIP": "Stop the given channel", + + "MOTOR_NAME": "Motor", + "MOTOR_DESCRIPTION": "Motor control library", + "MOTOR_LABEL_SETUP": "Creating a Motor instance (example configuration from Robutek V2)", + "MOTOR_LABEL_OPERATIONS": "Operations", + "MOTOR_LABEL_REQUIRED_CONFIG": "Speed and ramp configuration is by default set to 0. Configure it...", + + "MOTOR_CONSTRUCTOR_PINS_MESSAGE0": "motor pins motA $[MOT_A] motB $[MOT_B] encA $[ENC_A] encB $[ENC_B]", + "MOTOR_CONSTRUCTOR_PINS_TOOLTIP": "Define motor pins", + "MOTOR_CONSTRUCTOR_LEDC_MESSAGE0": "LED control timer $[TIMER] channelA $[CHANNEL_A] channelB $[CHANNEL_B]", + "MOTOR_CONSTRUCTOR_LEDC_TOOLTIP": "Define LEDC configuration", + "MOTOR_CONSTRUCTOR_REGPARAMS_MESSAGE0": "reg params kp $[KP] ki $[KI] kd $[KD]\n kv $[KV] ka $[KA] kc $[KC]\n maxIOut $[MAX_IOUT]\n unwindFactor $[UNWIND_FACTOR]", + "MOTOR_CONSTRUCTOR_REGPARAMS_TOOLTIP": "Define motor regulator parameters", + "MOTOR_CONSTRUCTOR_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] motor with \npins $[PINS]\nLEDC $[LEDC]\nreg params $[REG_PARAMS]\n encoder ticks $[ENC_TICKS] circumference $[CIRCUMFERENCE] (mm)", + "MOTOR_CONSTRUCTOR_TOOLTIP": "Create a new Motor instance", + "MOTOR_SETSPEED_MESSAGE0": "set speed of $[MOTOR_INSTANCE] to $[SPEED] (mm/s)", + "MOTOR_SETSPEED_TOOLTIP": "Set the speed of the motor", + "MOTOR_SETRAMP_MESSAGE0": "set speed ramp of $[MOTOR_INSTANCE] to $[RAMP] (mm/s²)", + "MOTOR_SETRAMP_TOOLTIP": "Set the speed ramp of the motor", + + "MOTOR_MOVE_DISTANCE_MESSAGE0": "move $[MOTOR_INSTANCE] for distance $[DISTANCE] mm", + "MOTOR_MOVE_DISTANCE_TOOLTIP": "Move the motor for the specified distance", + + "MOTOR_MOVE_TIME_MESSAGE0": "move $[MOTOR_INSTANCE] for time $[DURATION] ms", + "MOTOR_MOVE_TIME_TOOLTIP": "Move the motor for the specified time", + + "MOTOR_MOVE_INFINITE_MESSAGE0": "move $[MOTOR_INSTANCE] indefinitely", + "MOTOR_MOVE_INFINITE_TOOLTIP": "Move the motor indefinitely until another command is given", + + "MOTOR_SETRAW_MESSAGE0": "set raw power of $[MOTOR_INSTANCE] to $[POWER] (-1023 to 1023)", + "MOTOR_SETRAW_TOOLTIP": "Set raw power to the motor", + "MOTOR_STOP_MESSAGE0": "stop $[MOTOR_INSTANCE] with brake $[BRAKE]", + "MOTOR_STOP_TOOLTIP": "Stop the motor", + "MOTOR_STOP_ARGS0_FIELD_DROPDOWN_TRUE": "true", + "MOTOR_STOP_ARGS0_FIELD_DROPDOWN_FALSE": "false", + "MOTOR_GETPOSITION_MESSAGE0": "get position of $[MOTOR_INSTANCE]", + "MOTOR_GETPOSITION_TOOLTIP": "Get the position of the motor", + "MOTOR_CLOSE_MESSAGE0": "close $[MOTOR_INSTANCE]", + "MOTOR_CLOSE_TOOLTIP": "Close the motor", + + "PULSECOUNTER_NAME": "Pulse Counter", + "PULSECOUNTER_DESCRIPTION": "Pulse counter control blocks.", + "PULSECOUNTER_LABEL_SETUP": "Setup (this is required to use other Pulse Counter blocks)", + "PULSECOUNTER_LABEL_SETUP_DETAILS": "The edge pin detects signal pulses (rising/falling edges). The level pin modifies counting behavior based on its logic level.\nEdge actions (Hold/Increase/Decrease) define what happens on each edge.\nLevel actions (Keep/Inverse/Hold) override edge actions depending on the level pin state.\nThe counter starts automatically after creation. Note: Not available on ESP32-C3.", + "PULSECOUNTER_LABEL_READ": "Reading", + "PULSECOUNTER_LABEL_OTHER": "Other Pulse Counter operations", + + "PC_CONSTRUCTOR_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] pulse counter\n on level pin $[PIN_LEVEL] edge pin $[PIN_EDGE]\n with level low action $[LEVEL_LOW_ACTION]\n level high action $[LEVEL_HIGH_ACTION]\n edge positive action $[EDGE_POS_ACTION]\n edge negative action $[EDGE_NEG_ACTION]", + "PC_CONSTRUCTOR_TOOLTIP": "Create a new Pulse Counter instance", + "PC_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_KEEP": "Keep", + "PC_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_INVERSE": "Inverse", + "PC_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_HOLD": "Hold", + "PC_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_INCREASE": "Increase", + "PC_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_DECREASE": "Decrease", + "PC_READ_MESSAGE0": "read value from pulse counter $[PC_INSTANCE]", + "PC_READ_TOOLTIP": "Read the current value of the pulse counter", + "PC_CLEAR_MESSAGE0": "clear pulse counter $[PC_INSTANCE]", + "PC_CLEAR_TOOLTIP": "Reset the pulse counter to 0", + "PC_START_MESSAGE0": "start pulse counter $[PC_INSTANCE]", + "PC_START_TOOLTIP": "Start the pulse counter", + "PC_STOP_MESSAGE0": "stop pulse counter $[PC_INSTANCE]", + "PC_STOP_TOOLTIP": "Stop the pulse counter", + "PC_CLOSE_MESSAGE0": "close pulse counter $[PC_INSTANCE]", + "PC_CLOSE_TOOLTIP": "Close the pulse counter", + + "SIMPLERADIO_NAME": "SimpleRadio", + "SIMPLERADIO_DESCRIPTION": "SimpleRadio communication blocks.", + "SIMPLERADIO_LABEL_BEGIN": "SimpleRadio Setup", + "SIMPLERADIO_LABEL_SEND": "Sending", + "SIMPLERADIO_LABEL_RECEIVE": "Receiving", + "SIMPLERADIO_LABEL_END_RECEIVE": "End Receiving", + "SIMPLERADIO_LABEL_OTHER": "Other", + + "SR_BEGIN_MESSAGE0": "initialize SimpleRadio on group $[SR_GROUP]", + "SR_BEGIN_TOOLTIP": "Initialize the SimpleRadio on the specified group", + "SR_SETGROUP_MESSAGE0": "set SimpleRadio group to $[SR_GROUP]", + "SR_SETGROUP_TOOLTIP": "Set the SimpleRadio group", + "SR_GETGROUP_MESSAGE0": "get SimpleRadio group", + "SR_GETGROUP_TOOLTIP": "Get the current SimpleRadio group", + "SR_GETADDRESS_MESSAGE0": "get SimpleRadio address", + "SR_GETADDRESS_TOOLTIP": "Get the local SimpleRadio address", + "SR_SENDSTRING_MESSAGE0": "SimpleRadio send string $[SR_STRING]", + "SR_SENDSTRING_TOOLTIP": "Send a string via SimpleRadio", + "SR_SENDNUMBER_MESSAGE0": "SimpleRadio send number $[SR_NUMBER]", + "SR_SENDNUMBER_TOOLTIP": "Send a number via SimpleRadio", + "SR_SENDKEYVALUE_MESSAGE0": "SimpleRadio send key $[SR_KV_KEY] with value $[SR_KV_VALUE]", + "SR_SENDKEYVALUE_TOOLTIP": "Send a key-value pair via SimpleRadio", + "SR_END_MESSAGE0": "stop SimpleRadio", + "SR_END_TOOLTIP": "Stop the SimpleRadio", + "SR_ONRECEIVENUMBER_MESSAGE0": "on SimpleRadio receive number do\n$[SR_CODE]", + "SR_ONRECEIVENUMBER_TOOLTIP": "Register a callback for receiving numbers via SimpleRadio. Use the receivedNumber block inside to access the value.", + "SR_ONRECEIVENUMBER_CALLBACK_RECEIVEDNUMBER": "received Number", + "SR_ONRECEIVENUMBER_CALLBACK_SENDERADDRESS": "received Sender Address", + + "SR_ONRECEIVESTRING_MESSAGE0": "on SimpleRadio receive string do\n$[SR_CODE]", + "SR_ONRECEIVESTRING_TOOLTIP": "Register a callback for receiving strings via SimpleRadio. Use the receivedString block inside to access the value.", + "SR_ONRECEIVESTRING_CALLBACK_RECEIVEDSTRING": "received String", + "SR_ONRECEIVESTRING_CALLBACK_SENDERADDRESS": "received Sender Address", + + "SR_ONRECEIVEKEYVALUE_MESSAGE0": "on SimpleRadio receive key-value do\n$[SR_CODE]", + "SR_ONRECEIVEKEYVALUE_TOOLTIP": "Register a callback for receiving key-value pairs via SimpleRadio. Use the receivedKey, receivedValue blocks inside to access the values.", + "SR_ONRECEIVEKEYVALUE_CALLBACK_RECEIVEDKEY": "received Key", + "SR_ONRECEIVEKEYVALUE_CALLBACK_RECEIVEDVALUE": "received Value", + "SR_ONRECEIVEKEYVALUE_CALLBACK_SENDERADDRESS": "received Sender Address", + + "SR_OFFRECEIVE_MESSAGE0": "remove SimpleRadio receive handler for $[SR_TYPE]", + "SR_OFFRECEIVE_TOOLTIP": "Unregister the callback for the specified SimpleRadio packet type", + "SR_OFFRECEIVE_ARGS0_FIELD_DROPDOWN_NUMBER": "number", + "SR_OFFRECEIVE_ARGS0_FIELD_DROPDOWN_STRING": "string", + "SR_OFFRECEIVE_ARGS0_FIELD_DROPDOWN_KEY_VALUE": "key-value", + + "SR_SENDJSON_MESSAGE0": "SimpleRadio send JSON $[SR_JSON]", + "SR_SENDJSON_TOOLTIP": "Convert an object to JSON text and send it over SimpleRadio.", + "SR_ONRECEIVEJSON_MESSAGE0": "when SimpleRadio receives JSON do\n$[SR_CODE]", + "SR_ONRECEIVEJSON_TOOLTIP": "Listen for JSON text packets, parse them, and run the attached code.", + "SR_ONRECEIVEJSON_CALLBACK_RECEIVEDJSON": "SimpleRadio received JSON", + "SR_ONRECEIVEJSON_CALLBACK_SENDERADDRESS": "SimpleRadio received Sender Address", + + "STREAM_NAME": "Streams", + "STREAM_DESCRIPTION": "High-level standard input stream blocks.", + "STREAM_STDOUT_WRITE_MESSAGE0": "write $[TEXT] to standard output", + "STREAM_STDOUT_WRITE_TOOLTIP": "Write text directly to stdout without adding a new line.", + "STREAM_STDERR_WRITE_MESSAGE0": "write $[TEXT] to standard error", + "STREAM_STDERR_WRITE_TOOLTIP": "Write text directly to stderr without adding a new line.", + "STREAM_ONCHAR_MESSAGE0": "when a character is read from standard input do\n$[CODE]", + "STREAM_ONCHAR_TOOLTIP": "Continuously read one character at a time from standard input and run the attached code.", + "STREAM_ONCHAR_CALLBACK_CHAR_TEXT": "character text", + "STREAM_ONCHAR_CALLBACK_CHAR_LENGTH": "character length", + "STREAM_ONCHUNK_MESSAGE0": "when a chunk is read from standard input do\n$[CODE]", + "STREAM_ONCHUNK_TOOLTIP": "Continuously read chunks from standard input and run the attached code.", + "STREAM_ONCHUNK_CALLBACK_CHUNK_TEXT": "chunk text", + "STREAM_ONCHUNK_CALLBACK_CHUNK_LENGTH": "chunk length", + + "SPI_NAME": "SPI", + "SPI_DESCRIPTION": "SPI communication blocks.", + "SPI_LABEL_SETUP": "Setup (this is required to use other SPI blocks)", + "SPI_LABEL_TRANSACTIONS": "Transactions", + "SPI_SETUP_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] on $[SPI] with\n SCK pin $[SCK] MOSI pin $[MOSI] MISO pin $[MISO]\n baud $[BAUD] mode $[MODE] order $[ORDER]", + "SPI_SETUP_TOOLTIP": "Setup an SPI instance with selected bus and pin/options", + "SPI_SETUP_ARGS0_FIELD_DROPDOWN_SPI1": "SPI1", + "SPI_SETUP_ARGS0_FIELD_DROPDOWN_SPI2": "SPI2", + "SPI_SETUP_ARGS0_FIELD_DROPDOWN_SPI3_MAYBE": "SPI3 (might not exist)", + "SPI_SETUP_ARGS0_FIELD_DROPDOWN_MSB": "MSB", + "SPI_SETUP_ARGS0_FIELD_DROPDOWN_LSB": "LSB", + "SPI_SEND_MESSAGE0": "$[SPI_INSTANCE] send $[DATA] with CS pin $[CS]", + "SPI_SEND_TOOLTIP": "Send data over SPI and return received bytes", + "SPI_WRITE_MESSAGE0": "$[SPI_INSTANCE] write $[DATA] with CS pin $[CS]", + "SPI_WRITE_TOOLTIP": "Write data over SPI", + "SPI_TRANSFER_MESSAGE0": "$[SPI_INSTANCE] transfer $[DATA] with CS pin $[CS]\n read length $[RX_LENGTH] with QIO $[QIO]", + "SPI_TRANSFER_TOOLTIP": "Perform SPI write+read transfer and return read bytes", + "SPI_TRANSFER_ARGS0_FIELD_DROPDOWN_FALSE": "false", + "SPI_TRANSFER_ARGS0_FIELD_DROPDOWN_TRUE": "true", + + "UDP_NAME": "UDP Socket", + "UDP_DESCRIPTION": "UDP socket communication blocks.", + "UDP_LABEL_OPEN": "Open socket", + "UDP_LABEL_IO": "Read/Write", + "UDP_LABEL_OTHER": "Other", + "UDP_CONSTRUCTOR_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] UDP socket\n on address $[ADDRESS] port $[PORT]", + "UDP_CONSTRUCTOR_TOOLTIP": "Create and bind a UDP socket to the given address and port", + "UDP_READ_MESSAGE0": "read datagram from $[SOCKET_INSTANCE]", + "UDP_READ_TOOLTIP": "Read one datagram from UDP socket (returns null when none is available)", + "UDP_WRITE_MESSAGE0": "$[SOCKET_INSTANCE] send $[DATA] to $[ADDRESS]:$[PORT]", + "UDP_WRITE_TOOLTIP": "Send one UDP datagram to destination address and port", + "UDP_CLOSE_MESSAGE0": "close $[SOCKET_INSTANCE]", + "UDP_CLOSE_TOOLTIP": "Close the UDP socket", + + "SMARTLED_NAME": "SmartLED", + "SMARTLED_DESCRIPTION": "WS2812 smart LED control blocks.", + "SMARTLED_LABEL_EXAMPLE": "Example", + "SMARTLED_LABEL_SETUP": "Setup (this is required to use other SmartLED blocks)", + "SMARTLED_LABEL_SET": "Set LED Color", + "SMARTLED_LABEL_SHOW": "Show LEDs (update strip to show set colors)", + "SMARTLED_LABEL_OTHERS": "Other SmartLED operations", + + "STRIP_CONSTRUCTOR_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] strip on pin $[LED_PIN] with $[LED_COUNT] LEDs of type $[LED_TYPE]", + "STRIP_CONSTRUCTOR_TOOLTIP": "Create a new Smart LED strip instance", + "STRIP_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_LED_WS2812": "LED_WS2812", + "STRIP_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_LED_WS2812B": "LED_WS2812B", + "STRIP_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_LED_WS2812B_2020": "LED_WS2812B_2020", + "STRIP_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_LED_SK6812": "LED_SK6812", + "STRIP_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_LED_WS2813": "LED_WS2813", + "STRIP_SET_MESSAGE0": "set $[STRIP_INSTANCE] at index $[INDEX] to color $[COLOR_RGB]", + "STRIP_SET_TOOLTIP": "Set the LED at the specified index to the given color", + "STRIP_SHOW_MESSAGE0": "show $[STRIP_INSTANCE]", + "STRIP_SHOW_TOOLTIP": "Update the LED strip to show the set colors", + "STRIP_GET_MESSAGE0": "get color of $[STRIP_INSTANCE] at index $[INDEX]", + "STRIP_GET_TOOLTIP": "Get the color of the LED at the specified index", + "STRIP_CLEAR_MESSAGE0": "clear $[STRIP_INSTANCE]", + "STRIP_CLEAR_TOOLTIP": "Clear the LED strip (set all LEDs to off)", + + "WIFI_NAME": "WiFi", + "WIFI_DESCRIPTION": "WiFi blocks.", + "WIFI_LABEL_CURRENT_IP": "Get current IP address", + "WIFI_LABEL_WAIT_FOR_CONNECTION": "Wait for WiFi connection", + + "WIFI_CURRENTIP_MESSAGE0": "get current IP address", + "WIFI_CURRENTIP_TOOLTIP": "Get the current IP address of the WiFi connection", + "WIFI_WAITFORCONNECTION_MESSAGE0": "wait for WiFi connection", + "WIFI_WAITFORCONNECTION_TOOLTIP": "Wait until the device is connected to a WiFi network", + "WIFI_WAITFORCONNECTION_ARGS0_FIELD_DROPDOWN_PRINT_DOTS": "print dots", + "WIFI_WAITFORCONNECTION_ARGS0_FIELD_DROPDOWN_NO_PRINT_DOTS": "do not print dots", + + "COMMUNICATION_NAME": "Communication", + "COMMUNICATION_DESCRIPTION": "Blocks for various communication protocols and interfaces.", + + "PROTOCOLS_NAME": "Protocols", + "PROTOCOLS_DESCRIPTION": "Blocks for communication protocols like SPI, I2C, etc.", + + "PERIPHERALS_NAME": "Peripherals", + "PERIPHERALS_DESCRIPTION": "Blocks for various hardware peripherals like sensors, motors, etc.", + + "MOVEMENT_NAME": "Movement", + "MOVEMENT_DESCRIPTION": "Blocks for controlling movement, such as motors and servos." +} diff --git a/@types/jaculus/blocks/udp.jacly.json b/@types/jaculus/blocks/udp.jacly.json new file mode 100644 index 0000000..273bc1d --- /dev/null +++ b/@types/jaculus/blocks/udp.jacly.json @@ -0,0 +1,144 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "@types/jaculus", + "category": "udp", + "parentCategory": "communication", + "name": "%T%", + "description": "%T%", + "colour": "#16A085", + "icon": "RadioTower", + "priority": 112, + "import": ["import { UdpSocket } from \"udp\";"], + "contents": [ + { + "kind": "label", + "text": "%OPEN%" + }, + { + "kind": "block", + "type": "udp_constructor", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "udpSocket_?" + }, + { + "type": "input_value", + "name": "ADDRESS", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "0.0.0.0" + } + } + }, + { + "type": "input_value", + "name": "PORT", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 5000 + } + } + } + ], + "constructs": "udpSocket", + "tooltip": "%T%", + "code": "$[CONSTRUCTED_VAR_NAME] = new UdpSocket({ address: $[ADDRESS], port: $[PORT] });", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%IO%" + }, + { + "kind": "block", + "type": "udp_read", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SOCKET_INSTANCE", + "instanceof": "udpSocket" + } + ], + "tooltip": "%T%", + "code": "$[SOCKET_INSTANCE].read()", + "output": "ArrayBuffer" + }, + { + "kind": "block", + "type": "udp_write", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SOCKET_INSTANCE", + "instanceof": "udpSocket" + }, + { + "type": "input_value", + "name": "DATA", + "shadow": { + "type": "text", + "fields": { + "TEXT": "hello" + } + } + }, + { + "type": "input_value", + "name": "ADDRESS", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "192.168.1.100" + } + } + }, + { + "type": "input_value", + "name": "PORT", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 5000 + } + } + } + ], + "tooltip": "%T%", + "code": "$[SOCKET_INSTANCE].write($[DATA], $[ADDRESS], $[PORT]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%OTHER%" + }, + { + "kind": "block", + "type": "udp_close", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SOCKET_INSTANCE", + "instanceof": "udpSocket" + } + ], + "tooltip": "%T%", + "code": "$[SOCKET_INSTANCE].close();", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/@types/jaculus/blocks/wifi.jacly.json b/@types/jaculus/blocks/wifi.jacly.json new file mode 100644 index 0000000..08f3363 --- /dev/null +++ b/@types/jaculus/blocks/wifi.jacly.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "@types/jaculus", + "category": "wifi", + "parentCategory": "communication", + "name": "%T%", + "description": "%T%", + "colour": "#9C27B0", + "icon": "Wifi", + "priority": 105, + "import": ["import * as wifi from \"wifi\";"], + "contents": [ + { + "kind": "label", + "text": "%CURRENT_IP%" + }, + { + "kind": "block", + "type": "wifi_currentIp", + "message0": "%T%", + "tooltip": "%T%", + "code": "wifi.currentIp()", + "output": "String" + }, + { + "kind": "label", + "text": "%WAIT_FOR_CONNECTION%" + }, + { + "kind": "block", + "type": "wifi_waitForConnection", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PRINT_DOTS", + "options": [ + ["%PRINT_DOTS%", "true"], + ["%NO_PRINT_DOTS%", "false"] + ] + } + ], + "tooltip": "%T%", + "code": "while (!wifi.currentIp()) {\n if ($[PRINT_DOTS]) {\n console.log('.');\n }\n await sleep(500);\n}\n", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/@types/jaculus/package.json b/@types/jaculus/package.json new file mode 100644 index 0000000..502c891 --- /dev/null +++ b/@types/jaculus/package.json @@ -0,0 +1,27 @@ +{ + "name": "@types/jaculus", + "version": "0.1.0", + "author": "Petr Kubica (cubicap)", + "license": "MIT", + "description": "Core package for JacLy providing essential functionalities.", + "type": "module", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.d.ts", + "dist/**/*.js", + "blocks/**" + ], + "dependencies": { + "colors": "0.1.0" + }, + "scripts": { + "lib-ins": "jac lib-install", + "build-no-check": "npx copyfiles -u 2 src/types/*.d.ts dist/types", + "build": "npx copyfiles -u 2 src/types/*.d.ts dist/types", + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/@types/jaculus/src/types/adc.d.ts b/@types/jaculus/src/types/adc.d.ts new file mode 100644 index 0000000..f8ecbce --- /dev/null +++ b/@types/jaculus/src/types/adc.d.ts @@ -0,0 +1,23 @@ +declare module "adc" { + type Atten = number; + + const Attenuation: { + readonly Db0: Atten; + readonly Db2_5: Atten; + readonly Db6: Atten; + readonly Db11: Atten; + }; + + /** + * Enable ADC on the given pin. + * @param pin The pin to enable ADC on. + */ + function configure(pin: number, attenuation?: Atten): void; + + /** + * Read the value of the given pin. + * @param pin The pin to read. + * @returns The value of the pin (0-1023) + */ + function read(pin: number): number; +} diff --git a/@types/jaculus/src/types/basicStream.d.ts b/@types/jaculus/src/types/basicStream.d.ts new file mode 100644 index 0000000..70c9e06 --- /dev/null +++ b/@types/jaculus/src/types/basicStream.d.ts @@ -0,0 +1,22 @@ +declare interface Writable { + /** + * Write the given data to the stream. + * @param data The data to write. + */ + write(data: string): void; +} + +declare interface Readable { + /** + * Read a single character from the stream. + * @returns Promise that resolves to the character read. + */ + get(): Promise; + + /** + * Read a chunk of data from the stream. The size of the chunk is + * given by the implementation and available data. + * @returns Promise that resolves to the data read. + */ + read(): Promise; +} diff --git a/@types/jaculus/src/types/fs.d.ts b/@types/jaculus/src/types/fs.d.ts new file mode 100644 index 0000000..a21dc65 --- /dev/null +++ b/@types/jaculus/src/types/fs.d.ts @@ -0,0 +1,80 @@ +declare module "fs" { + interface File { + path: string; + + /** + * Check if the file is open. + */ + isOpen(): boolean; + + /** + * Close the file. + */ + close(): void; + + /** + * Read characters from the file. + * @param len The number of characters to read. + */ + read(len: number): string; + + /** + * Write text to the file. + * @param text The text to write. + */ + write(text: string): void; + } + + /** + * Open the given file in the given mode. + * @param path The path to the file. + * @param mode The mode to open the file in ("r", "w", "a" and combinations). + */ + function open(path: string, mode: string): File; + + /** + * Check if the given path exists. + * @param path The path to check. + * @returns True if the path exists, false otherwise. + */ + function exists(path: string): boolean; + + /** + * Check if the given path is a file. + * @param path The path to check. + * @returns True if the path is a file, false otherwise. + */ + function isFile(path: string): boolean; + + /** + * Check if the given path is a directory. + * @param path The path to check. + * @returns True if the path is a directory, false otherwise. + */ + function isDirectory(path: string): boolean; + + /** + * Create a directory at the given path. + * @param path The path to create the directory at. + */ + function mkdir(path: string): void; + + /** + * Remove the file at the given path. + * @param path The path to the file to remove. + */ + function rm(path: string): void; + + /** + * Remove the directory at the given path. + * @param path The path to the directory to remove. + */ + function rmdir(path: string): void; + + /** + * List the files in the given directory. + * @param path The path to the directory to list. + * @returns An array of file names in the directory. + */ + function readdir(path: string): string[]; +} diff --git a/@types/jaculus/src/types/globalTypes.d.ts b/@types/jaculus/src/types/globalTypes.d.ts new file mode 100644 index 0000000..821ce34 --- /dev/null +++ b/@types/jaculus/src/types/globalTypes.d.ts @@ -0,0 +1 @@ +declare type Rgb = number; diff --git a/@types/jaculus/src/types/gpio.d.ts b/@types/jaculus/src/types/gpio.d.ts new file mode 100644 index 0000000..f683b60 --- /dev/null +++ b/@types/jaculus/src/types/gpio.d.ts @@ -0,0 +1,53 @@ +declare module "gpio" { + const PinMode: { + readonly DISABLE: number; + readonly OUTPUT: number; + readonly INPUT: number; + readonly INPUT_PULLUP: number; + readonly INPUT_PULLDOWN: number; + }; + + interface EventInfo { + timestamp: Timestamp; + } + + /** + * Configure the given pin. + * @param pin The pin to configure. + * @param mode The mode to configure the pin in. + */ + function pinMode(pin: number, mode: number): void; + + /** + * Write digital value to the given pin. + * @param pin The pin to write to. + * @param value The value to write. + */ + function write(pin: number, value: number): void; + + /** + * Read digital value from the given pin. + * @param pin The pin to read from. + * @returns The value of the pin (0 or 1). + */ + function read(pin: number): number; + + /** + * Set event handler for the given pin. + * @param event The event to handle. + * @param pin The pin to handle the event for. + * @param callback The callback to call when the event occurs. + */ + function on( + event: "rising" | "falling" | "change", + pin: number, + callback: (info: EventInfo) => void, + ): void; + + /** + * Remove event handler for the given pin. + * @param event The event to remove. + * @param pin The pin to remove the event handler for. + */ + function off(event: "rising" | "falling" | "change", pin: number): void; +} diff --git a/@types/jaculus/src/types/gridui.d.ts b/@types/jaculus/src/types/gridui.d.ts new file mode 100644 index 0000000..2e5cad9 --- /dev/null +++ b/@types/jaculus/src/types/gridui.d.ts @@ -0,0 +1,470 @@ +declare module "gridui" { + namespace widget { + interface Base { + readonly uuid: number; + widgetX: number; + widgetY: number; + widgetW: number; + widgetH: number; + widgetTab: number; + css(key: string): string; + setCss(key: string, value: string): void; + } + + interface Arm extends Base { + readonly x: number; + readonly y: number; + } + + interface Bar extends Base { + color: string; + fontSize: number; + min: number; + max: number; + value: number; + showValue: boolean; + } + + interface Button extends Base { + text: string; + fontSize: number; + color: string; + background: string; + align: string; + valign: string; + disabled: boolean; + readonly pressed: boolean; + } + + interface Camera extends Base { + rotation: number; + clip: boolean; + } + + interface Checkbox extends Base { + fontSize: number; + checked: boolean; + color: string; + text: string; + } + + interface Circle extends Base { + color: string; + fontSize: number; + min: number; + max: number; + lineWidth: number; + valueStart: number; + value: number; + showValue: boolean; + } + + interface Input extends Base { + text: string; + color: string; + type: string; + disabled: boolean; + } + + interface Joystick extends Base { + color: string; + keys: string; + text: string; + readonly x: number; + readonly y: number; + } + + interface Led extends Base { + color: string; + on: boolean; + } + + interface Orientation extends Base { + color: string; + readonly yaw: number; + readonly pitch: number; + readonly roll: number; + readonly joystickX: number; + readonly joystickY: number; + } + + interface Select extends Base { + color: string; + background: string; + disabled: boolean; + options: string; + selectedIndex: number; + } + + interface Slider extends Base { + color: string; + fontSize: number; + min: number; + max: number; + value: number; + precision: number; + showValue: boolean; + } + + interface SpinEdit extends Base { + fontSize: number; + color: string; + value: number; + step: number; + precision: number; + } + + interface Switcher extends Base { + fontSize: number; + color: string; + value: number; + min: number; + max: number; + } + + interface Text extends Base { + text: string; + fontSize: number; + color: string; + background: string; + align: string; + valign: string; + prefix: string; + suffix: string; + } + } + + namespace builder { + interface Base { + css(key: string, value: string): this; + } + + interface Arm extends Base { + info(info: Record): Arm; + + onGrab(callback: (arm: widget.Arm) => void): Arm; + onPositionChanged(callback: (arm: widget.Arm) => void): Arm; + + finish(): widget.Arm; + } + + interface Bar extends Base { + color(color: string): Bar; + fontSize(fontSize: number): Bar; + min(min: number): Bar; + max(max: number): Bar; + value(value: number): Bar; + showValue(showValue: boolean): Bar; + + finish(): widget.Bar; + } + + interface Button extends Base { + text(text: string): Button; + fontSize(fontSize: number): Button; + color(color: string): Button; + background(background: string): Button; + align(align: string): Button; + valign(valign: string): Button; + disabled(disabled: boolean): Button; + + onPress(callback: (button: widget.Button) => void): Button; + onRelease(callback: (button: widget.Button) => void): Button; + + finish(): widget.Button; + } + + interface Camera extends Base { + rotation(rotation: number): Camera; + clip(clip: boolean): Camera; + tags(tags: any /* TODO: fix type */): Camera; + + finish(): widget.Camera; + } + + interface Checkbox extends Base { + fontSize(fontSize: number): Checkbox; + checked(checked: boolean): Checkbox; + color(color: string): Checkbox; + text(text: string): Checkbox; + + onChanged(callback: (checkbox: widget.Checkbox) => void): Checkbox; + + finish(): widget.Checkbox; + } + + interface Circle extends Base { + color(color: string): Circle; + fontSize(fontSize: number): Circle; + min(min: number): Circle; + max(max: number): Circle; + lineWidth(lineWidth: number): Circle; + valueStart(valueStart: number): Circle; + value(value: number): Circle; + showValue(showValue: boolean): Circle; + + finish(): widget.Circle; + } + + interface Input extends Base { + text(text: string): Input; + color(color: string): Input; + type(type: string): Input; + disabled(disabled: boolean): Input; + + onChanged(callback: (input: widget.Input) => void): Input; + + finish(): widget.Input; + } + + interface Joystick extends Base { + color(color: string): Joystick; + keys(keys: string): Joystick; + text(text: string): Joystick; + + onClick(callback: (joystick: widget.Joystick) => void): Joystick; + onPositionChanged( + callback: (joystick: widget.Joystick) => void, + ): Joystick; + + finish(): widget.Joystick; + } + + interface Led extends Base { + color(color: string): Led; + on(on: boolean): Led; + + finish(): widget.Led; + } + + interface Orientation extends Base { + color(color: string): Orientation; + + onPositionChanged( + callback: (orientation: widget.Orientation) => void, + ): Orientation; + + finish(): widget.Orientation; + } + + interface Select extends Base { + color(color: string): Select; + background(background: string): Select; + disabled(disabled: boolean): Select; + options(options: string): Select; + selectedIndex(selectedIndex: number): Select; + + onChanged(callback: (select: widget.Select) => void): Select; + + finish(): widget.Select; + } + + interface Slider extends Base { + color(color: string): Slider; + fontSize(fontSize: number): Slider; + min(min: number): Slider; + max(max: number): Slider; + value(value: number): Slider; + precision(precision: number): Slider; + showValue(showValue: boolean): Slider; + + onChanged(callback: (slider: widget.Slider) => void): Slider; + + finish(): widget.Slider; + } + + interface SpinEdit extends Base { + fontSize(fontSize: number): SpinEdit; + color(color: string): SpinEdit; + value(value: number): SpinEdit; + step(step: number): SpinEdit; + precision(precision: number): SpinEdit; + + onChanged(callback: (spinEdit: widget.SpinEdit) => void): SpinEdit; + + finish(): widget.SpinEdit; + } + + interface Switcher extends Base { + fontSize(fontSize: number): Switcher; + color(color: string): Switcher; + value(value: number): Switcher; + min(min: number): Switcher; + max(max: number): Switcher; + + onChanged(callback: (switcher: widget.Switcher) => void): Switcher; + + finish(): widget.Switcher; + } + + interface Text extends Base { + text(text: string): Text; + fontSize(fontSize: number): Text; + color(color: string): Text; + background(background: string): Text; + align(align: string): Text; + valign(valign: string): Text; + prefix(prefix: string): Text; + suffix(suffix: string): Text; + + finish(): widget.Text; + } + } + + class Builder { + arm( + x: number, + y: number, + w: number, + h: number, + uuid?: number, + tab?: number, + ): builder.Arm; + bar( + x: number, + y: number, + w: number, + h: number, + uuid?: number, + tab?: number, + ): builder.Bar; + button( + x: number, + y: number, + w: number, + h: number, + uuid?: number, + tab?: number, + ): builder.Button; + camera( + x: number, + y: number, + w: number, + h: number, + uuid?: number, + tab?: number, + ): builder.Camera; + checkbox( + x: number, + y: number, + w: number, + h: number, + uuid?: number, + tab?: number, + ): builder.Checkbox; + circle( + x: number, + y: number, + w: number, + h: number, + uuid?: number, + tab?: number, + ): builder.Circle; + input( + x: number, + y: number, + w: number, + h: number, + uuid?: number, + tab?: number, + ): builder.Input; + joystick( + x: number, + y: number, + w: number, + h: number, + uuid?: number, + tab?: number, + ): builder.Joystick; + led( + x: number, + y: number, + w: number, + h: number, + uuid?: number, + tab?: number, + ): builder.Led; + orientation( + x: number, + y: number, + w: number, + h: number, + uuid?: number, + tab?: number, + ): builder.Orientation; + select( + x: number, + y: number, + w: number, + h: number, + uuid?: number, + tab?: number, + ): builder.Select; + slider( + x: number, + y: number, + w: number, + h: number, + uuid?: number, + tab?: number, + ): builder.Slider; + spinEdit( + x: number, + y: number, + w: number, + h: number, + uuid?: number, + tab?: number, + ): builder.SpinEdit; + switcher( + x: number, + y: number, + w: number, + h: number, + uuid?: number, + tab?: number, + ): builder.Switcher; + text( + x: number, + y: number, + w: number, + h: number, + uuid?: number, + tab?: number, + ): builder.Text; + } + + /** + * Initialize GridUI. + * @param ownerName name of the owner, must match the name entered in RBController app. + * @param deviceName name of this device, visible in the RBController app. + * @param builderCallback callback, which receives the builder instance that can be used to create widgets. + */ + function begin( + ownerName: string, + deviceName: string, + builderCallback: (builder: Builder) => void, + ): void; + + /** + * Stop GridUI. + */ + function end(): void; + + /** + * Set current tab index + */ + function changeTab(index: number): void; + + /** + * Send a message to the integrated terminal at the top of the UI. + */ + function log(message: string): void; + + /** + * Returns included GridUI version as number, to be compared with hex representation of the version. + * + * For example, for version 5.1.0, do: `gridui.version() >= 0x050100` + */ + function version(): number; +} diff --git a/@types/jaculus/src/types/i2c.d.ts b/@types/jaculus/src/types/i2c.d.ts new file mode 100644 index 0000000..592c57c --- /dev/null +++ b/@types/jaculus/src/types/i2c.d.ts @@ -0,0 +1,49 @@ +declare module "i2c" { + type I2CData = ArrayBuffer | Uint8Array | number[] | string | number; + + interface I2C { + /** + * Find an I2C interface by its pin. + * @param pin The pin the I2C interface is connected to. + * @returns The I2C interface, or undefined if not found. + */ + find(pin: number): I2C | undefined; + + /** + * Read from the given address. + * @param address The address to read from. + * @param quantity The number of bytes to read. + * @returns The bytes read. + */ + readFrom(address: number, quantity: number): Uint8Array; + + /** + * Write to the given address. + * @param address The address to write to. + * @param buffer The data to write. + */ + writeTo(address: number, buffer: I2CData): void; + + /** + * Perform a write and read transaction. + * @param address The address to write to. + * @param buffer The data to write. + * @param quantity The number of bytes to read. + * @returns The bytes read. + */ + writeRead( + address: number, + buffer: I2CData, + quantity: number, + ): Uint8Array; + + /** + * Setup the I2C interface. + * @param options The options to use when setting up the I2C interface. + */ + setup(options: { scl?: number; sda?: number; bitrate?: number }): void; + } + + const I2C1: I2C; + const I2C2: I2C | undefined; +} diff --git a/@types/jaculus/src/types/index.d.ts b/@types/jaculus/src/types/index.d.ts new file mode 100644 index 0000000..9746bcd --- /dev/null +++ b/@types/jaculus/src/types/index.d.ts @@ -0,0 +1,23 @@ +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// diff --git a/@types/jaculus/src/types/keyvalue.d.ts b/@types/jaculus/src/types/keyvalue.d.ts new file mode 100644 index 0000000..216fdbb --- /dev/null +++ b/@types/jaculus/src/types/keyvalue.d.ts @@ -0,0 +1,67 @@ +declare module "keyvalue" { + class KeyValueNamespace { + /** + * Get saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present + */ + get(key: string): string | number | null; + + /** + * Get string saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present or if not string + */ + getString(key: string): string | null; + + /** + * Get number saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present or if not number + */ + getNumber(key: string): number | null; + + /** + * Set value in KeyValue namespace, overwriting any previous value. + * Call commit() after setting everything, otherwise changes will be lost! + * @param key key, max 15 characters. + * @param value value + */ + set(key: string, value: string | number): void; + + /** + * Erase value from KeyValue namespace. + * Call commit() after setting everything, otherwise changes will be lost! + * @param key key, max 15 characters. + */ + erase(key: string): void; + + /** + * Check existance of a key. + * @param key key, max 15 characters. + * @returns true if exists, false otherwise + */ + exists(key: string): boolean; + + /** + * List all keys in the namespace. + * @returns array of keys + */ + keys(): string[]; + + /** + * Save modifications to persistent storage. + */ + commit(): void; + } + + /** + * Open a KeyValue namespace for use. KeyValue is a persitent storage for small + * pieces of data that saves values across restarts. + * + * The namespaces are isolated from each other. + * + * @param namespace Namespace name, max 15 characters. + */ + function open(namespace: string): KeyValueNamespace; +} diff --git a/@types/jaculus/src/types/ledc.d.ts b/@types/jaculus/src/types/ledc.d.ts new file mode 100644 index 0000000..435988f --- /dev/null +++ b/@types/jaculus/src/types/ledc.d.ts @@ -0,0 +1,53 @@ +declare module "ledc" { + /** + * Configure the given timer. + * @param timer The timer to configure. + * @param frequency The frequency to configure the timer to. + * @param resolution The resolution to configure the timer to (default 10 bits, changes frequency range) + */ + function configureTimer( + timer: number, + frequency: number, + resolution?: number, + ): void; + + /** + * Configure the given LEDC channel. + * @param channel The channel to configure. + * @param pin The pin to configure the channel to. + * @param timer The timer to configure the channel to. + * @param duty The duty to configure the channel to (0-1023). + */ + function configureChannel( + channel: number, + pin: number, + timer: number, + duty: number, + ): void; + + /** + * Set the frequency of the given timer. + * @param timer The timer to set the frequency of. + * @param frequency The frequency to set the timer to. + */ + function setFrequency(timer: number, frequency: number): void; + + /** + * Set the duty of the given channel. + * @param channel The channel to set the duty of. + * @param duty The duty to set the channel to (0-1023). + */ + function setDuty(channel: number, duty: number): void; + + /** + * Stop the given timer. + * @param timer The timer to stop. + */ + function stopTimer(timer: number): void; + + /** + * Stop the given channel. + * @param channel The channel to stop. + */ + function stopChannel(channel: number): void; +} diff --git a/@types/jaculus/src/types/misc.d.ts b/@types/jaculus/src/types/misc.d.ts new file mode 100644 index 0000000..05e77a2 --- /dev/null +++ b/@types/jaculus/src/types/misc.d.ts @@ -0,0 +1,5 @@ +/** + * Exits the current program. + * @param code The exit code to use. + */ +declare function exit(code?: number): void; diff --git a/@types/jaculus/src/types/motor.d.ts b/@types/jaculus/src/types/motor.d.ts new file mode 100644 index 0000000..c6edfb1 --- /dev/null +++ b/@types/jaculus/src/types/motor.d.ts @@ -0,0 +1,121 @@ +declare module "motor" { + type MoveDuration = { distance: number } | { time: number } | {}; + + type MotorPins = { + motA: number; + motB: number; + encA: number; + encB: number; + }; + + type LedcConfig = { + timer: number; + channelA: number; + channelB: number; + }; + + type RegParams = { + // All values must be integers, defaults to 0 + kp?: number; // Proportional gain + ki?: number; // Integral gain + kd?: number; // Derivative gain + kv?: number; // Velocity feedforward + ka?: number; // Acceleration feedforward + kc?: number; // Constant feedforward + maxIOut?: number; // Maximum integral output + unwindFactor?: number; // Integral unwind factor (0 for no change) + }; + + class Motor { + /** + * Construc a new Motor instance + * @param options Motor configuration + * @note Units used in the circumference parameter determines the units used in the other methods + */ + constructor(options: { + pins: MotorPins; + ledc: LedcConfig; + reg: RegParams; + encTicks: number; + circumference: number; + }); + + /** + * Set the speed of the motor + * @param speed Speed in units per second + * @note The units are the same as used in the circumference parameter + */ + setSpeed(speed: number): void; + + /** + * Set the speed ramp of the motor + * @param ramp Speed ramp in units per second squared + * @note The units are the same as used in the circumference parameter + */ + setRamp(ramp: number): void; + + /** + * Set the end position tolerance for distance moves + * @param tolerance + */ + setEndPosTolerance(tolerance: number): void; + + /** + * Move the motor + * @param duration Duration of the movement + * @note The units are the same as used in the circumference parameter + * @note If the duration is not provided, the motor will move indefinitely + */ + move(duration?: MoveDuration): Promise; + + /** + * Set raw power to the motor + * @param pwm Power in the range -1023 to 1023 + * @note This switches the motor to unregulated mode + */ + setRaw(power: number): void; + + /** + * Stop the motor + * @param brake If true, the motor will brake, otherwise it will coast to a stop + */ + stop(brake?: boolean): Promise; + + /** + * Get the position of the motor + * @note The units are the same as used in the circumference parameter + * @returns The position of the motor + */ + getPosition(): number; + + /** + * Close the motor + */ + close(): void; + + /** + * Start logging motor data for reporting + * @param everyNth Log every nth data point + * @note The data is stored in memory and if the memory is full, + * may cause the program to crash. Use everyNth to limit + * the data points. + * @note Previous data is not cleared until reportClear is called. + */ + reportStart(everyNth: number): void; + + /** + * Stop logging motor data + */ + reportStop(): void; + + /** + * Clear the report data + */ + reportClear(): void; + + /** + * Dump the report data to stdout + */ + reportDump(): void; + } +} diff --git a/@types/jaculus/src/types/onewire.d.ts b/@types/jaculus/src/types/onewire.d.ts new file mode 100644 index 0000000..09c120d --- /dev/null +++ b/@types/jaculus/src/types/onewire.d.ts @@ -0,0 +1,59 @@ +declare module "onewire" { + type OneWireData = ArrayBuffer | Uint8Array | number[] | string | number; + + class OneWire { + /** + * Create a OneWire instance on the specified data pin. + * @param pin The specified pin + */ + constructor(pin: number); + + /** + * Reset the 1-Wire bus. + * @return True if any devices are present, false otherwise. + */ + reset(): boolean; + + /** + * Read bytes from the bus. + * @param count Number of bytes to read. Defaults to 1. + * @return The bytes read. + */ + read(count?: number): Uint8Array; + + /** + * Write bytes to the bus. + * @param data Data to write. + */ + write(data: OneWireData): void; + + /** + * Select a specific device by its 8-byte ROM. + * @param rom Device ROM address. + */ + select(rom: OneWireData): void; + + /** + * Skip ROM selection and address all devices on the bus. + */ + skip(): void; + + /** + * Search devices on the bus. + * @returns Array of 8-byte ROM addresses. + */ + search(): Uint8Array[]; + + /** + * Compute Maxim/Dallas CRC-8 for the provided data. + * @param data Data to checksum. + * @returns CRC-8 value. + */ + crc8(data: OneWireData): number; + + /** + * Release bus resources. + */ + close(): void; + } +} diff --git a/@types/jaculus/src/types/path.d.ts b/@types/jaculus/src/types/path.d.ts new file mode 100644 index 0000000..c4434c6 --- /dev/null +++ b/@types/jaculus/src/types/path.d.ts @@ -0,0 +1,36 @@ +declare module "path" { + /** + * Normalize the given path. + * @param path The path to normalize. + * @returns The normalized path. + */ + function normalize(path: string): string; + + /** + * Get the parent directory of the given path. + * @param path The path to get the parent directory of. + * @returns The parent directory. + */ + function dirname(path: string): string; + + /** + * Get the last part of the given path. + * @param path The path to get the last part of. + * @returns The last part of the path. + */ + function basename(path: string): string; + + /** + * Join the given paths. + * @param paths The paths to join. + * @returns The joined path. + */ + function join(...paths: string[]): string; + + /** + * Check if the given path is absolute. + * @param path The path to check. + * @returns True if the path is absolute, false otherwise. + */ + function isAbsolute(path: string): boolean; +} diff --git a/@types/jaculus/src/types/platform.d.ts b/@types/jaculus/src/types/platform.d.ts new file mode 100644 index 0000000..8830c9d --- /dev/null +++ b/@types/jaculus/src/types/platform.d.ts @@ -0,0 +1,6 @@ +declare const PlatformInfo: { + /** + * The name of the platform the program is running on. + */ + name: string; +}; diff --git a/@types/jaculus/src/types/pulseCounter.d.ts b/@types/jaculus/src/types/pulseCounter.d.ts new file mode 100644 index 0000000..a51dbb0 --- /dev/null +++ b/@types/jaculus/src/types/pulseCounter.d.ts @@ -0,0 +1,64 @@ +declare module "pulseCounter" { + type LevelAction = number; + type EdgeAction = number; + + const LevelAction: { + Keep: LevelAction; + Inverse: LevelAction; + Hold: LevelAction; + }; + + const EdgeAction: { + Hold: EdgeAction; + Increase: EdgeAction; + Decrease: EdgeAction; + }; + + type LevelMode = { + low: LevelAction; + high: LevelAction; + }; + + type EdgeMode = { + pos: EdgeAction; + neg: EdgeAction; + }; + + class PulseCounter { + /** + * Create a new pulse counter instance. + * @param options The options for the pulse counter. + */ + constructor(options: { + pinLevel: number; + pinEdge: number; + levelMode: LevelMode; + edgeMode: EdgeMode; + }); + + /** + * Read the current value of the pulse counter. + */ + read(): number; + + /** + * Reset the pulse counter to 0. + */ + clear(): void; + + /** + * Start the pulse counter. + */ + start(): void; + + /** + * Stop the pulse counter. + */ + stop(): void; + + /** + * Close the pulse counter. + */ + close(): void; + } +} diff --git a/@types/jaculus/src/types/simpleradio.d.ts b/@types/jaculus/src/types/simpleradio.d.ts new file mode 100644 index 0000000..1805ce3 --- /dev/null +++ b/@types/jaculus/src/types/simpleradio.d.ts @@ -0,0 +1,110 @@ +declare module "simpleradio" { + type PacketDataType = "number" | "string" | "keyvalue" | "blob"; + type BlobData = ArrayBuffer | Uint8Array | number[]; + + interface PacketInfo { + group: number; + address: string; + rssi: number; + } + + /** + * Initialize the radio. + * @param group The radio group to use. + */ + function begin(group: number): void; + + /** + * Set the radio group. + * @param group The radio group to use, between 0 and 255 inclusive. + */ + function setGroup(group: number): void; + + /** + * Get current radio group + * @returns ID of the current group + */ + function group(): number; + + /** + * Get the local device address. + * @returns the local device address. Only works after begin() is called. + */ + function address(): string; + + /** + * Send a string. + * @param str The string to send. + */ + function sendString(str: string): void; + + /** + * Send a number. + * @param num The number to send. + */ + function sendNumber(num: number): void; + + /** + * Send a key-value pair. + * @param key The key to send. + * @param value The number to send. + */ + function sendKeyValue(key: string, value: number): void; + + /** + * Send a binary blob. + * @param data The binary data to send. + */ + function sendBlob(data: BlobData): void; + + /** + * Register a callback for a packet type. + * @param type The packet type to register for. + * @param callback The callback to register. + */ + function on( + type: "number", + callback: (num: number, info: PacketInfo) => void, + ): void; + + /** + * Register a callback for a packet type. + * @param type The packet type to register for. + * @param callback The callback to register. + */ + function on( + type: "string", + callback: (str: string, info: PacketInfo) => void, + ): void; + + /** + * Register a callback for a packet type. + * @param type The packet type to register for. + * @param callback The callback to register. + */ + function on( + type: "keyvalue", + callback: (key: string, value: number, info: PacketInfo) => void, + ): void; + + /** + * Register a callback for a packet type. + * @param type The packet type to register for. + * @param callback The callback to register. + */ + function on( + type: "blob", + callback: (data: Uint8Array, info: PacketInfo) => void, + ): void; + + /** + * Unregister a callback for a packet type. + * @param type The packet type to unregister for. + */ + function off(type: PacketDataType): void; + + /** + * Stop the radio. + */ + function end(): void; +} diff --git a/@types/jaculus/src/types/smartled.d.ts b/@types/jaculus/src/types/smartled.d.ts new file mode 100644 index 0000000..d77fe2f --- /dev/null +++ b/@types/jaculus/src/types/smartled.d.ts @@ -0,0 +1,50 @@ +declare module "smartled" { + interface LedType { + T0H: number; + T1H: number; + T0L: number; + T1L: number; + TRS: number; + } + + class SmartLed { + public readonly count: number; + /** + * Create a new Smart LED strip. + * @param pin The pin the strip is connected to. + * @param count The number of LEDs in the strip. + * @param type The type of LED strip. + */ + constructor(pin: number, count: number, type?: LedType); + + /** + * Show the current buffer on the strip. + */ + public show(): void; + + /** + * Set the color of the given LED. + * @param index The index of the LED to set. + * @param rgb The color to set the LED to. + */ + public set(index: number, rgb: Rgb): void; + + /** + * Get the color of the given LED. + * @param index The index of the LED to get. + * @returns The color of the LED. + */ + public get(index: number): Rgb; + + /** + * Clear the buffer. + */ + public clear(): void; + } + + const LED_WS2812: LedType; + const LED_WS2812B: LedType; + const LED_WS2812B_2020: LedType; + const LED_SK6812: LedType; + const LED_WS2813: LedType; +} diff --git a/@types/jaculus/src/types/spi.d.ts b/@types/jaculus/src/types/spi.d.ts new file mode 100644 index 0000000..5ef64fa --- /dev/null +++ b/@types/jaculus/src/types/spi.d.ts @@ -0,0 +1,56 @@ +declare module "spi" { + type SPIData = ArrayBuffer | Uint8Array | number[] | string | number; + + interface SPI { + /** + * Send data and return received bytes + * @param data Data to send. + * @param cs Chip-select pin. + */ + send(data: SPIData, cs: number): Uint8Array; + + /** + * Write data + * @param data Data to write. + * @param cs Chip-select pin. + */ + write(data: SPIData, cs: number): void; + + /** + * Perform a write and read transaction. + * @param data Data to write. + * @param cs Chip-select pin. + * @param rxLength Number of bytes to read. + * @param qio Whether to use quad SPI mode. + * @returns The bytes read. + */ + transfer( + data: SPIData, + cs: number, + rxLength: number, + qio: boolean, + ): Uint8Array; + + /** + * Configure SPI bus and device pins/options. + * Accepts either mosi/miso or data0/data1 for pins of the first two data lines; + * data2/data3 can be used for quad SPI with qio=true in transfer() calls. + */ + setup(options: { + sck?: number; + mosi?: number; + miso?: number; + data0?: number; + data1?: number; + data2?: number; + data3?: number; + baud?: number; + mode?: number; + order?: "lsb" | "msb"; + }): void; + } + + const SPI1: SPI; + const SPI2: SPI; + const SPI3: SPI | undefined; +} diff --git a/@types/jaculus/src/types/stdio.d.ts b/@types/jaculus/src/types/stdio.d.ts new file mode 100644 index 0000000..78c2af2 --- /dev/null +++ b/@types/jaculus/src/types/stdio.d.ts @@ -0,0 +1,13 @@ +declare module "stdio" { + let stdout: Writable; + let stderr: Writable; + let stdin: Readable; +} + +declare const console: { + debug(arg: any): void; + log(arg: any): void; + warn(arg: any): void; + info(arg: any): void; + error(arg: any): void; +}; diff --git a/@types/jaculus/src/types/timers.d.ts b/@types/jaculus/src/types/timers.d.ts new file mode 100644 index 0000000..4f73380 --- /dev/null +++ b/@types/jaculus/src/types/timers.d.ts @@ -0,0 +1,31 @@ +/** + * Returns a promise that resolves after the specified time. + * @param ms The number of milliseconds to wait before resolving the promise. + */ +declare function sleep(ms: number): Promise; + +/** + * Calls a function after the specified time. + * @param callback The function to call. + * @param ms The number of milliseconds to wait before calling the function. + */ +declare function setTimeout(callback: () => void, ms: number): number; + +/** + * Calls a function repeatedly, with a fixed time delay between each call. + * @param callback The function to call. + * @param ms The number of milliseconds to wait before calling the function. + */ +declare function setInterval(callback: () => void, ms: number): number; + +/** + * Cancels a timeout previously established by calling setTimeout(). + * @param id The identifier of the timeout to cancel. + */ +declare function clearTimeout(id: number): void; + +/** + * Cancels a timeout previously established by calling setInterval(). + * @param id The identifier of the interval to cancel. + */ +declare function clearInterval(id: number): void; diff --git a/@types/jaculus/src/types/timestamp.d.ts b/@types/jaculus/src/types/timestamp.d.ts new file mode 100644 index 0000000..bd9ab6c --- /dev/null +++ b/@types/jaculus/src/types/timestamp.d.ts @@ -0,0 +1,11 @@ +declare interface Timestamp { + /** + * Convert the timestamp to milliseconds. + */ + millis(): number; + + /** + * Get the lower 10^3 part of the timestamp in microseconds. + */ + micros(): number; +} diff --git a/@types/jaculus/src/types/udpSocket.d.ts b/@types/jaculus/src/types/udpSocket.d.ts new file mode 100644 index 0000000..e577481 --- /dev/null +++ b/@types/jaculus/src/types/udpSocket.d.ts @@ -0,0 +1,39 @@ +declare module "udp" { + interface UdpBuffer extends ArrayBuffer { + address: string; + port: number; + } + + class UdpSocket { + /** + * Creates a UDP socket and binds it to the specified address and port. + * @param options The socket options. + */ + constructor(options: { + address?: string; + port?: number; + onReadable?: (dgramsAvailable: number) => void; + onError?: (error: Error) => void; + }); + + /** + * Reads a single datagram from the socket. + * @returns The datagram buffer with `address` and `port` properties, + * or `null` if no datagram is available. + */ + read(): UdpBuffer | null; + + /** + * Sends a datagram to the specified address and port. + * @param data The data to send. + * @param address The destination IP address. + * @param port The destination port. + */ + write(data: ArrayBuffer, address: string, port: number): void; + + /** + * Closes the socket. + */ + close(): void; + } +} diff --git a/@types/jaculus/src/types/wifi.d.ts b/@types/jaculus/src/types/wifi.d.ts new file mode 100644 index 0000000..ca7ec27 --- /dev/null +++ b/@types/jaculus/src/types/wifi.d.ts @@ -0,0 +1,16 @@ +declare module "wifi" { + /** + * Return current IPv4 of the device, or null if WiFi is disabled or not connected. + */ + function currentIp(): string | null; + + /** + * List all saved WiFi networks. + */ + function listNetworks(): string[]; + + /** + * Return the device MAC address in standard string form. + */ + function address(): string; +} diff --git a/@types/jaculus/tsconfig.json b/@types/jaculus/tsconfig.json new file mode 100644 index 0000000..3a3fbba --- /dev/null +++ b/@types/jaculus/tsconfig.json @@ -0,0 +1,14 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true + } +} diff --git a/LICENSE b/LICENSE index b60da95..fc70fbd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Petr Kubica (cubicap) +Copyright (c) 2025 Jaculus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 1630891..d3104a1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ This repository contains libraries for Jaculus. - ## Creating a new library Libraries mostly follow the same structure as npm packages, i.e. they use a `package.json` file to define metadata and dependencies. An additional field `jaculus` can be used to specify additional options for Jaculus, such as block definitions. diff --git a/algorithms/.gitignore b/algorithms/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/algorithms/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/algorithms/README.md b/algorithms/README.md new file mode 100644 index 0000000..62c362c --- /dev/null +++ b/algorithms/README.md @@ -0,0 +1,266 @@ +# Algorithms + +Collection of optimized algorithms for signal processing and data analysis. + +## Exponential Moving Average (EMA) + +### Overview + +**Exponential Moving Average** is a filter that smooths data by giving more weight to recent values while gradually discounting older observations. It's essential for: + +- Sensor data smoothing +- Trend detection +- Noise reduction in real-time systems + +### Fast EMA Implementation + +The `createEmaFilter` function provides a high-performance EMA filter optimized for microcontrollers and performance-critical JavaScript: + +**Why "Fast"?** + +- **Memory**: O(1) - stores only a single state variable +- **Speed**: Only 1 subtraction, 1 addition, 2 bit shifts per call +- **No float**: Uses fixed-point integer arithmetic +- **Stable**: Jumps to real value on initialization, not starting from zero + +### Usage + +```typescript +import { createEmaFilter } from "algorithms"; + +// Create filter with shift=2 (window ≈ 4 samples, fastest response) +const ema = createEmaFilter(2); + +// Process sensor readings +console.log(ema(100)); // 100 (first value, initialized) +console.log(ema(102)); // 101 (smoothed toward 102) +console.log(ema(50)); // 80 (smoothed toward 50) +console.log(ema(48)); // 65 +console.log(ema(150)); // 111 (smoothed toward 150) + +// Shift=4 (window ≈ 16 samples, smoother, slower response) +const smoothEma = createEmaFilter(4); +console.log(smoothEma(100)); // 100 +console.log(smoothEma(102)); // 101 +console.log(smoothEma(50)); // 93 (smoother transition) +console.log(smoothEma(48)); // 86 +console.log(smoothEma(150)); // 104 (slower response) +``` + +### Parameters + +- **shift** (number): Controls smoothing strength + - Relationship: window ≈ 2^shift samples + - **shift=2**: Fast response, window ≈ 4 samples + - **shift=4**: Balanced, window ≈ 16 samples + - **shift=6**: Smoother, window ≈ 64 samples + - **shift=8**: Very smooth, window ≈ 256 samples + +### How It Works + +#### Storage Variable + +**storage** is the internal state variable that accumulates the smoothed value. It holds the weighted average multiplied by 2^shift to keep everything as integers. For example: + +- With shift=2: `storage = 100` means the actual smoothed value is 25 (100 ÷ 4) +- With shift=4: `storage = 400` means the actual smoothed value is 25 (400 ÷ 16) + +#### Fixed-Point Arithmetic + +To avoid floating-point operations: + +- Store value multiplied by 2^shift internally (into `storage`) +- All operations use integer bit shifts and arithmetic +- Extract and display using right shift (>>) operation + +#### Algorithm + +``` +storage = storage - (storage >> shift) + newValue +output = (storage + offset) >> shift // offset = 2^(shift-1) for rounding +``` + +#### Rounding + +Without rounding, results round down. Adding `offset` before the division gives proper banker's rounding (0.5+ rounds up): + +``` +// Example: shift=2 (divisor=4) +stored value: 10 (represents 2.5 in fixed-point) +Without rounding: 10 >> 2 = 2 +With rounding: (10 + 2) >> 2 = 3 ✓ +``` + +### Limitations + +- JavaScript bitwise operations work with 32-bit signed integers +- Maximum safe storage value: 2,147,483,647 +- For 16-bit sensor data (0-65535), safe shift range is 0-14 + +## PID Controller + +### Overview + +**PID (Proportional-Integral-Derivative) Controller** is a closed-loop control algorithm used to drive a measured value (speed, angle, distance, temperature) toward a target setpoint. + +- **P** reacts to current error +- **I** compensates long-term steady-state error +- **D** dampens fast changes and overshoot + +### Usage + +```typescript +import { createPidController } from "algorithms"; + +const pid = createPidController({ + kp: 1.2, + ki: 0.4, + kd: 0.05, + outputMin: -255, + outputMax: 255, + integralMin: -200, + integralMax: 200, +}); + +const control = pid.update({ + setpoint: 300, + measurement: 250, + dt: 0.02, // 20 ms control loop +}); + +console.log(control); // PWM or motor command +``` + +### API + +`createPidController(config)` returns an object with: + +- `update({ setpoint, measurement, dt })` -> computed control output +- `reset()` -> clears integral and derivative history + +### Parameters + +`config` fields: + +- `kp` (number): proportional gain +- `ki` (number): integral gain +- `kd` (number): derivative gain +- `outputMin` (number, optional): lower output clamp +- `outputMax` (number, optional): upper output clamp +- `integralMin` (number, optional): lower integral clamp (anti-windup) +- `integralMax` (number, optional): upper integral clamp (anti-windup) + +`update` input fields: + +- `setpoint` (number): desired target value +- `measurement` (number): current measured value +- `dt` (number): loop period in seconds; must be positive and finite + +### Notes + +- Start tuning with `ki = 0` and `kd = 0`, then increase `kp` +- Add small `ki` to remove steady-state error +- Add `kd` only if needed to reduce overshoot/noise sensitivity +- Always use output/integral limits for motor control loops + +### Larger Example (ADC -> LEDC with PID) + +The following example builds on your ADC-to-LEDC pattern and adds PID so LED brightness changes smoothly and controllably. + +```typescript +import * as adc from "adc"; +import * as ledc from "ledc"; +import { createPidController } from "algorithms"; + +const INPUT_PIN = 1; +const LED_PIN = 46; + +const PWM_TIMER = 0; +const PWM_CHANNEL = 0; +const PWM_MIN = 0; +const PWM_MAX = 1023; + +adc.configure(INPUT_PIN); +ledc.configureTimer(PWM_TIMER, 1000); +ledc.configureChannel(PWM_CHANNEL, LED_PIN, PWM_TIMER, PWM_MAX); + +const power = 3; +const dt = 0.01; // 10 ms + +const pid = createPidController({ + kp: 0.1, + ki: 0.05, + kd: 0.005, + outputMin: -120, + outputMax: 120, + integralMin: -400, + integralMax: 400, +}); + +let currentDuty = 0; + +const clamp = (value: number, min: number, max: number) => + Math.min(Math.max(value, min), max); + +setInterval(() => { + // Target value from ADC (same non-linear response as your example). + const adcValue = adc.read(INPUT_PIN); + const targetDuty = Math.pow(adcValue, power) / Math.pow(1023, power - 1); + + // PID output is used as duty increment/decrement per cycle. + const step = pid.update({ + setpoint: targetDuty, + measurement: currentDuty, + dt, + }); + + currentDuty = clamp(currentDuty + step, PWM_MIN, PWM_MAX); + ledc.setDuty(PWM_CHANNEL, currentDuty); +}, 50); +``` + +Why this helps: + +- Direct mapping (`setDuty(mappedValue)`) can jump abruptly. +- PID treats brightness as a controlled process and moves toward target smoothly. +- Output and integral limits prevent aggressive jumps and windup. + +### Using PID with Motor Speed + +For motors, the wiring is similar, only your `measurement` should come from encoder speed (or another real feedback source): + +```typescript +import { createPidController } from "algorithms"; + +const pid = createPidController({ + kp: 1.2, + ki: 0.35, + kd: 0.02, + outputMin: -1023, + outputMax: 1023, + integralMin: -3000, + integralMax: 3000, +}); + +const dt = 0.01; // 10 ms control loop + +setInterval(() => { + const targetSpeed = 220; // mm/s, from joystick/plan + const measuredSpeed = getMotorSpeedMmS(); // from encoder + + const motorCommand = pid.update({ + setpoint: targetSpeed, + measurement: measuredSpeed, + dt, + }); + + setMotorPower(motorCommand); // e.g. setRaw / PWM duty +}, 10); +``` + +Motor checklist: + +- `setpoint`: desired speed (or position/angle) +- `measurement`: encoder-derived actual value +- `output`: motor command (PWM/raw power/current request) +- Keep loop period fixed and pass it as `dt` in seconds diff --git a/algorithms/blocks/algorithms.jacly.json b/algorithms/blocks/algorithms.jacly.json new file mode 100644 index 0000000..d04499f --- /dev/null +++ b/algorithms/blocks/algorithms.jacly.json @@ -0,0 +1,232 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "category": "algorithms", + "name": "%T%", + "description": "%T%", + "colour": "#7A9E65", + "icon": "Sigma", + "priority": 140, + "import": ["import * as algorithms from \"algorithms\";"], + "contents": [ + { + "kind": "label", + "text": "%EMA%" + }, + { + "kind": "block", + "type": "algorithms_ema_create", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "ema_filter_?" + }, + { + "type": "input_value", + "name": "SHIFT", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 4 + } + } + } + ], + "constructs": "ema_filter", + "tooltip": "%T%", + "code": "$[CONSTRUCTED_VAR_NAME] = algorithms.createEmaFilter($[SHIFT]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "algorithms_ema_apply", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "FILTER_INSTANCE", + "instanceof": "ema_filter" + }, + { + "type": "input_value", + "name": "VALUE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + } + ], + "tooltip": "%T%", + "code": "$[FILTER_INSTANCE]($[VALUE])", + "output": "Number" + }, + { + "kind": "label", + "text": "%PID%" + }, + { + "kind": "block", + "type": "algorithms_pid_create", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "pid_?" + }, + { + "type": "input_value", + "name": "KP", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0.1 + } + } + }, + { + "type": "input_value", + "name": "KI", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0.05 + } + } + }, + { + "type": "input_value", + "name": "KD", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0.005 + } + } + }, + { + "type": "input_value", + "name": "OUTPUT_MIN", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": -120 + } + } + }, + { + "type": "input_value", + "name": "OUTPUT_MAX", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 120 + } + } + }, + { + "type": "input_value", + "name": "INTEGRAL_MIN", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": -400 + } + } + }, + { + "type": "input_value", + "name": "INTEGRAL_MAX", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 400 + } + } + } + ], + "constructs": "pid", + "tooltip": "%T%", + "code": "$[CONSTRUCTED_VAR_NAME] = algorithms.createPidController({ kp: $[KP], ki: $[KI], kd: $[KD], outputMin: $[OUTPUT_MIN], outputMax: $[OUTPUT_MAX], integralMin: $[INTEGRAL_MIN], integralMax: $[INTEGRAL_MAX] });", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "algorithms_pid_update", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PID_INSTANCE", + "instanceof": "pid" + }, + { + "type": "input_value", + "name": "SETPOINT", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "MEASUREMENT", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "DT", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0.01 + } + } + } + ], + "tooltip": "%T%", + "code": "$[PID_INSTANCE].update({ setpoint: $[SETPOINT], measurement: $[MEASUREMENT], dt: $[DT] })", + "output": "Number" + }, + { + "kind": "block", + "type": "algorithms_pid_reset", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PID_INSTANCE", + "instanceof": "pid" + } + ], + "tooltip": "%T%", + "code": "$[PID_INSTANCE].reset();", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/algorithms/blocks/translations/cs.lang.json b/algorithms/blocks/translations/cs.lang.json new file mode 100644 index 0000000..411a42b --- /dev/null +++ b/algorithms/blocks/translations/cs.lang.json @@ -0,0 +1,16 @@ +{ + "ALGORITHMS_NAME": "Algoritmy", + "ALGORITHMS_DESCRIPTION": "Bloky algoritmů pro zpracování dat a filtrování.", + "ALGORITHMS_LABEL_EMA": "Exponenciální klouzavý průměr (EMA)", + "ALGORITHMS_LABEL_PID": "PID regulátor", + "ALGORITHMS_EMA_CREATE_MESSAGE0": "vytvořit EMA filtr $[CONSTRUCTED_VAR_NAME] s posunem $[SHIFT]", + "ALGORITHMS_EMA_CREATE_TOOLTIP": "Vytvoří instanci filtru Exponenciální klouzavý průměr", + "ALGORITHMS_EMA_APPLY_MESSAGE0": "aplikovat EMA filtr $[FILTER_INSTANCE] na hodnotu $[VALUE]", + "ALGORITHMS_EMA_APPLY_TOOLTIP": "Aplikuje filtr na vstupní hodnotu a vrátí vyhlazený výstup", + "ALGORITHMS_PID_CREATE_MESSAGE0": "vytvořit PID regulátor $[CONSTRUCTED_VAR_NAME]\nkp $[KP] ki $[KI] kd $[KD]\nvýstup min $[OUTPUT_MIN] max $[OUTPUT_MAX]\nintegrál min $[INTEGRAL_MIN] max $[INTEGRAL_MAX]", + "ALGORITHMS_PID_CREATE_TOOLTIP": "Vytvoří instanci PID regulátoru se zesílením a limity", + "ALGORITHMS_PID_UPDATE_MESSAGE0": "PID $[PID_INSTANCE] výpočet\nžádaná hodnota $[SETPOINT] měření $[MEASUREMENT] dt $[DT] s", + "ALGORITHMS_PID_UPDATE_TOOLTIP": "Vypočítá výstup PID podle žádané hodnoty, měření a času smyčky", + "ALGORITHMS_PID_RESET_MESSAGE0": "resetovat PID regulátor $[PID_INSTANCE]", + "ALGORITHMS_PID_RESET_TOOLTIP": "Vynuluje integrál a historii derivace PID regulátoru" +} diff --git a/algorithms/blocks/translations/en.lang.json b/algorithms/blocks/translations/en.lang.json new file mode 100644 index 0000000..a8a07df --- /dev/null +++ b/algorithms/blocks/translations/en.lang.json @@ -0,0 +1,16 @@ +{ + "ALGORITHMS_NAME": "Algorithms", + "ALGORITHMS_DESCRIPTION": "Algorithm blocks for data processing and filtering.", + "ALGORITHMS_LABEL_EMA": "Exponential Moving Average (EMA)", + "ALGORITHMS_LABEL_PID": "PID Controller", + "ALGORITHMS_EMA_CREATE_MESSAGE0": "create EMA filter $[CONSTRUCTED_VAR_NAME] with shift $[SHIFT]", + "ALGORITHMS_EMA_CREATE_TOOLTIP": "Create an Exponential Moving Average filter instance", + "ALGORITHMS_EMA_APPLY_MESSAGE0": "apply EMA filter $[FILTER_INSTANCE] to value $[VALUE]", + "ALGORITHMS_EMA_APPLY_TOOLTIP": "Apply filter to input value and return smoothed output", + "ALGORITHMS_PID_CREATE_MESSAGE0": "create PID controller $[CONSTRUCTED_VAR_NAME]\nkp $[KP] ki $[KI] kd $[KD]\noutput min $[OUTPUT_MIN] max $[OUTPUT_MAX]\nintegral min $[INTEGRAL_MIN] max $[INTEGRAL_MAX]", + "ALGORITHMS_PID_CREATE_TOOLTIP": "Create a PID controller instance with gains and limits", + "ALGORITHMS_PID_UPDATE_MESSAGE0": "PID $[PID_INSTANCE] update\nsetpoint $[SETPOINT] measurement $[MEASUREMENT] dt $[DT] s", + "ALGORITHMS_PID_UPDATE_TOOLTIP": "Compute PID output from setpoint, measurement, and loop time", + "ALGORITHMS_PID_RESET_MESSAGE0": "reset PID controller $[PID_INSTANCE]", + "ALGORITHMS_PID_RESET_TOOLTIP": "Reset PID integral and derivative history" +} diff --git a/algorithms/package.json b/algorithms/package.json new file mode 100644 index 0000000..4cf7ca6 --- /dev/null +++ b/algorithms/package.json @@ -0,0 +1,25 @@ +{ + "name": "algorithms", + "version": "0.1.0", + "author": "Jakub Andrysek", + "license": "MIT", + "description": "Collection of algorithmic functions (Moving Average and PID controller)", + "type": "module", + "main": "dist/js/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/algorithms/src/index.ts b/algorithms/src/index.ts new file mode 100644 index 0000000..5720630 --- /dev/null +++ b/algorithms/src/index.ts @@ -0,0 +1,93 @@ +/** + * High-performance Exponential Moving Average (EMA) filter with rounding. + * Uses fixed-point arithmetic for fast integer operations. + * Note: JavaScript bitwise operations work only with 32-bit signed integers. + * Maximum safe storage value is 2,147,483,647. + * @param shift Smoothing coefficient (window ≈ 2^shift samples) + * @returns Filter function that takes a sample and returns the smoothed value + */ +export const createEmaFilter = (shift: number) => { + let storage: number | null = null; + const offset = 1 << (shift - 1); // Half the divisor for rounding + + return (newValue: number): number => { + if (storage === null) { + storage = newValue << shift; + return newValue; + } + + storage = storage - (storage >> shift) + newValue; + return (storage + offset) >> shift; + }; +}; + +export type PidControllerConfig = { + kp: number; + ki: number; + kd: number; + outputMin?: number; + outputMax?: number; + integralMin?: number; + integralMax?: number; +}; + +export type PidControllerInput = { + setpoint: number; + measurement: number; + dt: number; +}; + +export type PidController = { + update: (input: PidControllerInput) => number; + reset: () => void; +}; + +const clamp = (value: number, min: number, max: number) => { + if (value < min) return min; + if (value > max) return max; + return value; +}; + +/** + * PID controller for closed-loop control. + * @param config PID gains and optional output/integral limits + * @returns Controller with update() and reset() + */ +export const createPidController = ( + config: PidControllerConfig, +): PidController => { + const { + kp, + ki, + kd, + outputMin = Number.NEGATIVE_INFINITY, + outputMax = Number.POSITIVE_INFINITY, + integralMin = Number.NEGATIVE_INFINITY, + integralMax = Number.POSITIVE_INFINITY, + } = config; + + let integral = 0; + let previousError: number | null = null; + + return { + update: ({ setpoint, measurement, dt }: PidControllerInput): number => { + if (!Number.isFinite(dt) || dt <= 0) { + throw new Error("PID dt must be a positive finite number"); + } + + const error = setpoint - measurement; + integral = clamp(integral + error * dt, integralMin, integralMax); + + const derivative = + previousError === null ? 0 : (error - previousError) / dt; + previousError = error; + + const output = kp * error + ki * integral + kd * derivative; + return clamp(output, outputMin, outputMax); + }, + reset: () => { + integral = 0; + previousError = null; + }, + }; +}; diff --git a/algorithms/tsconfig.json b/algorithms/tsconfig.json new file mode 100644 index 0000000..3a3fbba --- /dev/null +++ b/algorithms/tsconfig.json @@ -0,0 +1,14 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true + } +} diff --git a/basic/.gitignore b/basic/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/basic/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/basic/README.md b/basic/README.md new file mode 100644 index 0000000..88224e5 --- /dev/null +++ b/basic/README.md @@ -0,0 +1,4 @@ +# Basic setup and loop utils with JacLy definitions + +This package provides a basic functionality to run "multiple" JS code concurrently. +Package also includes JacLy definitions for basic setup, infinite loops, and log commands. diff --git a/basic/blocks/basic.jacly.json b/basic/blocks/basic.jacly.json new file mode 100644 index 0000000..0224976 --- /dev/null +++ b/basic/blocks/basic.jacly.json @@ -0,0 +1,254 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "category": "basic", + "name": "%T%", + "description": "%T%", + "colour": "#1e90ff", + "icon": "LayoutGrid", + "priority": 10, + "import": ["import * as basic from \"basic\";"], + "contents": [ + { + "kind": "block", + "type": "basic_onStart", + "message0": "%T%", + "args0": [ + { + "type": "input_statement", + "name": "CODE", + "check": "Function" + } + ], + "isProgramStart": true, + "tooltip": "%T%", + "code": "basic.onStart(async () => {\n$[CODE]\n});" + }, + { + "kind": "block", + "type": "basic_forever", + "message0": "%T%", + "args0": [ + { + "type": "input_statement", + "name": "CODE", + "check": "Function", + "block": { + "type": "basic_sleep", + "inputs": { + "TIME": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 500 + } + } + } + } + } + } + ], + "import": ["import * as basic from \"basic\";"], + "isProgramStart": true, + "tooltip": "%T%", + "code": "basic.forever(async () => {\n$[CODE]\n});" + }, + { + "kind": "block", + "type": "basic_console_log", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "MODE", + "options": [ + ["%LOG%", "log"], + ["%WARN%", "warn"], + ["%ERROR%", "error"] + ] + }, + { + "type": "input_value", + "name": "TEXT" + } + ], + "inputs": { + "TEXT": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "" + } + } + } + }, + "tooltip": "%T%", + "code": "console.$[MODE]($[TEXT]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "basic_console_keyval", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "KEY", + "shadow": { + "type": "text", + "fields": { + "KEY": "key" + } + } + }, + { + "type": "input_value", + "name": "VALUE", + "shadow": { + "type": "text", + "fields": { + "VALUE": "value" + } + } + } + ], + "tooltip": "%T%", + "code": "console.log($[KEY] + \": \" + ($[VALUE]));", + "inputsInline": true, + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "basic_sleep", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "TIME", + "check": "Number" + } + ], + "inputs": { + "TIME": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1000 + } + } + } + }, + "tooltip": "%T%", + "code": "await sleep($[TIME]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%INTERVALS%" + }, + { + "kind": "block", + "type": "basic_set_interval", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "interval_?" + }, + { + "type": "input_statement", + "name": "CODE", + "check": "Function" + }, + { + "type": "input_value", + "name": "TIME", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1000 + } + } + } + ], + "constructs": "interval", + "tooltip": "%T%", + "code": "let $[CONSTRUCTED_VAR_NAME] = setInterval(async () => {\n$[CODE]\n}, $[TIME]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "basic_clear_interval", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "INTERVAL_INSTANCE", + "instanceof": "interval" + } + ], + "tooltip": "%T%", + "code": "clearInterval($[INTERVAL_INSTANCE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%TIMEOUTS%" + }, + { + "kind": "block", + "type": "basic_set_timeout", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "timeout_?" + }, + { + "type": "input_statement", + "name": "CODE", + "check": "Function" + }, + { + "type": "input_value", + "name": "TIME", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1000 + } + } + } + ], + "constructs": "timeout", + "tooltip": "%T%", + "code": "let $[CONSTRUCTED_VAR_NAME] = setTimeout(async () => {\n$[CODE]\n}, $[TIME]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "basic_clear_timeout", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "TIMEOUT_INSTANCE", + "instanceof": "timeout" + } + ], + "tooltip": "%T%", + "code": "clearTimeout($[TIMEOUT_INSTANCE]);", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/basic/blocks/basic_ext.jacly.json b/basic/blocks/basic_ext.jacly.json new file mode 100644 index 0000000..b29046e --- /dev/null +++ b/basic/blocks/basic_ext.jacly.json @@ -0,0 +1,29 @@ +{ + "category": "basic_ext", + "parentCategory": "basic", + "name": "%T%", + "description": "%T%", + "colour": "#1e90ff", + "icon": "LayoutGrid", + "priority": 10, + "contents": [ + { + "kind": "block", + "type": "basic_promise_all", + "message0": "%T%", + "tooltip": "%T%", + "previousStatement": null, + "nextStatement": null, + "mutator": "basic_promise_all_mutator" + }, + { + "kind": "block", + "type": "basic_yield", + "message0": "%T%", + "tooltip": "%T%", + "code": "await sleep(0);", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/basic/blocks/translations/cs.lang.json b/basic/blocks/translations/cs.lang.json new file mode 100644 index 0000000..04c4b42 --- /dev/null +++ b/basic/blocks/translations/cs.lang.json @@ -0,0 +1,48 @@ +{ + "BASIC_NAME": "Základní", + "BASIC_DESCRIPTION": "Základní bloky pro řízení úloh.", + + "BASIC_ONSTART_MESSAGE0": "při spuštění", + "BASIC_ONSTART_TOOLTIP": "Spustí kód při startu programu", + "BASIC_FOREVER_MESSAGE0": "opakuj stále", + "BASIC_FOREVER_TOOLTIP": "Spustí kód opakovaně na pozadí", + "BASIC_CONSOLE_LOG_MESSAGE0": "konzole $[MODE] $[TEXT]", + "BASIC_CONSOLE_LOG_ARGS0_FIELD_DROPDOWN_LOG": "log", + "BASIC_CONSOLE_LOG_ARGS0_FIELD_DROPDOWN_WARN": "varování", + "BASIC_CONSOLE_LOG_ARGS0_FIELD_DROPDOWN_ERROR": "chyba", + "BASIC_CONSOLE_LOG_TOOLTIP": "Vypíše hodnotu do konzole", + + "BASIC_CONSOLE_KEYVAL_MESSAGE0": "konzole klíč $[KEY] hodnota $[VALUE]", + "BASIC_CONSOLE_KEYVAL_TOOLTIP": "Vypíše klíč a hodnotu do konzole", + + "BASIC_SLEEP_MESSAGE0": "čekej (ms) $[TIME]", + "BASIC_SLEEP_TOOLTIP": "Pozastaví provádění na zadanou dobu v milisekundách", + + "BASIC_LABEL_INTERVALS": "Intervaly", + + "BASIC_SET_INTERVAL_MESSAGE0": "nastavit interval $[CONSTRUCTED_VAR_NAME]\n proveď $[CODE] každých $[TIME] ms", + "BASIC_SET_INTERVAL_TOOLTIP": "Nastaví opakující se interval pro spouštění kódu každých zadaných milisekund", + + "BASIC_CLEAR_INTERVAL_MESSAGE0": "zrušit interval $[INTERVAL_INSTANCE]", + "BASIC_CLEAR_INTERVAL_TOOLTIP": "Zruší zadaný interval, aby se dále nespouštěl", + + "BASIC_LABEL_TIMEOUTS": "Časovače", + "BASIC_SET_TIMEOUT_MESSAGE0": "nastavit časovač $[CONSTRUCTED_VAR_NAME]\n proveď $[CODE] po $[TIME] ms", + "BASIC_SET_TIMEOUT_TOOLTIP": "Nastaví časovač pro spuštění kódu po zadané době v milisekundách", + + "BASIC_CLEAR_TIMEOUT_MESSAGE0": "zrušit časovač $[TIMEOUT_INSTANCE]", + "BASIC_CLEAR_TIMEOUT_TOOLTIP": "Zruší zadaný časovač, aby se nespustil", + + "BASIC_EXT_NAME": "Základní rozšířené", + "BASIC_EXT_DESCRIPTION": "Rozšířené základní bloky pro řízení úloh.", + + "BASIC_PROMISE_ALL_MESSAGE0": "spusť paralelně", + "BASIC_PROMISE_ALL_TOOLTIP": "Spustí více úloh paralelně a počká na dokončení všech", + "BASIC_PROMISE_ALL_CONTAINER": "paralelní úlohy", + "BASIC_PROMISE_ALL_CONTAINER_TOOLTIP": "Přidej, odeber nebo změň pořadí úloh", + "BASIC_PROMISE_ALL_ITEM": "úloha", + "BASIC_PROMISE_ALL_ITEM_TOOLTIP": "Přidej slot pro paralelní úlohu", + + "BASIC_YIELD_MESSAGE0": "předej řízení", + "BASIC_YIELD_TOOLTIP": "Předá řízení ostatním úlohám" +} diff --git a/basic/blocks/translations/en.lang.json b/basic/blocks/translations/en.lang.json new file mode 100644 index 0000000..015864c --- /dev/null +++ b/basic/blocks/translations/en.lang.json @@ -0,0 +1,49 @@ +{ + "BASIC_NAME": "Basic", + "BASIC_DESCRIPTION": "Basic task control blocks.", + + "BASIC_ONSTART_MESSAGE0": "on start", + "BASIC_ONSTART_TOOLTIP": "Run code when the program starts", + "BASIC_FOREVER_MESSAGE0": "forever", + "BASIC_FOREVER_TOOLTIP": "Run code repeatedly in the background", + + "BASIC_CONSOLE_LOG_MESSAGE0": "console $[MODE] $[TEXT]", + "BASIC_CONSOLE_LOG_ARGS0_FIELD_DROPDOWN_LOG": "log", + "BASIC_CONSOLE_LOG_ARGS0_FIELD_DROPDOWN_WARN": "warn", + "BASIC_CONSOLE_LOG_ARGS0_FIELD_DROPDOWN_ERROR": "error", + "BASIC_CONSOLE_LOG_TOOLTIP": "Log a value to the console", + + "BASIC_CONSOLE_KEYVAL_MESSAGE0": "console key $[KEY] value $[VALUE]", + "BASIC_CONSOLE_KEYVAL_TOOLTIP": "Log a key-value pair to the console", + + "BASIC_SLEEP_MESSAGE0": "sleep (ms) $[TIME]", + "BASIC_SLEEP_TOOLTIP": "Pause execution for the specified time in milliseconds", + + "BASIC_LABEL_INTERVALS": "Intervals", + + "BASIC_SET_INTERVAL_MESSAGE0": "set interval name $[CONSTRUCTED_VAR_NAME]\n do $[CODE] every $[TIME] ms", + "BASIC_SET_INTERVAL_TOOLTIP": "Set a repeating interval to run code every specified time in milliseconds", + + "BASIC_CLEAR_INTERVAL_MESSAGE0": "clear interval name $[INTERVAL_INSTANCE]", + "BASIC_CLEAR_INTERVAL_TOOLTIP": "Clear the specified interval to stop it from running further", + + "BASIC_LABEL_TIMEOUTS": "Timeouts", + "BASIC_SET_TIMEOUT_MESSAGE0": "set timeout name $[CONSTRUCTED_VAR_NAME]\n do $[CODE] after $[TIME] ms", + "BASIC_SET_TIMEOUT_TOOLTIP": "Set a timeout to run code after the specified time in milliseconds", + + "BASIC_CLEAR_TIMEOUT_MESSAGE0": "clear timeout name $[TIMEOUT_INSTANCE]", + "BASIC_CLEAR_TIMEOUT_TOOLTIP": "Clear the specified timeout to stop it from running further", + + "BASIC_EXT_NAME": "Basic Extended", + "BASIC_EXT_DESCRIPTION": "Basic extended task control blocks.", + + "BASIC_PROMISE_ALL_MESSAGE0": "promise all", + "BASIC_PROMISE_ALL_TOOLTIP": "Promise all tasks and wait for all of them to complete", + "BASIC_PROMISE_ALL_CONTAINER": "parallel tasks", + "BASIC_PROMISE_ALL_CONTAINER_TOOLTIP": "Add, remove, or reorder task slots", + "BASIC_PROMISE_ALL_ITEM": "task", + "BASIC_PROMISE_ALL_ITEM_TOOLTIP": "Add a parallel task slot", + + "BASIC_YIELD_MESSAGE0": "yield", + "BASIC_YIELD_TOOLTIP": "Yield control to other tasks" +} diff --git a/basic/package.json b/basic/package.json new file mode 100644 index 0000000..662a817 --- /dev/null +++ b/basic/package.json @@ -0,0 +1,28 @@ +{ + "name": "basic", + "version": "0.1.0", + "author": "Jakub Andrysek", + "license": "MIT", + "description": "Jacly basic blocks", + "type": "module", + "main": "dist/js/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "dependencies": { + "@types/jaculus": "0.1.0" + }, + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/basic/src/index.ts b/basic/src/index.ts new file mode 100644 index 0000000..8271890 --- /dev/null +++ b/basic/src/index.ts @@ -0,0 +1,14 @@ +export type Handler = () => Promise; + +export function forever(fn: Handler): void { + (async () => { + while (true) { + await fn(); + await sleep(0); + } + })(); +} + +export function onStart(fn: Handler): void { + fn(); +} diff --git a/empty-template/tsconfig.json b/basic/tsconfig.json similarity index 85% rename from empty-template/tsconfig.json rename to basic/tsconfig.json index af99d99..ab82ab7 100644 --- a/empty-template/tsconfig.json +++ b/basic/tsconfig.json @@ -1,5 +1,5 @@ { - "include": [ "src" ], + "include": ["src"], "compilerOptions": { "target": "es2023", "module": "nodenext", @@ -10,6 +10,6 @@ "outDir": "dist/js", "rootDir": "src", "strict": true, - "baseUrl": ".", + "types": ["jaculus"] } } diff --git a/bq2589/.gitignore b/bq2589/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/bq2589/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/bq2589/README.md b/bq2589/README.md new file mode 100644 index 0000000..b706734 --- /dev/null +++ b/bq2589/README.md @@ -0,0 +1 @@ +# BQ2589 battery charger control library diff --git a/bq2589/blocks/bq2589.jacly.json b/bq2589/blocks/bq2589.jacly.json new file mode 100644 index 0000000..067b12b --- /dev/null +++ b/bq2589/blocks/bq2589.jacly.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "category": "bq2589", + "parentCategory": "peripherals", + "name": "%T%", + "description": "%T%", + "colour": "#AC8FBC", + "icon": "BatteryCharging", + "priority": 170, + "import": ["import { BQ2589 } from \"bq2589\";"], + "contents": [ + { + "kind": "block", + "type": "bq2589_constructor", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "bq2589_?" + }, + { + "type": "field_dropdown", + "name": "I2C_INSTANCE", + "instanceof": "i2c_inst" + } + ], + "constructs": "bq2589", + "tooltip": "%T%", + "code": "$[CONSTRUCTED_VAR_NAME] = new BQ2589($[I2C_INSTANCE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "bq2589_read_adc_oneshot", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "BQ2589_INSTANCE", + "instanceof": "bq2589" + } + ], + "tooltip": "%T%", + "inputsInline": true, + "output": "Number", + "code": "(await $[BQ2589_INSTANCE].readADCOneshot())" + } + ] +} diff --git a/bq2589/blocks/translations/cs.lang.json b/bq2589/blocks/translations/cs.lang.json new file mode 100644 index 0000000..f4e840d --- /dev/null +++ b/bq2589/blocks/translations/cs.lang.json @@ -0,0 +1,10 @@ +{ + "BQ2589_NAME": "Nabíječka baterií (BQ2589)", + "BQ2589_DESCRIPTION": "Knihovna pro řízení nabíječky baterií BQ2589", + + "BQ2589_CONSTRUCTOR_MESSAGE0": "vytvoř $[CONSTRUCTED_VAR_NAME] nabíječku (BQ2589) na I2C sběrnici $[I2C_INSTANCE]", + "BQ2589_CONSTRUCTOR_TOOLTIP": "Vytvoří nový BQ2589 nabíječka baterií na I2C sběrnici", + + "BQ2589_READ_ADC_ONESHOT_MESSAGE0": "přečíst napětí (V) z $[BQ2589_INSTANCE] (jednorázově)", + "BQ2589_READ_ADC_ONESHOT_TOOLTIP": "Přečte hodnotu ADC napětí z BQ2589 v jednorázovém režimu (čeká na konverzi)" +} diff --git a/bq2589/blocks/translations/en.lang.json b/bq2589/blocks/translations/en.lang.json new file mode 100644 index 0000000..b2f27d8 --- /dev/null +++ b/bq2589/blocks/translations/en.lang.json @@ -0,0 +1,10 @@ +{ + "BQ2589_NAME": "Battery charger (BQ2589)", + "BQ2589_DESCRIPTION": "BQ2589 battery charger control library", + + "BQ2589_CONSTRUCTOR_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] battery charger (BQ2589) on I2C bus $[I2C_INSTANCE]", + "BQ2589_CONSTRUCTOR_TOOLTIP": "Create a new BQ2589 battery charger instance on the I2C bus", + + "BQ2589_READ_ADC_ONESHOT_MESSAGE0": "read voltage (V) from $[BQ2589_INSTANCE] (oneshot)", + "BQ2589_READ_ADC_ONESHOT_TOOLTIP": "Read the ADC voltage value from BQ2589 in oneshot mode (waits for conversion)" +} diff --git a/bq2589/package.json b/bq2589/package.json new file mode 100644 index 0000000..fcd3d7b --- /dev/null +++ b/bq2589/package.json @@ -0,0 +1,28 @@ +{ + "name": "bq2589", + "version": "0.1.0", + "author": "Jakub Andrysek", + "license": "MIT", + "description": "BQ2589 battery charger control library", + "type": "module", + "main": "dist/js/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "dependencies": { + "@types/jaculus": "0.1.0" + }, + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/bq2589/src/index.ts b/bq2589/src/index.ts new file mode 100644 index 0000000..1e3d4b3 --- /dev/null +++ b/bq2589/src/index.ts @@ -0,0 +1,68 @@ +import * as i2c from "i2c"; + +const ADDRESS = 0x6a; + +export class BQ2589 { + readonly bus: i2c.I2C; + + constructor(bus: i2c.I2C) { + this.bus = bus; + } + + private setBit(address: number, reg: number, bit: number, value: boolean) { + this.bus.writeTo(address, reg); + let data = this.bus.readFrom(address, 1)[0]; + data = value ? data | (1 << bit) : data & ~(1 << bit); + this.bus.writeTo(address, [reg, data]); + } + + startADCContinuous() { + this.setBit(ADDRESS, 2, 6, true); + } + + startADCOneshot() { + this.setBit(ADDRESS, 2, 7, true); + } + + readADC() { + this.bus.writeTo(ADDRESS, 0x0f); + let value = this.bus.readFrom(ADDRESS, 1)[0]; + return (value * 20 + 2304) / 1000; + } + + resetWatchdog() { + this.setBit(ADDRESS, 3, 6, true); + } + + async readADCOneshot(): Promise { + this.startADCOneshot(); + await sleep(50); + return this.readADC(); + } + + startBatteryChecker( + setStatus: (status: number) => void, + batteryThreshold: number = 3.2, + intervalMs: number = 10000, + ) { + let blinkTimer: number | undefined = undefined; + let blinkState = false; + + setInterval(async () => { + const u = await this.readADCOneshot(); + if (u < batteryThreshold && blinkTimer === undefined) { + blinkTimer = setInterval(() => { + blinkState = !blinkState; + setStatus(blinkState ? 1 : 0); + if (blinkState) { + console.log(`Battery low: ${u.toFixed(2)} V`); + } + }, 500); + } else if (u >= batteryThreshold && blinkTimer !== undefined) { + clearInterval(blinkTimer); + setStatus(0); + blinkTimer = undefined; + } + }, intervalMs); + } +} diff --git a/bq2589/tsconfig.json b/bq2589/tsconfig.json new file mode 100644 index 0000000..ab82ab7 --- /dev/null +++ b/bq2589/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true, + "types": ["jaculus"] + } +} diff --git a/button/.gitignore b/button/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/button/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/button/README.md b/button/README.md new file mode 100644 index 0000000..23d3115 --- /dev/null +++ b/button/README.md @@ -0,0 +1 @@ +# Button event library diff --git a/button/blocks/button.jacly.json b/button/blocks/button.jacly.json new file mode 100644 index 0000000..b7f5cb8 --- /dev/null +++ b/button/blocks/button.jacly.json @@ -0,0 +1,164 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "category": "button", + "parentCategory": "peripherals", + "name": "%T%", + "description": "%T%", + "colour": "#4F46E5", + "icon": "MousePointerClick", + "priority": 140, + "import": ["import { Button } from \"button\";"], + "contents": [ + { + "kind": "label", + "text": "%SETUP%" + }, + { + "kind": "block", + "type": "button_constructor", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "button_?" + }, + { + "type": "input_value", + "name": "PIN", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 5 + } + } + }, + { + "type": "input_value", + "name": "DOUBLE_CLICK_LIMIT", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 250 + } + } + } + ], + "constructs": "button", + "tooltip": "%T%", + "inputsInline": true, + "code": "$[CONSTRUCTED_VAR_NAME] = new Button($[PIN], $[DOUBLE_CLICK_LIMIT]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%EVENTS%" + }, + { + "kind": "block", + "type": "button_on", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "BUTTON_INSTANCE", + "instanceof": "button" + }, + { + "type": "field_dropdown", + "name": "EVENT", + "options": [ + ["%PRESS%", "\"press\""], + ["%RELEASE%", "\"release\""], + ["%DOUBLE_CLICK%", "\"doubleClick\""] + ] + }, + { + "type": "input_statement", + "name": "CODE", + "check": "Function" + } + ], + "isProgramStart": true, + "tooltip": "%T%", + "code": "$[BUTTON_INSTANCE].on($[EVENT], async function() {\n$[CODE]\n});" + }, + { + "kind": "block", + "type": "button_on_click", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "BUTTON_INSTANCE", + "instanceof": "button" + }, + { + "type": "input_statement", + "name": "CODE", + "check": "Function" + } + ], + "callbackVars": [ + { + "identifier": "duration", + "message": "%DURATION%", + "type": "Number", + "codeName": "duration" + } + ], + "isProgramStart": true, + "tooltip": "%T%", + "code": "$[BUTTON_INSTANCE].on(\"click\", async function(duration) {\n$[CODE]\n});" + }, + { + "kind": "block", + "type": "button_off", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "BUTTON_INSTANCE", + "instanceof": "button" + }, + { + "type": "field_dropdown", + "name": "EVENT", + "options": [ + ["%PRESS%", "\"press\""], + ["%RELEASE%", "\"release\""], + ["%CLICK%", "\"click\""], + ["%DOUBLE_CLICK%", "\"doubleClick\""] + ] + } + ], + "tooltip": "%T%", + "code": "$[BUTTON_INSTANCE].off($[EVENT]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%OTHER%" + }, + { + "kind": "block", + "type": "button_close", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "BUTTON_INSTANCE", + "instanceof": "button" + } + ], + "tooltip": "%T%", + "code": "$[BUTTON_INSTANCE].close();", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/button/blocks/translations/cs.lang.json b/button/blocks/translations/cs.lang.json new file mode 100644 index 0000000..861c72d --- /dev/null +++ b/button/blocks/translations/cs.lang.json @@ -0,0 +1,32 @@ +{ + "BUTTON_NAME": "Tlačítko", + "BUTTON_DESCRIPTION": "Knihovna pro obsluhu událostí tlačítka", + "BUTTON_LABEL_SETUP": "Nastavení (je potřeba pro použití ostatních bloků tlačítka)", + "BUTTON_LABEL_EVENTS": "Události", + "BUTTON_LABEL_OTHER": "Ostatní operace s tlačítkem", + + "BUTTON_CONSTRUCTOR_MESSAGE0": "vytvořit $[CONSTRUCTED_VAR_NAME] tlačítko na pinu $[PIN] s limitem dvojkliku $[DOUBLE_CLICK_LIMIT] ms", + "BUTTON_CONSTRUCTOR_TOOLTIP": "Vytvoří novou instanci tlačítka na zadaném GPIO pinu", + + "BUTTON_ON_MESSAGE0": "při $[EVENT] na $[BUTTON_INSTANCE] proveď\n$[CODE]", + "BUTTON_ON_TOOLTIP": "Zaregistruje obsluhu pro vybranou událost tlačítka", + + "BUTTON_ON_CLICK_MESSAGE0": "při kliknutí na $[BUTTON_INSTANCE] proveď\n$[CODE]", + "BUTTON_ON_CLICK_TOOLTIP": "Zaregistruje obsluhu pro událost kliknutí. Proměnná doba trvání obsahuje délku stisknutí v milisekundách.", + "BUTTON_ON_CLICK_CALLBACK_DURATION": "doba trvání (ms)", + + "BUTTON_OFF_MESSAGE0": "odstranit obsluhu $[EVENT] z $[BUTTON_INSTANCE]", + "BUTTON_OFF_TOOLTIP": "Odstraní zaregistrovanou obsluhu pro vybranou událost tlačítka", + + "BUTTON_CLOSE_MESSAGE0": "zavřít $[BUTTON_INSTANCE]", + "BUTTON_CLOSE_TOOLTIP": "Odregistruje GPIO obsluhy událostí pro tlačítko", + + "BUTTON_ON_ARGS0_FIELD_DROPDOWN_PRESS": "stisknutí", + "BUTTON_ON_ARGS0_FIELD_DROPDOWN_RELEASE": "puštění", + "BUTTON_ON_ARGS0_FIELD_DROPDOWN_DOUBLE_CLICK": "dvojité kliknutí", + + "BUTTON_OFF_ARGS0_FIELD_DROPDOWN_PRESS": "stisknutí", + "BUTTON_OFF_ARGS0_FIELD_DROPDOWN_RELEASE": "puštění", + "BUTTON_OFF_ARGS0_FIELD_DROPDOWN_CLICK": "kliknutí", + "BUTTON_OFF_ARGS0_FIELD_DROPDOWN_DOUBLE_CLICK": "dvojité kliknutí" +} diff --git a/button/blocks/translations/en.lang.json b/button/blocks/translations/en.lang.json new file mode 100644 index 0000000..c35bf3a --- /dev/null +++ b/button/blocks/translations/en.lang.json @@ -0,0 +1,32 @@ +{ + "BUTTON_NAME": "Button", + "BUTTON_DESCRIPTION": "Event-driven button handler library", + "BUTTON_LABEL_SETUP": "Setup (this is required to use other Button blocks)", + "BUTTON_LABEL_EVENTS": "Events", + "BUTTON_LABEL_OTHER": "Other Button operations", + + "BUTTON_CONSTRUCTOR_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] button on pin $[PIN] with double-click limit $[DOUBLE_CLICK_LIMIT] ms", + "BUTTON_CONSTRUCTOR_TOOLTIP": "Create a new Button instance on the specified GPIO pin", + + "BUTTON_ON_MESSAGE0": "on $[BUTTON_INSTANCE] $[EVENT] do\n$[CODE]", + "BUTTON_ON_TOOLTIP": "Register a callback for the selected button event", + + "BUTTON_ON_CLICK_MESSAGE0": "on $[BUTTON_INSTANCE] click do\n$[CODE]", + "BUTTON_ON_CLICK_TOOLTIP": "Register a callback for the click event. The duration variable contains the press duration in milliseconds.", + "BUTTON_ON_CLICK_CALLBACK_DURATION": "duration (ms)", + + "BUTTON_OFF_MESSAGE0": "remove $[EVENT] handler from $[BUTTON_INSTANCE]", + "BUTTON_OFF_TOOLTIP": "Remove the registered callback for the selected button event", + + "BUTTON_CLOSE_MESSAGE0": "close $[BUTTON_INSTANCE]", + "BUTTON_CLOSE_TOOLTIP": "Unregister GPIO event handlers for the button", + + "BUTTON_ON_ARGS0_FIELD_DROPDOWN_PRESS": "press", + "BUTTON_ON_ARGS0_FIELD_DROPDOWN_RELEASE": "release", + "BUTTON_ON_ARGS0_FIELD_DROPDOWN_DOUBLE_CLICK": "double click", + + "BUTTON_OFF_ARGS0_FIELD_DROPDOWN_PRESS": "press", + "BUTTON_OFF_ARGS0_FIELD_DROPDOWN_RELEASE": "release", + "BUTTON_OFF_ARGS0_FIELD_DROPDOWN_CLICK": "click", + "BUTTON_OFF_ARGS0_FIELD_DROPDOWN_DOUBLE_CLICK": "double click" +} diff --git a/button/package.json b/button/package.json new file mode 100644 index 0000000..dd831d0 --- /dev/null +++ b/button/package.json @@ -0,0 +1,28 @@ +{ + "name": "button", + "version": "0.1.0", + "author": "Milan Novak", + "license": "MIT", + "description": "Button event library", + "type": "module", + "main": "dist/js/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "dependencies": { + "@types/jaculus": "0.1.0" + }, + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/button/src/index.ts b/button/src/index.ts new file mode 100644 index 0000000..06769e5 --- /dev/null +++ b/button/src/index.ts @@ -0,0 +1,142 @@ +import * as gpio from "gpio"; + +type ButtonEventCallbacks = { + press?: () => void; + release?: () => void; + click?: (duration?: number) => void; + doubleClick?: () => void; +}; + +type ButtonEvent = keyof ButtonEventCallbacks; + +/** + * Event-driven button handler. + * Only a single callback can be registered for each event. + */ +export class Button { + /** Maximum time (ms) between two clicks to be considered a double click. */ + private doubleClickLimit: number; + + /** Timestamp of the last button press. */ + private lastFallingTime: number; + + /** Timeout used to delay click detection while waiting for a possible second click. */ + private clickTimeout: number; + + /** GPIO pin connected to the button. */ + private buttonPin: number; + + /** Registered event callbacks. */ + private eventCallbacks: ButtonEventCallbacks; + + /** + * Creates a new button instance. + * + * @param buttonPin GPIO pin connected to the button. + * @param doubleClickLimit Maximum interval in milliseconds between two clicks + * to recognize them as a double click. Defaults to 250 ms. + */ + constructor(buttonPin: number, doubleClickLimit: number = 250) { + gpio.pinMode(buttonPin, gpio.PinMode.INPUT); + + this.doubleClickLimit = doubleClickLimit; + this.lastFallingTime = 0; + this.clickTimeout = 0; + this.buttonPin = buttonPin; + this.eventCallbacks = {}; + + gpio.on("falling", buttonPin, this.handleFallingEvent.bind(this)); + gpio.on("rising", buttonPin, this.handleRisingEvent.bind(this)); + } + + /** + * Unregisters GPIO event handlers associated with this button. + */ + close() { + gpio.off("falling", this.buttonPin); + gpio.off("rising", this.buttonPin); + } + + /** + * Registers a callback for a button event. + + * Only one callback may be registered per event. + * + * @param event Event to listen for. + * @param callback Function invoked when the event occurs. For the `click` event, the callback receives the duration of the button press in milliseconds. + * + * @throws Error if a callback for the event is already registered. + */ + on( + event: E, + callback: ButtonEventCallbacks[E], + ): void { + if (this.eventCallbacks[event]) { + throw new Error( + `Event ${event} already has a callback registered.`, + ); + } + + this.eventCallbacks[event] = callback; + } + + /** + * Removes the callback associated with a button event. + * + * @param event Event whose callback should be removed. + * + * @throws Error if no callback is registered for the event. + */ + off(event: ButtonEvent) { + if (!this.eventCallbacks[event]) { + throw new Error( + `Event ${event} does not have a callback registered.`, + ); + } + + delete this.eventCallbacks[event]; + } + + /** + * Handles the GPIO falling-edge event. + * + * Triggers the `press` callback and records the press timestamp. + * + * @param info GPIO event information. + */ + private handleFallingEvent(info: gpio.EventInfo) { + this.eventCallbacks["press"]?.(); + + this.lastFallingTime = info.timestamp.millis(); + } + + /** + * Handles the GPIO rising-edge event. + * + * Triggers the `release` callback. If another click occurs within the + * configured double-click interval, a `doubleClick` event is emitted. + * Otherwise, a delayed `click` event is emitted. + * + * @param info GPIO event information. + */ + private handleRisingEvent(info: gpio.EventInfo) { + this.eventCallbacks["release"]?.(); + + if (this.lastFallingTime === 0) { + return; + } + + if (this.clickTimeout) { + clearTimeout(this.clickTimeout); + this.eventCallbacks["doubleClick"]?.(); + this.clickTimeout = 0; + } else { + this.clickTimeout = setTimeout(() => { + this.eventCallbacks["click"]?.( + info.timestamp.millis() - this.lastFallingTime, + ); + this.clickTimeout = 0; + }, this.doubleClickLimit); + } + } +} diff --git a/button/tsconfig.json b/button/tsconfig.json new file mode 100644 index 0000000..ab82ab7 --- /dev/null +++ b/button/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true, + "types": ["jaculus"] + } +} diff --git a/colors/.gitignore b/colors/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/colors/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/colors/README.md b/colors/README.md new file mode 100644 index 0000000..46a57c6 --- /dev/null +++ b/colors/README.md @@ -0,0 +1 @@ +# Colors utility functions and types diff --git a/colors/blocks/colors.jacly.json b/colors/blocks/colors.jacly.json new file mode 100644 index 0000000..d0db8b5 --- /dev/null +++ b/colors/blocks/colors.jacly.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "category": "colors", + "name": "%T%", + "description": "%T%", + "colour": "#A58F5C", + "icon": "Palette", + "priority": 135, + "import": ["import * as colors from \"colors\";"], + "contents": [ + { + "kind": "block", + "type": "dynamic_color_rgb" + }, + { + "kind": "block", + "type": "colors_rgb_to_string", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "RGB_VALUE", + "check": "Color" + } + ], + "output": "String", + "tooltip": "%T%", + "code": "colors.rgbToString($[RGB_VALUE])" + } + ] +} diff --git a/colors/blocks/translations/cs.lang.json b/colors/blocks/translations/cs.lang.json new file mode 100644 index 0000000..34fffe6 --- /dev/null +++ b/colors/blocks/translations/cs.lang.json @@ -0,0 +1,24 @@ +{ + "COLORS_NAME": "Barvy", + "COLORS_DESCRIPTION": "Bloky pro práci s barvami.", + "DYNAMIC_COLOR_RGB_MESSAGE0": "Barva podle", + "DYNAMIC_COLOR_RGB_DROPDOWN_NAMES": "názvu", + "DYNAMIC_COLOR_RGB_DROPDOWN_RGB": "RGB", + "DYNAMIC_COLOR_RGB_DROPDOWN_HSL": "HSL", + "DYNAMIC_COLOR_RGB_DROPDOWN_HEX": "Hex", + "DYNAMIC_COLOR_RGB_DROPDOWN_PALETTE": "Palety", + "DYNAMIC_COLOR_RGB_TOOLTIP": "Vytvoří barvu pomocí dynamických RGB hodnot", + "DYNAMIC_COLOR_RED": "Červená", + "DYNAMIC_COLOR_ORANGE": "Oranžová", + "DYNAMIC_COLOR_YELLOW": "Žlutá", + "DYNAMIC_COLOR_GREEN": "Zelená", + "DYNAMIC_COLOR_LIGHT_BLUE": "Světle modrá", + "DYNAMIC_COLOR_BLUE": "Modrá", + "DYNAMIC_COLOR_PURPLE": "Fialová", + "DYNAMIC_COLOR_PINK": "Růžová", + "DYNAMIC_COLOR_WHITE": "Bílá", + "DYNAMIC_COLOR_OFF": "Vypnuto", + + "COLORS_RGB_TO_STRING_MESSAGE0": "Převést RGB $[RGB_VALUE] na text", + "COLORS_RGB_TO_STRING_TOOLTIP": "Převést RGB hodnotu do čitelného textového řetězce (CSS formát)" +} diff --git a/colors/blocks/translations/en.lang.json b/colors/blocks/translations/en.lang.json new file mode 100644 index 0000000..2a9382d --- /dev/null +++ b/colors/blocks/translations/en.lang.json @@ -0,0 +1,24 @@ +{ + "COLORS_NAME": "Colors", + "COLORS_DESCRIPTION": "Blocks for working with colors.", + "DYNAMIC_COLOR_RGB_MESSAGE0": "Color by", + "DYNAMIC_COLOR_RGB_DROPDOWN_NAMES": "Name", + "DYNAMIC_COLOR_RGB_DROPDOWN_RGB": "RGB", + "DYNAMIC_COLOR_RGB_DROPDOWN_HSL": "HSL", + "DYNAMIC_COLOR_RGB_DROPDOWN_HEX": "Hex", + "DYNAMIC_COLOR_RGB_DROPDOWN_PALETTE": "Palette", + "DYNAMIC_COLOR_RGB_TOOLTIP": "Create a color using dynamic RGB values", + "DYNAMIC_COLOR_RED": "Red", + "DYNAMIC_COLOR_ORANGE": "Orange", + "DYNAMIC_COLOR_YELLOW": "Yellow", + "DYNAMIC_COLOR_GREEN": "Green", + "DYNAMIC_COLOR_LIGHT_BLUE": "Light Blue", + "DYNAMIC_COLOR_BLUE": "Blue", + "DYNAMIC_COLOR_PURPLE": "Purple", + "DYNAMIC_COLOR_PINK": "Pink", + "DYNAMIC_COLOR_WHITE": "White", + "DYNAMIC_COLOR_OFF": "Off", + + "COLORS_RGB_TO_STRING_MESSAGE0": "Convert RGB $[RGB_VALUE] to string", + "COLORS_RGB_TO_STRING_TOOLTIP": "Convert an RGB color value to readable text string (CSS format)" +} diff --git a/colors/package.json b/colors/package.json new file mode 100644 index 0000000..6ff89aa --- /dev/null +++ b/colors/package.json @@ -0,0 +1,25 @@ +{ + "name": "colors", + "version": "0.1.0", + "author": "Jakub Andrysek", + "license": "MIT", + "description": "Colors utility functions and types", + "type": "module", + "main": "dist/js/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/colors/src/index.ts b/colors/src/index.ts new file mode 100644 index 0000000..02440b2 --- /dev/null +++ b/colors/src/index.ts @@ -0,0 +1,177 @@ +/** + * Colors utility functions and types + * @module colors + */ + +type Rgb = number; + +/** + * Convert RGB color to a number in format 0xRRGGBB + * @param r Red component (0-255) + * @param g Green component (0-255) + * @param b Blue component (0-255) + * @returns {Rgb} RGB color as a number in format 0xRRGGBB + */ +export function rgb(r: number, g: number, b: number): Rgb { + return (r << 16) | (g << 8) | b; +} + +export function rgbToString(color: Rgb): string { + const r = (color >> 16) & 0xff; + const g = (color >> 8) & 0xff; + const b = color & 0xff; + return `rgb(${r}, ${g}, ${b})`; +} + +/** + * Convert HSL color to RGB color + * @param hue (0-360) + * @param saturation (0-100) % + * @param lightness (0-100) % + * @returns {Rgb} RGB color as a number in format 0xRRGGBB + */ +export function hslToRgb( + hue: number, + saturation: number, + lightness: number, +): Rgb { + const s = saturation / 100; + const l = lightness / 100; + const chroma = (1 - Math.abs(2 * l - 1)) * s; + const hueNormalized = hue / 60; + const x = chroma * (1 - Math.abs((hueNormalized % 2) - 1)); + + let r: number, g: number, b: number; + if (hueNormalized > 0 && hueNormalized < 1) { + r = chroma; + g = x; + b = 0; + } else if (hueNormalized >= 1 && hueNormalized < 2) { + r = x; + g = chroma; + b = 0; + } else if (hueNormalized >= 2 && hueNormalized < 3) { + r = 0; + g = chroma; + b = x; + } else if (hueNormalized >= 3 && hueNormalized < 4) { + r = 0; + g = x; + b = chroma; + } else if (hueNormalized >= 4 && hueNormalized < 5) { + r = x; + g = 0; + b = chroma; + } else { + r = chroma; + g = 0; + b = x; + } + const correction = l - chroma / 2; + r = Math.round((r + correction) * 255); + g = Math.round((g + correction) * 255); + b = Math.round((b + correction) * 255); + + return rgb(r, g, b); +} + +/** + * Convert HEX color to RGB color + * @param hex {string} HEX color string in format #RRGGBB + * @returns {Rgb} RGB color as a number in format 0xRRGGBB + */ +export function hexToRgb(hex: string): Rgb { + if (hex.startsWith("#")) { + hex = hex.slice(1); + } + + if (hex.length !== 6) { + throw new Error("Invalid HEX color format. Expected format: #RRGGBB"); + } + + const r = parseInt(hex.slice(0, 2), 16); + const g = parseInt(hex.slice(2, 4), 16); + const b = parseInt(hex.slice(4, 6), 16); + + return rgb(r, g, b); +} + +/** + * Apply gamma correction to a single channel (0-255). + * Uses x^3 approximation with a bias of 4 for WS2812 LEDs. + * @param channel Channel value (0-255) + * @returns Gamma-corrected channel value (0-255) + */ +function channelGamma(channel: number): number { + if (channel === 0) return 0; + const c = (channel * channel * channel * 251) | 0; + return (4 + (c >> 24)) & 0xff; +} + +/** + * Apply gamma linearization (x^3 approximation) to an RGB color. + * Suitable for WS2812 / NeoPixel LED strips. + * @param color RGB color (format 0xRRGGBB) + * @returns Linearized RGB color (format 0xRRGGBB) + */ +export function linearize(color: Rgb): Rgb { + const r = (color >> 16) & 0xff; + const g = (color >> 8) & 0xff; + const b = color & 0xff; + return rgb(channelGamma(r), channelGamma(g), channelGamma(b)); +} + +/** + * Stretch each channel of an RGB color to a given maximum using (value * max) >> 8. + * @param color RGB color (format 0xRRGGBB) + * @param maxR Maximum value for red channel (0-255) + * @param maxG Maximum value for green channel (0-255) + * @param maxB Maximum value for blue channel (0-255) + * @returns Stretched RGB color (format 0xRRGGBB) + */ +export function stretchChannels( + color: Rgb, + maxR: number, + maxG: number, + maxB: number, +): Rgb { + const r = (((color >> 16) & 0xff) * maxR) >> 8; + const g = (((color >> 8) & 0xff) * maxG) >> 8; + const b = ((color & 0xff) * maxB) >> 8; + return rgb(r, g, b); +} + +/** + * Stretch all channels evenly to the same maximum. + * @param color RGB color (format 0xRRGGBB) + * @param max Maximum value for all channels (0-255) + * @returns Stretched RGB color (format 0xRRGGBB) + */ +export function stretchChannelsEvenly(color: Rgb, max: number): Rgb { + return stretchChannels(color, max, max, max); +} + +/** + * Function rainbow fixes saturation and brightness, and cycles through colors + * @param hue (0-360) + * @param brightness (0-100) - 50 is the default value + * @returns {Rgb} RGB color as a number in format 0xRRGGBB + */ +export function rainbow(hue: number, brightness: number = 50): Rgb { + hue = Math.min(hue, 360); + // fix range to 0-100 + let brightness_mapped = Math.min(Math.max(brightness, 0), 100); + return hslToRgb(hue, 100, brightness_mapped); +} + +/* Basic colors for LED strips */ +export const red = rainbow(0); +export const orange = rainbow(27); +export const yellow = rainbow(54); +export const green = rainbow(110); +export const light_blue = rainbow(177); +export const blue = rainbow(240); +export const purple = rainbow(285); +export const pink = rainbow(323); +export const white: Rgb = 0xffffff; +export const off: Rgb = 0x000000; diff --git a/colors/tsconfig.json b/colors/tsconfig.json new file mode 100644 index 0000000..3a3fbba --- /dev/null +++ b/colors/tsconfig.json @@ -0,0 +1,14 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true + } +} diff --git a/differential-drive/.gitignore b/differential-drive/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/differential-drive/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/differential-drive/README.md b/differential-drive/README.md new file mode 100644 index 0000000..e6eaa1d --- /dev/null +++ b/differential-drive/README.md @@ -0,0 +1 @@ +# Differential drive motor control library diff --git a/differential-drive/blocks/differential_drive.jacly.json b/differential-drive/blocks/differential_drive.jacly.json new file mode 100644 index 0000000..5a4760b --- /dev/null +++ b/differential-drive/blocks/differential_drive.jacly.json @@ -0,0 +1,355 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "differential-drive", + "category": "differential_drive", + "parentCategory": "movement", + "name": "%T%", + "description": "%T%", + "colour": "#7d5834", + "icon": "ChevronsLeftRightEllipsis", + "priority": 140, + "import": ["import { DifferentialDrive } from \"differential-drive\";"], + "contents": [ + { + "kind": "label", + "text": "%SETUP%" + }, + { + "kind": "block", + "type": "differential_drive_constructor", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "d_drive_?" + }, + { + "type": "field_dropdown", + "name": "LEFT_MOTOR", + "instanceof": "motor" + }, + { + "type": "field_dropdown", + "name": "RIGHT_MOTOR", + "instanceof": "motor" + }, + { + "type": "input_value", + "name": "DIAMETER", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 83 + } + } + } + ], + "constructs": "d_drive", + "tooltip": "%T%", + "code": "$[CONSTRUCTED_VAR_NAME] = new DifferentialDrive($[LEFT_MOTOR], $[RIGHT_MOTOR], $[DIAMETER]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%MOVEMENT%" + }, + { + "kind": "block", + "type": "differential_drive_move_distance", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "DRIVE_INSTANCE", + "instanceof": "d_drive" + }, + { + "type": "input_value", + "name": "CURVE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "DISTANCE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 100 + } + } + } + ], + "tooltip": "%T%", + "code": "await $[DRIVE_INSTANCE].move($[CURVE], { distance: $[DISTANCE] });", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "differential_drive_move_time", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "DRIVE_INSTANCE", + "instanceof": "d_drive" + }, + { + "type": "input_value", + "name": "CURVE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "TIME", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 100 + } + } + } + ], + "tooltip": "%T%", + "code": "await $[DRIVE_INSTANCE].move($[CURVE], { time: $[TIME] });", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "differential_drive_move_infinite", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "DRIVE_INSTANCE", + "instanceof": "d_drive" + }, + { + "type": "input_value", + "name": "CURVE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + } + ], + "tooltip": "%T%", + "code": "$[DRIVE_INSTANCE].move($[CURVE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "differential_drive_rotate", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "DRIVE_INSTANCE", + "instanceof": "d_drive" + }, + { + "type": "input_value", + "name": "ANGLE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 90 + } + } + } + ], + "tooltip": "%T%", + "code": "await $[DRIVE_INSTANCE].rotate($[ANGLE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "differential_drive_stop", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "DRIVE_INSTANCE", + "instanceof": "d_drive" + }, + { + "type": "input_value", + "name": "BRAKE", + "check": "Boolean", + "shadow": { + "type": "logic_boolean", + "fields": { + "BOOL": 0 + } + } + } + ], + "tooltip": "%T%", + "code": "await $[DRIVE_INSTANCE].stop($[BRAKE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%HEADING%" + }, + { + "kind": "block", + "type": "differential_drive_getHeading", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "DRIVE_INSTANCE", + "instanceof": "d_drive" + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[DRIVE_INSTANCE].getHeading()" + }, + { + "kind": "block", + "type": "differential_drive_getHeadingClamped", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "DRIVE_INSTANCE", + "instanceof": "d_drive" + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[DRIVE_INSTANCE].getHeadingClamped()" + }, + { + "kind": "block", + "type": "differential_drive_resetHeading", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "DRIVE_INSTANCE", + "instanceof": "d_drive" + } + ], + "tooltip": "%T%", + "code": "$[DRIVE_INSTANCE].resetHeading();", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%SPEED_AND_RAMP%" + }, + { + "kind": "block", + "type": "differential_drive_setSpeed", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "DRIVE_INSTANCE", + "instanceof": "d_drive" + }, + { + "type": "input_value", + "name": "SPEED", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 100 + } + } + } + ], + "tooltip": "%T%", + "code": "$[DRIVE_INSTANCE].setSpeed($[SPEED]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "differential_drive_getSpeed", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "DRIVE_INSTANCE", + "instanceof": "d_drive" + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[DRIVE_INSTANCE].getSpeed()" + }, + { + "kind": "block", + "type": "differential_drive_setRamp", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "DRIVE_INSTANCE", + "instanceof": "d_drive" + }, + { + "type": "input_value", + "name": "RAMP", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 2000 + } + } + } + ], + "tooltip": "%T%", + "code": "$[DRIVE_INSTANCE].setRamp($[RAMP]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "differential_drive_getRamp", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "DRIVE_INSTANCE", + "instanceof": "d_drive" + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[DRIVE_INSTANCE].getRamp()" + } + ] +} diff --git a/differential-drive/blocks/translations/cs.lang.json b/differential-drive/blocks/translations/cs.lang.json new file mode 100644 index 0000000..251c86b --- /dev/null +++ b/differential-drive/blocks/translations/cs.lang.json @@ -0,0 +1,40 @@ +{ + "DIFFERENTIAL_DRIVE_NAME": "Diferenciální pohon", + "DIFFERENTIAL_DRIVE_DESCRIPTION": "Knihovna pro řízení diferenciálního pohonu", + + "DIFFERENTIAL_DRIVE_LABEL_SETUP": "Nastavení", + "DIFFERENTIAL_DRIVE_CONSTRUCTOR_MESSAGE0": "vytvořit $[CONSTRUCTED_VAR_NAME] diferenciální pohon\nlevý motor $[LEFT_MOTOR] pravý motor $[RIGHT_MOTOR]\nrozchod kol (mm) $[DIAMETER]", + "DIFFERENTIAL_DRIVE_CONSTRUCTOR_TOOLTIP": "Vytvoří novou instanci diferenciálního pohonu ze dvou motorů a rozchodu kol", + + "DIFFERENTIAL_DRIVE_LABEL_MOVEMENT": "Pohyb", + + "DIFFERENTIAL_DRIVE_MOVE_DISTANCE_MESSAGE0": "na $[DRIVE_INSTANCE] pohnout\n-1 je plná levá, 0 je rovně a 1 je plná pravá\nkřivka $[CURVE] (rozsah -1 až 1)\npro $[DISTANCE] mm", + "DIFFERENTIAL_DRIVE_MOVE_DISTANCE_TOOLTIP": "Pohne robotem se zadanou křivkou pro zadanou vzdálenost (v milimetrech)", + "DIFFERENTIAL_DRIVE_MOVE_TIME_MESSAGE0": "na $[DRIVE_INSTANCE] pohnout\n-1 je plná levá, 0 je rovně a 1 je plná pravá\nkřivka $[CURVE] (rozsah -1 až 1)\npro $[TIME] ms", + "DIFFERENTIAL_DRIVE_MOVE_TIME_TOOLTIP": "Pohne robotem se zadanou křivkou pro zadanou dobu (v milisekundách)", + "DIFFERENTIAL_DRIVE_MOVE_INFINITE_MESSAGE0": "na $[DRIVE_INSTANCE] pohnout\n-1 je plná levá, 0 je rovně a 1 je plná pravá\nkřivka $[CURVE] (rozsah -1 až 1)\ndokud nezastaveno jiným blokem", + "DIFFERENTIAL_DRIVE_MOVE_INFINITE_TOOLTIP": "Pohne robotem se zadanou křivkou dokud jiný blok nezastaví pohon", + + "DIFFERENTIAL_DRIVE_ROTATE_MESSAGE0": "na $[DRIVE_INSTANCE] otočit o $[ANGLE] stupňů", + "DIFFERENTIAL_DRIVE_ROTATE_TOOLTIP": "Otočí robota o zadaný úhel (ve stupních)", + "DIFFERENTIAL_DRIVE_STOP_MESSAGE0": "na $[DRIVE_INSTANCE] zastavit robota s brzdou $[BRAKE]", + "DIFFERENTIAL_DRIVE_STOP_TOOLTIP": "Zastaví robota, volitelně s brzdou", + + "DIFFERENTIAL_DRIVE_LABEL_HEADING": "Směr", + "DIFFERENTIAL_DRIVE_GETHEADING_MESSAGE0": "na $[DRIVE_INSTANCE] získat směr", + "DIFFERENTIAL_DRIVE_GETHEADING_TOOLTIP": "Získá aktuální směr robota (ve stupních)", + "DIFFERENTIAL_DRIVE_GETHEADINGCLAMPED_MESSAGE0": "na $[DRIVE_INSTANCE] získat omezený směr", + "DIFFERENTIAL_DRIVE_GETHEADINGCLAMPED_TOOLTIP": "Získá aktuální směr robota, omezený na 0-360 stupňů", + "DIFFERENTIAL_DRIVE_RESETHEADING_MESSAGE0": "na $[DRIVE_INSTANCE] resetovat směr", + "DIFFERENTIAL_DRIVE_RESETHEADING_TOOLTIP": "Resetuje směr robota na 0 stupňů", + + "DIFFERENTIAL_DRIVE_LABEL_SPEED_AND_RAMP": "Rychlost a rampa", + "DIFFERENTIAL_DRIVE_SETSPEED_MESSAGE0": "na $[DRIVE_INSTANCE] nastavit rychlost na $[SPEED]", + "DIFFERENTIAL_DRIVE_SETSPEED_TOOLTIP": "Nastaví rychlost robota (v mm/s)", + "DIFFERENTIAL_DRIVE_GETSPEED_MESSAGE0": "na $[DRIVE_INSTANCE] získat rychlost", + "DIFFERENTIAL_DRIVE_GETSPEED_TOOLTIP": "Získá aktuální rychlost robota (v mm/s)", + "DIFFERENTIAL_DRIVE_SETRAMP_MESSAGE0": "na $[DRIVE_INSTANCE] nastavit rampu na $[RAMP] ms", + "DIFFERENTIAL_DRIVE_SETRAMP_TOOLTIP": "Nastaví dobu zrychlení/zpomalení (v milisekundách)", + "DIFFERENTIAL_DRIVE_GETRAMP_MESSAGE0": "na $[DRIVE_INSTANCE] získat rampu", + "DIFFERENTIAL_DRIVE_GETRAMP_TOOLTIP": "Získá aktuální dobu zrychlení/zpomalení (v milisekundách)" +} diff --git a/differential-drive/blocks/translations/en.lang.json b/differential-drive/blocks/translations/en.lang.json new file mode 100644 index 0000000..2536e5f --- /dev/null +++ b/differential-drive/blocks/translations/en.lang.json @@ -0,0 +1,40 @@ +{ + "DIFFERENTIAL_DRIVE_NAME": "Differential Drive", + "DIFFERENTIAL_DRIVE_DESCRIPTION": "Differential drive motor control library", + + "DIFFERENTIAL_DRIVE_LABEL_SETUP": "Setup", + "DIFFERENTIAL_DRIVE_CONSTRUCTOR_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] differential drive\nleft motor $[LEFT_MOTOR] right motor $[RIGHT_MOTOR]\nwheel distance (mm) $[DIAMETER]", + "DIFFERENTIAL_DRIVE_CONSTRUCTOR_TOOLTIP": "Create a new differential drive instance from two motors and wheel distance", + + "DIFFERENTIAL_DRIVE_LABEL_MOVEMENT": "Movement", + + "DIFFERENTIAL_DRIVE_MOVE_DISTANCE_MESSAGE0": "on $[DRIVE_INSTANCE] move\n-1 is full left, 0 is straight and 1 is full right\ncurve $[CURVE] (range -1 to 1)\nfor $[DISTANCE] mm", + "DIFFERENTIAL_DRIVE_MOVE_DISTANCE_TOOLTIP": "Move the robot with the specified curve for the given distance (in millimeters)", + "DIFFERENTIAL_DRIVE_MOVE_TIME_MESSAGE0": "on $[DRIVE_INSTANCE] move\n-1 is full left, 0 is straight and 1 is full right\ncurve $[CURVE] (range -1 to 1)\nfor $[TIME] ms", + "DIFFERENTIAL_DRIVE_MOVE_TIME_TOOLTIP": "Move the robot with the specified curve for the given time (in milliseconds)", + "DIFFERENTIAL_DRIVE_MOVE_INFINITE_MESSAGE0": "on $[DRIVE_INSTANCE] move\n-1 is full left, 0 is straight and 1 is full right\ncurve $[CURVE] (range -1 to 1)\nuntil stopped brake and block", + "DIFFERENTIAL_DRIVE_MOVE_INFINITE_TOOLTIP": "Move the robot with the specified curve indefinitely", + + "DIFFERENTIAL_DRIVE_ROTATE_MESSAGE0": "on $[DRIVE_INSTANCE] rotate by $[ANGLE] degrees", + "DIFFERENTIAL_DRIVE_ROTATE_TOOLTIP": "Rotate the robot by the specified angle (in degrees)", + "DIFFERENTIAL_DRIVE_STOP_MESSAGE0": "on $[DRIVE_INSTANCE] stop robot brake $[BRAKE]", + "DIFFERENTIAL_DRIVE_STOP_TOOLTIP": "Stop the robot, optionally braking", + + "DIFFERENTIAL_DRIVE_LABEL_HEADING": "Heading", + "DIFFERENTIAL_DRIVE_GETHEADING_MESSAGE0": "on $[DRIVE_INSTANCE] get heading", + "DIFFERENTIAL_DRIVE_GETHEADING_TOOLTIP": "Get the current heading of the robot (in degrees)", + "DIFFERENTIAL_DRIVE_GETHEADINGCLAMPED_MESSAGE0": "on $[DRIVE_INSTANCE] get clamped heading", + "DIFFERENTIAL_DRIVE_GETHEADINGCLAMPED_TOOLTIP": "Get the current heading of the robot, clamped to 0-360 degrees", + "DIFFERENTIAL_DRIVE_RESETHEADING_MESSAGE0": "on $[DRIVE_INSTANCE] reset heading", + "DIFFERENTIAL_DRIVE_RESETHEADING_TOOLTIP": "Reset the heading of the robot to 0 degrees", + + "DIFFERENTIAL_DRIVE_LABEL_SPEED_AND_RAMP": "Speed and Ramp", + "DIFFERENTIAL_DRIVE_SETSPEED_MESSAGE0": "on $[DRIVE_INSTANCE] set speed to $[SPEED]", + "DIFFERENTIAL_DRIVE_SETSPEED_TOOLTIP": "Set the speed of the robot (in mm/s)", + "DIFFERENTIAL_DRIVE_GETSPEED_MESSAGE0": "on $[DRIVE_INSTANCE] get speed", + "DIFFERENTIAL_DRIVE_GETSPEED_TOOLTIP": "Get the current speed of the robot (in mm/s)", + "DIFFERENTIAL_DRIVE_SETRAMP_MESSAGE0": "on $[DRIVE_INSTANCE] set ramp to $[RAMP] ms", + "DIFFERENTIAL_DRIVE_SETRAMP_TOOLTIP": "Set the acceleration/deceleration ramp time (in milliseconds)", + "DIFFERENTIAL_DRIVE_GETRAMP_MESSAGE0": "on $[DRIVE_INSTANCE] get ramp", + "DIFFERENTIAL_DRIVE_GETRAMP_TOOLTIP": "Get the current acceleration/deceleration ramp time (in milliseconds)" +} diff --git a/differential-drive/package.json b/differential-drive/package.json new file mode 100644 index 0000000..b7f29d5 --- /dev/null +++ b/differential-drive/package.json @@ -0,0 +1,28 @@ +{ + "name": "differential-drive", + "version": "0.1.0", + "author": "Jakub Andrysek", + "license": "MIT", + "description": "Differential drive motor control library", + "type": "module", + "main": "dist/js/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "dependencies": { + "@types/jaculus": "0.1.0" + }, + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/differential-drive/src/index.ts b/differential-drive/src/index.ts new file mode 100644 index 0000000..1530174 --- /dev/null +++ b/differential-drive/src/index.ts @@ -0,0 +1,155 @@ +import * as motor from "motor"; + +const PI_180 = 180 / Math.PI; + +export class DifferentialDrive { + private speed: number = 0; + private ramp: number = 0; + private diameter: number; + + public leftMotor: motor.Motor; + public rightMotor: motor.Motor; + + constructor( + leftMotor: motor.Motor, + rightMotor: motor.Motor, + diameter: number, + ) { + this.leftMotor = leftMotor; + this.rightMotor = rightMotor; + this.diameter = diameter; + } + + public setSpeed(value: number): void { + this.speed = value; + } + + public setRamp(value: number): void { + this.ramp = value; + } + + public getSpeed(): number { + return this.speed; + } + + public getRamp(): number { + return this.ramp; + } + + /** + * Move the robot + * @param curve number in range -1 to 1, where -1 is full left, 0 is straight and 1 is full right + * @param duration optional duration of the move + */ + public async move(curve: number, duration?: motor.MoveDuration) { + let lMot = 0; + let rMot = 0; + + if (curve < 0) { + lMot = 1 + curve * 2; + rMot = 1; + } else if (curve > 0) { + lMot = 1; + rMot = 1 - curve * 2; + } else { + lMot = 1; + rMot = 1; + } + + this.leftMotor.setSpeed(lMot * this.speed); + this.rightMotor.setSpeed(rMot * this.speed); + this.leftMotor.setRamp(lMot * this.ramp); + this.rightMotor.setRamp(rMot * this.ramp); + + const hasTime = duration && duration.hasOwnProperty("time"); + const hasDistance = duration && duration.hasOwnProperty("distance"); + + if (duration && (hasTime || hasDistance)) { + if (hasTime) { + await Promise.all([ + this.leftMotor.move(duration), + this.rightMotor.move(duration), + ]); + } else if (hasDistance) { + const distance = (duration as { distance: number }).distance; + await Promise.all([ + this.leftMotor.move({ distance: distance * lMot }), + this.rightMotor.move({ distance: distance * rMot }), + ]); + } + } else { + await Promise.all([this.leftMotor.move(), this.rightMotor.move()]); + } + } + + /** + * Rotate the robot + * @param angle in degrees + */ + public async rotate(angle: number) { + this.leftMotor.setSpeed(this.speed); + this.rightMotor.setSpeed(this.speed); + this.leftMotor.setRamp(this.ramp); + this.rightMotor.setRamp(this.ramp); + + const arcLength = (Math.abs(angle) / 360) * Math.PI * this.diameter; + + let lMot: number; + let rMot: number; + + if (angle < 0) { + lMot = -arcLength; + rMot = arcLength; + } else { + lMot = arcLength; + rMot = -arcLength; + } + + await Promise.all([ + this.leftMotor.move({ distance: lMot }), + this.rightMotor.move({ distance: rMot }), + ]); + } + + private headingOffset: number = 0; + private getHeadingRaw(): number { + const difference = + this.leftMotor.getPosition() - this.rightMotor.getPosition(); + const heading = (difference / this.diameter) * PI_180; + return Math.round(heading); + } + /** + * Get the current heading of the robot, measured from start + * @returns heading in degrees + */ + public getHeading(): number { + return this.getHeadingRaw() + this.headingOffset; + } + + /** + * Get the current heading of the robot, clamped to 0-360 degrees + * @returns heading in degrees, always in range 0-360 + */ + public getHeadingClamped(): number { + const heading = this.getHeading() % 360; + return heading < 0 ? heading + 360 : heading; + } + + /** + * Reset the heading of the robot to 0 degrees + */ + public resetHeading(): void { + this.headingOffset = -this.getHeadingRaw(); + } + + /** + * Stop the robot + * @param brake if true, the robot will brake, otherwise it will coast to a stop + */ + public async stop(brake?: boolean) { + await Promise.all([ + this.leftMotor.stop(brake), + this.rightMotor.stop(brake), + ]); + } +} diff --git a/differential-drive/tsconfig.json b/differential-drive/tsconfig.json new file mode 100644 index 0000000..ab82ab7 --- /dev/null +++ b/differential-drive/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true, + "types": ["jaculus"] + } +} diff --git a/ds18b20/.gitignore b/ds18b20/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/ds18b20/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/ds18b20/README.md b/ds18b20/README.md new file mode 100644 index 0000000..89a58ef --- /dev/null +++ b/ds18b20/README.md @@ -0,0 +1 @@ +# DS18B20 temperature sensor - One Wire sensor library diff --git a/ds18b20/blocks/ds18b20.jacly.json b/ds18b20/blocks/ds18b20.jacly.json new file mode 100644 index 0000000..1b634cf --- /dev/null +++ b/ds18b20/blocks/ds18b20.jacly.json @@ -0,0 +1,94 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "category": "ds18b20", + "parentCategory": "peripherals", + "name": "%T%", + "description": "%T%", + "colour": "#DC2626", + "icon": "Thermometer", + "priority": 140, + "import": ["import { DS18B20 } from \"ds18b20\";"], + "contents": [ + { + "kind": "label", + "text": "%SETUP%" + }, + { + "kind": "block", + "type": "ds18b20_constructor", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "ds18b20_?" + }, + { + "type": "input_value", + "name": "PIN", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 5 + } + } + }, + { + "type": "field_dropdown", + "name": "RESOLUTION", + "options": [ + ["%9BITS%", "9"], + ["%10BITS%", "10"], + ["%11BITS%", "11"], + ["%12BITS%", "12"] + ] + } + ], + "constructs": "ds18b20", + "tooltip": "%T%", + "code": "$[CONSTRUCTED_VAR_NAME] = new DS18B20($[PIN], $[RESOLUTION]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%READ%" + }, + { + "kind": "block", + "type": "ds18b20_read_temperature", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SENSOR_INSTANCE", + "instanceof": "ds18b20" + } + ], + "tooltip": "%T%", + "output": "Number", + "code": "(await $[SENSOR_INSTANCE].readTemperature())" + }, + { + "kind": "label", + "text": "%OTHER%" + }, + { + "kind": "block", + "type": "ds18b20_close", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SENSOR_INSTANCE", + "instanceof": "ds18b20" + } + ], + "tooltip": "%T%", + "code": "$[SENSOR_INSTANCE].close();", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/ds18b20/blocks/translations/cs.lang.json b/ds18b20/blocks/translations/cs.lang.json new file mode 100644 index 0000000..1f448bb --- /dev/null +++ b/ds18b20/blocks/translations/cs.lang.json @@ -0,0 +1,20 @@ +{ + "DS18B20_NAME": "DS18B20", + "DS18B20_DESCRIPTION": "Knihovna pro teplotní čidlo DS18B20 (1-Wire)", + "DS18B20_LABEL_SETUP": "Nastavení (je potřeba pro použití ostatních bloků DS18B20)", + "DS18B20_LABEL_READ": "Čtení", + "DS18B20_LABEL_OTHER": "Ostatní operace s DS18B20", + + "DS18B20_CONSTRUCTOR_MESSAGE0": "vytvořit $[CONSTRUCTED_VAR_NAME] DS18B20 na pinu $[PIN] s přesností $[RESOLUTION]", + "DS18B20_CONSTRUCTOR_TOOLTIP": "Vytvoří novou instanci teplotního čidla DS18B20", + "DS18B20_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_9BITS": "9 bitů", + "DS18B20_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_10BITS": "10 bitů", + "DS18B20_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_11BITS": "11 bitů", + "DS18B20_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_12BITS": "12 bitů", + + "DS18B20_READ_TEMPERATURE_MESSAGE0": "přečíst teplotu z $[SENSOR_INSTANCE]", + "DS18B20_READ_TEMPERATURE_TOOLTIP": "Přečte aktuální teplotu ve stupních Celsia", + + "DS18B20_CLOSE_MESSAGE0": "zavřít $[SENSOR_INSTANCE]", + "DS18B20_CLOSE_TOOLTIP": "Uvolní prostředky čidla DS18B20" +} diff --git a/ds18b20/blocks/translations/en.lang.json b/ds18b20/blocks/translations/en.lang.json new file mode 100644 index 0000000..b3b8f91 --- /dev/null +++ b/ds18b20/blocks/translations/en.lang.json @@ -0,0 +1,20 @@ +{ + "DS18B20_NAME": "DS18B20", + "DS18B20_DESCRIPTION": "DS18B20 temperature sensor library (1-Wire)", + "DS18B20_LABEL_SETUP": "Setup (this is required to use other DS18B20 blocks)", + "DS18B20_LABEL_READ": "Reading", + "DS18B20_LABEL_OTHER": "Other DS18B20 operations", + + "DS18B20_CONSTRUCTOR_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] DS18B20 on pin $[PIN] with resolution $[RESOLUTION]", + "DS18B20_CONSTRUCTOR_TOOLTIP": "Create a new DS18B20 temperature sensor instance", + "DS18B20_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_9BITS": "9 bits", + "DS18B20_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_10BITS": "10 bits", + "DS18B20_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_11BITS": "11 bits", + "DS18B20_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_12BITS": "12 bits", + + "DS18B20_READ_TEMPERATURE_MESSAGE0": "read temperature from $[SENSOR_INSTANCE]", + "DS18B20_READ_TEMPERATURE_TOOLTIP": "Read the current temperature in degrees Celsius", + + "DS18B20_CLOSE_MESSAGE0": "close $[SENSOR_INSTANCE]", + "DS18B20_CLOSE_TOOLTIP": "Release the DS18B20 sensor resources" +} diff --git a/ds18b20/package.json b/ds18b20/package.json new file mode 100644 index 0000000..8c1b695 --- /dev/null +++ b/ds18b20/package.json @@ -0,0 +1,28 @@ +{ + "name": "ds18b20", + "version": "0.1.0", + "author": "Milan Novak", + "license": "MIT", + "description": "DS18B20 temperature sensor - One Wire sensor library", + "type": "module", + "main": "dist/js/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "dependencies": { + "@types/jaculus": "0.1.0" + }, + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/ds18b20/src/index.ts b/ds18b20/src/index.ts new file mode 100644 index 0000000..b4caffe --- /dev/null +++ b/ds18b20/src/index.ts @@ -0,0 +1,134 @@ +import { OneWire } from "onewire"; + +const SENSOR_ROM = 0x28; + +enum Command { + CONVERT_T = 0x44, + READ_SCRATCHPAD = 0xbe, + WRITE_SCRATCHPAD = 0x4e, +} + +const READ_BYTES_COUNT = 9; +const TEMP_LSB_INDEX = 0; +const TEMP_MSB_INDEX = 1; + +type ResolutionBits = 9 | 10 | 11 | 12; + +const TIMEOUT_MAP: Record = { + 9: 94, + 10: 188, + 11: 375, + 12: 750, +}; + +/** + * Library for a DS18B20 temperature sensor on a 1-Wire bus. + */ +export class DS18B20 { + private address: Uint8Array; + private onewire: OneWire; + private resolutionBits: ResolutionBits; + + /** + * Create a sensor instance for the specified data pin. + * + * @param pinNumber Pin connected to the DS18B20 data line. + * @param resolutionBits Temperature resolution in bits. Supported values are 9, 10, 11, and 12. + */ + constructor(pinNumber: number, resolutionBits: ResolutionBits = 11) { + this.onewire = new OneWire(pinNumber); + this.resolutionBits = resolutionBits; + + if (!this.onewire) { + throw new Error("Failed to initialize OneWire"); + } + + const devices = this.onewire.search(); + const address = devices.find((rom) => rom[0] === SENSOR_ROM); + + if (!address) { + this.onewire.close(); + throw new Error( + `DS18B20 (family 0x${SENSOR_ROM.toString(16)}) not found`, + ); + } + this.address = address; + this.setResolution(); + } + + /** + * Release resources + */ + close() { + this.onewire.close(); + } + + private sendCommand(command: number | Uint8Array) { + this.onewire.reset(); + this.onewire.select(this.address); + this.onewire.write(command); + } + + private startConversion() { + this.sendCommand(Command.CONVERT_T); + } + + private readScratchpad(): Uint8Array { + this.sendCommand(Command.READ_SCRATCHPAD); + return this.onewire.read(READ_BYTES_COUNT); + } + + private setResolution() { + this.onewire.reset(); + this.onewire.select(this.address); + this.onewire.write(Command.WRITE_SCRATCHPAD); + // Resolution is set in bits 5 and 6 of the configuration register + const configByte = (this.resolutionBits - 9) << 5; + // TH and TL registers are not used, set to 0 + this.onewire.write(new Uint8Array([0, 0, configByte])); + } + + private validateScratchpad(scratchpad: Uint8Array): boolean { + const data = scratchpad.slice(0, READ_BYTES_COUNT - 1); // exclude CRC byte + const sensorCrc = scratchpad[8]; + const computedCrc = this.onewire.crc8(data); + + return computedCrc === sensorCrc; + } + + private decodeTemperature(scratchpad: Uint8Array): number { + let raw = + (scratchpad[TEMP_MSB_INDEX] << 8) | scratchpad[TEMP_LSB_INDEX]; + // convert to signed 16-bit integer + if (raw & 0x8000) { + raw -= 0x10000; + } + + // Convert to Celsius + const tempC = raw / 16; + return tempC; + } + + /** + * Read the current temperature from the sensor. + * + * @returns Temperature in degrees Celsius. + * @throws If the scratchpad cannot be read or its CRC check fails. + */ + async readTemperature(): Promise { + this.startConversion(); + + await sleep(TIMEOUT_MAP[this.resolutionBits]); + const scratchpad = this.readScratchpad(); + if (!scratchpad) { + throw new Error("Failed to read scratchpad"); + } + + if (!this.validateScratchpad(scratchpad)) { + throw new Error("Scratchpad CRC validation failed"); + } + + const tempC = this.decodeTemperature(scratchpad); + return tempC; + } +} diff --git a/ds18b20/tsconfig.json b/ds18b20/tsconfig.json new file mode 100644 index 0000000..ab82ab7 --- /dev/null +++ b/ds18b20/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true, + "types": ["jaculus"] + } +} diff --git a/ds3231/.gitignore b/ds3231/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/ds3231/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/ds3231/README.md b/ds3231/README.md new file mode 100644 index 0000000..83d7dff --- /dev/null +++ b/ds3231/README.md @@ -0,0 +1 @@ +# DS3231 real-time clock (RTC) library diff --git a/ds3231/blocks/ds3231.jacly.json b/ds3231/blocks/ds3231.jacly.json new file mode 100644 index 0000000..5db11d3 --- /dev/null +++ b/ds3231/blocks/ds3231.jacly.json @@ -0,0 +1,216 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "category": "ds3231", + "parentCategory": "peripherals", + "name": "%T%", + "description": "%T%", + "colour": "#AC8FBC", + "icon": "Clock", + "priority": 140, + "import": ["import { DS3231 } from \"ds3231\";"], + "contents": [ + { + "kind": "block", + "type": "i2c_constructor" + }, + { + "kind": "block", + "type": "ds3231_constructor", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "ds3231_?" + }, + { + "type": "field_dropdown", + "name": "I2C_INSTANCE", + "instanceof": "i2c_inst" + } + ], + "constructs": "ds3231", + "tooltip": "%T%", + "code": "let $[CONSTRUCTED_VAR_NAME] = new DS3231($[I2C_INSTANCE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "ds3231_read_field", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SENSOR_INSTANCE", + "instanceof": "ds3231" + } + ], + "hideInToolbox": true, + "tooltip": "%T%", + "code": "$[SENSOR_INSTANCE].readTime()", + "output": "RtcDateTime" + }, + { + "kind": "block", + "type": "ds3231_unpack_time", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "DATETIME", + "check": "RtcDateTime", + "block": { + "type": "ds3231_read_field", + "fields": { + "SENSOR_INSTANCE": "ds3231_?" + } + } + }, + { + "type": "field_dropdown", + "name": "FIELD", + "options": [ + ["%YEAR%", "year"], + ["%MONTH%", "month"], + ["%DATE%", "date"], + ["%DAY%", "day"], + ["%HOURS%", "hours"], + ["%MINUTES%", "minutes"], + ["%SECONDS%", "seconds"] + ] + } + ], + "tooltip": "%T%", + "code": "$[DATETIME].$[FIELD]", + "output": "Number" + }, + { + "kind": "block", + "type": "ds3231_create_datetime", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "YEAR", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 2026 + } + } + }, + { + "type": "input_value", + "name": "MONTH", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + }, + { + "type": "input_value", + "name": "DATE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + }, + { + "type": "input_value", + "name": "DAY", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + }, + { + "type": "input_value", + "name": "HOURS", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "MINUTES", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "SECONDS", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + } + ], + "hideInToolbox": true, + "tooltip": "%T%", + "code": "{ year: $[YEAR], month: $[MONTH], date: $[DATE], day: $[DAY], hours: $[HOURS], minutes: $[MINUTES], seconds: $[SECONDS] }", + "output": "RtcDateTime" + }, + { + "kind": "block", + "type": "ds3231_set_time", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SENSOR_INSTANCE", + "instanceof": "ds3231" + }, + { + "type": "input_value", + "name": "DATETIME", + "check": "RtcDateTime", + "block": { + "type": "ds3231_create_datetime" + } + } + ], + "tooltip": "%T%", + "code": "$[SENSOR_INSTANCE].setTime($[DATETIME]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "ds3231_read_temperature", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SENSOR_INSTANCE", + "instanceof": "ds3231" + } + ], + "tooltip": "%T%", + "code": "$[SENSOR_INSTANCE].readTemperature()", + "output": "Number" + } + ] +} diff --git a/ds3231/blocks/translations/cs.lang.json b/ds3231/blocks/translations/cs.lang.json new file mode 100644 index 0000000..ffe24d5 --- /dev/null +++ b/ds3231/blocks/translations/cs.lang.json @@ -0,0 +1,23 @@ +{ + "DS3231_NAME": "DS3231", + "DS3231_DESCRIPTION": "DS3231 knihovna pro real-time hodiny", + "DS3231_CONSTRUCTOR_MESSAGE0": "vytvořit $[CONSTRUCTED_VAR_NAME] DS3231 na I2C sběrnici $[I2C_INSTANCE]", + "DS3231_CONSTRUCTOR_TOOLTIP": "Vytvoří instanci DS3231 RTC na zadané I2C sběrnici", + "DS3231_READ_FIELD_MESSAGE0": "číst čas z $[SENSOR_INSTANCE]", + "DS3231_READ_FIELD_TOOLTIP": "Přečte aktuální datum a čas z RTC jako objekt DateTime", + "DS3231_UNPACK_TIME_MESSAGE0": "získat $[FIELD] z $[DATETIME]", + "DS3231_UNPACK_TIME_TOOLTIP": "Získá konkrétní položku z hodnoty DateTime", + "DS3231_UNPACK_TIME_ARGS0_FIELD_DROPDOWN_YEAR": "rok", + "DS3231_UNPACK_TIME_ARGS0_FIELD_DROPDOWN_MONTH": "měsíc", + "DS3231_UNPACK_TIME_ARGS0_FIELD_DROPDOWN_DATE": "datum", + "DS3231_UNPACK_TIME_ARGS0_FIELD_DROPDOWN_DAY": "den v týdnu", + "DS3231_UNPACK_TIME_ARGS0_FIELD_DROPDOWN_HOURS": "hodiny", + "DS3231_UNPACK_TIME_ARGS0_FIELD_DROPDOWN_MINUTES": "minuty", + "DS3231_UNPACK_TIME_ARGS0_FIELD_DROPDOWN_SECONDS": "sekundy", + "DS3231_CREATE_DATETIME_MESSAGE0": "vytvořit DateTime\nRok $[YEAR] Měsíc $[MONTH] Den $[DATE] DenVT $[DAY]\nHodiny $[HOURS] Minuty $[MINUTES] Sekundy $[SECONDS]", + "DS3231_CREATE_DATETIME_TOOLTIP": "Vytvoří objekt DateTime se zadanými hodnotami data a času", + "DS3231_SET_TIME_MESSAGE0": "nastavit čas $[SENSOR_INSTANCE] $[DATETIME]", + "DS3231_SET_TIME_TOOLTIP": "Nastaví datum a čas v RTC", + "DS3231_READ_TEMPERATURE_MESSAGE0": "$[SENSOR_INSTANCE] číst teplotu (°C)", + "DS3231_READ_TEMPERATURE_TOOLTIP": "Čte vnitřní teplotu RTC ve stupních Celsia" +} diff --git a/ds3231/blocks/translations/en.lang.json b/ds3231/blocks/translations/en.lang.json new file mode 100644 index 0000000..540556c --- /dev/null +++ b/ds3231/blocks/translations/en.lang.json @@ -0,0 +1,23 @@ +{ + "DS3231_NAME": "DS3231", + "DS3231_DESCRIPTION": "DS3231 real-time clock library", + "DS3231_CONSTRUCTOR_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] DS3231 on I2C bus $[I2C_INSTANCE]", + "DS3231_CONSTRUCTOR_TOOLTIP": "Create DS3231 RTC instance on specified I2C bus", + "DS3231_READ_FIELD_MESSAGE0": "read time from $[SENSOR_INSTANCE]", + "DS3231_READ_FIELD_TOOLTIP": "Read current date and time from the RTC as a DateTime object", + "DS3231_UNPACK_TIME_MESSAGE0": "get $[FIELD] from $[DATETIME]", + "DS3231_UNPACK_TIME_TOOLTIP": "Get a specific field from a DateTime value", + "DS3231_UNPACK_TIME_ARGS0_FIELD_DROPDOWN_YEAR": "year", + "DS3231_UNPACK_TIME_ARGS0_FIELD_DROPDOWN_MONTH": "month", + "DS3231_UNPACK_TIME_ARGS0_FIELD_DROPDOWN_DATE": "date", + "DS3231_UNPACK_TIME_ARGS0_FIELD_DROPDOWN_DAY": "day", + "DS3231_UNPACK_TIME_ARGS0_FIELD_DROPDOWN_HOURS": "hours", + "DS3231_UNPACK_TIME_ARGS0_FIELD_DROPDOWN_MINUTES": "minutes", + "DS3231_UNPACK_TIME_ARGS0_FIELD_DROPDOWN_SECONDS": "seconds", + "DS3231_CREATE_DATETIME_MESSAGE0": "create DateTime\nY $[YEAR] M $[MONTH] D $[DATE] W $[DAY]\nH $[HOURS] M $[MINUTES] S $[SECONDS]", + "DS3231_CREATE_DATETIME_TOOLTIP": "Create a DateTime object with the specified date and time values", + "DS3231_SET_TIME_MESSAGE0": "set $[SENSOR_INSTANCE] time $[DATETIME]", + "DS3231_SET_TIME_TOOLTIP": "Set date and time on the RTC", + "DS3231_READ_TEMPERATURE_MESSAGE0": "$[SENSOR_INSTANCE] read temperature (°C)", + "DS3231_READ_TEMPERATURE_TOOLTIP": "Read RTC internal temperature in degrees Celsius" +} diff --git a/ds3231/package.json b/ds3231/package.json new file mode 100644 index 0000000..31fcde9 --- /dev/null +++ b/ds3231/package.json @@ -0,0 +1,28 @@ +{ + "name": "ds3231", + "version": "0.1.0", + "author": "Jakub Andrysek", + "license": "MIT", + "description": "DS3231 real-time clock (RTC) library", + "type": "module", + "main": "dist/js/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "dependencies": { + "@types/jaculus": "0.1.0" + }, + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/ds3231/src/index.ts b/ds3231/src/index.ts new file mode 100644 index 0000000..b1dd250 --- /dev/null +++ b/ds3231/src/index.ts @@ -0,0 +1,133 @@ +import { I2C } from "i2c"; + +const DEFAULT_ADDRESS = 0x68; + +enum Reg { + Seconds = 0x00, + Minutes = 0x01, + Hours = 0x02, + Day = 0x03, + Date = 0x04, + Month = 0x05, + Year = 0x06, + TempMsb = 0x11, + TempLsb = 0x12, +} + +export type DateTime = { + year: number; + month: number; + date: number; + day: number; + hours: number; + minutes: number; + seconds: number; +}; + +function bcdToDec(value: number): number { + return (value >> 4) * 10 + (value & 0x0f); +} + +function decToBcd(value: number): number { + let v = Math.floor(value); + return ((Math.floor(v / 10) << 4) | (v % 10)) & 0xff; +} + +function decodeHours(value: number): number { + if (value & 0x40) { + let hour = bcdToDec(value & 0x1f); + if (value & 0x20) { + hour = (hour % 12) + 12; + } + return hour; + } + return bcdToDec(value & 0x3f); +} + +function encodeHours(hours: number): number { + return decToBcd(hours) & 0x3f; +} + +export class DS3231 { + readonly bus: I2C; + readonly addr: number; + + /** + * Creates a new instance of the DS3231 RTC. + * @param bus The I2C bus used for communication + * @param address Optional I2C address (default 0x68) + */ + constructor(bus: I2C, address: number = DEFAULT_ADDRESS) { + this.bus = bus; + this.addr = address; + } + + /** + * Reads date and time from the RTC. + * @returns Current date and time from the DS3231 + */ + readTime(): DateTime { + let data = this.bus.writeRead(this.addr, Reg.Seconds, 7); + + let seconds = bcdToDec(data[0] & 0x7f); + let minutes = bcdToDec(data[1] & 0x7f); + let hours = decodeHours(data[2]); + let day = bcdToDec(data[3] & 0x07); + let date = bcdToDec(data[4] & 0x3f); + + let monthRaw = data[5]; + let month = bcdToDec(monthRaw & 0x1f); + let year = bcdToDec(data[6]); + if (monthRaw & 0x80) { + year += 100; + } + + return { + year: 2000 + year, + month, + date, + day, + hours, + minutes, + seconds, + }; + } + + /** + * Sets date and time on the RTC. + * @param time Date and time to set (year 2000-2199 recommended) + */ + setTime(time: DateTime) { + let year = time.year; + if (year < 2000 || year > 2199) { + throw new Error("Year must be in range 2000-2199"); + } + + let century = year >= 2100 ? 0x80 : 0x00; + let yearTwo = year % 100; + + this.bus.writeTo(this.addr, [ + Reg.Seconds, + decToBcd(time.seconds) & 0x7f, + decToBcd(time.minutes) & 0x7f, + encodeHours(time.hours), + decToBcd(time.day), + decToBcd(time.date), + decToBcd(time.month) | century, + decToBcd(yearTwo), + ]); + } + + /** + * Reads temperature in degrees Celsius from the DS3231. + */ + readTemperature(): number { + let data = this.bus.writeRead(this.addr, Reg.TempMsb, 2); + let msb = data[0]; + if (msb & 0x80) { + msb -= 256; + } + let frac = (data[1] >> 6) * 0.25; + return msb + frac; + } +} diff --git a/ds3231/tsconfig.json b/ds3231/tsconfig.json new file mode 100644 index 0000000..ab82ab7 --- /dev/null +++ b/ds3231/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true, + "types": ["jaculus"] + } +} diff --git a/elks/.gitignore b/elks/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/elks/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/elks/README.md b/elks/README.md new file mode 100644 index 0000000..4a000ed --- /dev/null +++ b/elks/README.md @@ -0,0 +1,7 @@ +# ELKS (ESP Learning Kit Starter) + +ESP Learning Kit Starter is a development board for the ESP32-S3 microcontroller. It's an educational tool for teaching electronics and embedded programming. We employ it ourselves in courses for beginners and advanced students. + +It can be powered by a USB cable or power bank coupled directly with the board. This way you can take your ELKS with you anywhere and use it for your on-the-go projects. + +https://elks.robotikabrno.cz diff --git a/elks/blocks/elks_pins.jacly.json b/elks/blocks/elks_pins.jacly.json new file mode 100644 index 0000000..24f75c9 --- /dev/null +++ b/elks/blocks/elks_pins.jacly.json @@ -0,0 +1,230 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "elks", + "category": "elks_pins", + "name": "%T%", + "description": "%T%", + "colour": "#A51F5C", + "icon": "Palette", + "priority": 135, + "import": ["import * as elks from \"elks\";"], + "contents": [ + { + "kind": "label", + "text": "%PINS%" + }, + { + "kind": "label", + "text": "%PINS_BUTTONS%" + }, + { + "kind": "block", + "type": "elks_pins_buttons", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PIN", + "options": [ + ["%BUTTON_SW0%", "elks.Pins.SW0"], + ["%BUTTON_SW1%", "elks.Pins.SW1"], + ["%BUTTON_SW2%", "elks.Pins.SW2"] + ] + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[PIN]" + }, + { + "kind": "label", + "text": "%PINS_LEDS%" + }, + { + "kind": "block", + "type": "elks_pins_leds", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PIN", + "options": [ + ["%LED_G%", "elks.Pins.LED_G"], + ["%LED_Y%", "elks.Pins.LED_Y"], + ["%LED_R%", "elks.Pins.LED_R"], + ["%LED_B%", "elks.Pins.LED_B"] + ] + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[PIN]" + }, + { + "kind": "label", + "text": "%PINS_SMARTLEDS%" + }, + { + "kind": "block", + "type": "elks_pins_smartleds", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PIN", + "options": [ + ["%ILED_ESP32%", "elks.Pins.ILED_ESP32"], + ["%ILED_EXTERNAL%", "elks.Pins.ILED_EXTERNAL"] + ] + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[PIN]" + }, + { + "kind": "label", + "text": "%PINS_POTENTIOMETERS%" + }, + { + "kind": "block", + "type": "elks_pins_potentiometers", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PIN", + "options": [ + ["%POT0%", "elks.Pins.POT0"], + ["%POT1%", "elks.Pins.POT1"] + ] + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[PIN]" + }, + { + "kind": "label", + "text": "%PINS_BUZZER%" + }, + { + "kind": "block", + "type": "elks_pins_buzzer", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PIN", + "options": [ + ["%BUZZER_PIN_A%", "elks.Pins.BUZZER_PIN_A"], + ["%BUZZER_PIN_B%", "elks.Pins.BUZZER_PIN_B"] + ] + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[PIN]" + }, + { + "kind": "label", + "text": "%PINS_SERVOS%" + }, + { + "kind": "block", + "type": "elks_pins_servos", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PIN", + "options": [ + ["%SER0%", "elks.Pins.SER0"], + ["%SER1%", "elks.Pins.SER1"] + ] + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[PIN]" + }, + { + "kind": "label", + "text": "%PINS_I2C%" + }, + { + "kind": "block", + "type": "elks_pins_i2c", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PIN", + "options": [ + ["%SDA%", "elks.Pins.SDA"], + ["%SCL%", "elks.Pins.SCL"] + ] + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[PIN]" + }, + { + "kind": "label", + "text": "%PINS_PMOD1%" + }, + { + "kind": "block", + "type": "elks_pins_pmod1", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PIN", + "options": [ + ["%SIDE1_PIN1%", "elks.pmod0.side1.Pin1"], + ["%SIDE1_PIN2%", "elks.pmod0.side1.Pin2"], + ["%SIDE1_PIN3%", "elks.pmod0.side1.Pin3"], + ["%SIDE1_PIN4%", "elks.pmod0.side1.Pin4"], + ["%SIDE2_PIN1%", "elks.pmod0.side2.Pin1"], + ["%SIDE2_PIN2%", "elks.pmod0.side2.Pin2"], + ["%SIDE2_PIN3%", "elks.pmod0.side2.Pin3"], + ["%SIDE2_PIN4%", "elks.pmod0.side2.Pin4"] + ] + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[PIN]" + }, + { + "kind": "label", + "text": "%PINS_PMOD2%" + }, + { + "kind": "block", + "type": "elks_pins_pmod2", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PIN", + "options": [ + ["%SIDE1_PIN1%", "elks.pmod1.side1.Pin1"], + ["%SIDE1_PIN2%", "elks.pmod1.side1.Pin2"], + ["%SIDE1_PIN3%", "elks.pmod1.side1.Pin3"], + ["%SIDE1_PIN4%", "elks.pmod1.side1.Pin4"], + ["%SIDE2_PIN1%", "elks.pmod1.side2.Pin1"], + ["%SIDE2_PIN2%", "elks.pmod1.side2.Pin2"], + ["%SIDE2_PIN3%", "elks.pmod1.side2.Pin3"], + ["%SIDE2_PIN4%", "elks.pmod1.side2.Pin4"] + ] + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[PIN]" + } + ] +} diff --git a/elks/blocks/translations/cs.lang.json b/elks/blocks/translations/cs.lang.json new file mode 100644 index 0000000..28e7197 --- /dev/null +++ b/elks/blocks/translations/cs.lang.json @@ -0,0 +1,75 @@ +{ + "ELKS_PINS_NAME": "ELKS piny", + "ELKS_PINS_DESCRIPTION": "Konstanty pinů pro desku ELKS (ESP Learning Kit Starter)", + + "ELKS_PINS_LABEL_PINS": "Piny", + "ELKS_PINS_LABEL_PINS_BUTTONS": "Piny tlačítek", + "ELKS_PINS_LABEL_PINS_LEDS": "LED piny", + "ELKS_PINS_LABEL_PINS_SMARTLEDS": "SmartLED piny", + "ELKS_PINS_LABEL_PINS_POTENTIOMETERS": "Piny potenciometrů", + "ELKS_PINS_LABEL_PINS_BUZZER": "Piny bzučáku", + "ELKS_PINS_LABEL_PINS_SERVOS": "Servo piny", + "ELKS_PINS_LABEL_PINS_I2C": "I2C piny", + "ELKS_PINS_LABEL_PINS_PMOD1": "PMOD1 piny", + "ELKS_PINS_LABEL_PINS_PMOD2": "PMOD2 piny", + + "ELKS_PINS_BUTTONS_MESSAGE0": "pin $[PIN]", + "ELKS_PINS_BUTTONS_TOOLTIP": "Vrátí vybraný pin tlačítka ELKS", + "ELKS_PINS_BUTTONS_ARGS0_FIELD_DROPDOWN_BUTTON_SW0": "SW0", + "ELKS_PINS_BUTTONS_ARGS0_FIELD_DROPDOWN_BUTTON_SW1": "SW1", + "ELKS_PINS_BUTTONS_ARGS0_FIELD_DROPDOWN_BUTTON_SW2": "SW2", + + "ELKS_PINS_LEDS_MESSAGE0": "pin $[PIN]", + "ELKS_PINS_LEDS_TOOLTIP": "Vrátí vybraný LED pin ELKS", + "ELKS_PINS_LEDS_ARGS0_FIELD_DROPDOWN_LED_G": "Zelená LED", + "ELKS_PINS_LEDS_ARGS0_FIELD_DROPDOWN_LED_Y": "Žlutá LED", + "ELKS_PINS_LEDS_ARGS0_FIELD_DROPDOWN_LED_R": "Červená LED", + "ELKS_PINS_LEDS_ARGS0_FIELD_DROPDOWN_LED_B": "Modrá LED", + + "ELKS_PINS_SMARTLEDS_MESSAGE0": "pin $[PIN]", + "ELKS_PINS_SMARTLEDS_TOOLTIP": "Vrátí vybraný SmartLED pin ELKS", + "ELKS_PINS_SMARTLEDS_ARGS0_FIELD_DROPDOWN_ILED_ESP32": "SmartLED na ESP32", + "ELKS_PINS_SMARTLEDS_ARGS0_FIELD_DROPDOWN_ILED_EXTERNAL": "Externí SmartLED konektor", + + "ELKS_PINS_POTENTIOMETERS_MESSAGE0": "pin $[PIN]", + "ELKS_PINS_POTENTIOMETERS_TOOLTIP": "Vrátí vybraný pin potenciometru ELKS", + "ELKS_PINS_POTENTIOMETERS_ARGS0_FIELD_DROPDOWN_POT0": "POT0", + "ELKS_PINS_POTENTIOMETERS_ARGS0_FIELD_DROPDOWN_POT1": "POT1", + + "ELKS_PINS_BUZZER_MESSAGE0": "pin $[PIN]", + "ELKS_PINS_BUZZER_TOOLTIP": "Vrátí vybraný pin bzučáku ELKS", + "ELKS_PINS_BUZZER_ARGS0_FIELD_DROPDOWN_BUZZER_PIN_A": "Bzučák Pin A", + "ELKS_PINS_BUZZER_ARGS0_FIELD_DROPDOWN_BUZZER_PIN_B": "Bzučák Pin B", + + "ELKS_PINS_SERVOS_MESSAGE0": "pin $[PIN]", + "ELKS_PINS_SERVOS_TOOLTIP": "Vrátí vybraný servo pin ELKS", + "ELKS_PINS_SERVOS_ARGS0_FIELD_DROPDOWN_SER0": "Servo 0", + "ELKS_PINS_SERVOS_ARGS0_FIELD_DROPDOWN_SER1": "Servo 1", + + "ELKS_PINS_I2C_MESSAGE0": "pin $[PIN]", + "ELKS_PINS_I2C_TOOLTIP": "Vrátí vybraný I2C pin ELKS", + "ELKS_PINS_I2C_ARGS0_FIELD_DROPDOWN_SDA": "SDA", + "ELKS_PINS_I2C_ARGS0_FIELD_DROPDOWN_SCL": "SCL", + + "ELKS_PINS_PMOD1_MESSAGE0": "PMOD1 pin $[PIN]", + "ELKS_PINS_PMOD1_TOOLTIP": "Vrátí vybraný pin ELKS PMOD1", + "ELKS_PINS_PMOD1_ARGS0_FIELD_DROPDOWN_SIDE1_PIN1": "Side1 Pin1", + "ELKS_PINS_PMOD1_ARGS0_FIELD_DROPDOWN_SIDE1_PIN2": "Side1 Pin2", + "ELKS_PINS_PMOD1_ARGS0_FIELD_DROPDOWN_SIDE1_PIN3": "Side1 Pin3", + "ELKS_PINS_PMOD1_ARGS0_FIELD_DROPDOWN_SIDE1_PIN4": "Side1 Pin4", + "ELKS_PINS_PMOD1_ARGS0_FIELD_DROPDOWN_SIDE2_PIN1": "Side2 Pin1", + "ELKS_PINS_PMOD1_ARGS0_FIELD_DROPDOWN_SIDE2_PIN2": "Side2 Pin2", + "ELKS_PINS_PMOD1_ARGS0_FIELD_DROPDOWN_SIDE2_PIN3": "Side2 Pin3", + "ELKS_PINS_PMOD1_ARGS0_FIELD_DROPDOWN_SIDE2_PIN4": "Side2 Pin4", + + "ELKS_PINS_PMOD2_MESSAGE0": "PMOD2 pin $[PIN]", + "ELKS_PINS_PMOD2_TOOLTIP": "Vrátí vybraný pin ELKS PMOD2", + "ELKS_PINS_PMOD2_ARGS0_FIELD_DROPDOWN_SIDE1_PIN1": "Side1 Pin1", + "ELKS_PINS_PMOD2_ARGS0_FIELD_DROPDOWN_SIDE1_PIN2": "Side1 Pin2", + "ELKS_PINS_PMOD2_ARGS0_FIELD_DROPDOWN_SIDE1_PIN3": "Side1 Pin3", + "ELKS_PINS_PMOD2_ARGS0_FIELD_DROPDOWN_SIDE1_PIN4": "Side1 Pin4", + "ELKS_PINS_PMOD2_ARGS0_FIELD_DROPDOWN_SIDE2_PIN1": "Side2 Pin1", + "ELKS_PINS_PMOD2_ARGS0_FIELD_DROPDOWN_SIDE2_PIN2": "Side2 Pin2", + "ELKS_PINS_PMOD2_ARGS0_FIELD_DROPDOWN_SIDE2_PIN3": "Side2 Pin3", + "ELKS_PINS_PMOD2_ARGS0_FIELD_DROPDOWN_SIDE2_PIN4": "Side2 Pin4" +} diff --git a/elks/blocks/translations/en.lang.json b/elks/blocks/translations/en.lang.json new file mode 100644 index 0000000..653ed96 --- /dev/null +++ b/elks/blocks/translations/en.lang.json @@ -0,0 +1,75 @@ +{ + "ELKS_PINS_NAME": "ELKS Pins", + "ELKS_PINS_DESCRIPTION": "Pin constants for the ELKS (ESP Learning Kit Starter) board", + + "ELKS_PINS_LABEL_PINS": "Pins", + "ELKS_PINS_LABEL_PINS_BUTTONS": "Button Pins", + "ELKS_PINS_LABEL_PINS_LEDS": "LED Pins", + "ELKS_PINS_LABEL_PINS_SMARTLEDS": "SmartLED Pins", + "ELKS_PINS_LABEL_PINS_POTENTIOMETERS": "Potentiometer Pins", + "ELKS_PINS_LABEL_PINS_BUZZER": "Buzzer Pins", + "ELKS_PINS_LABEL_PINS_SERVOS": "Servo Pins", + "ELKS_PINS_LABEL_PINS_I2C": "I2C Pins", + "ELKS_PINS_LABEL_PINS_PMOD1": "PMOD1 Pins", + "ELKS_PINS_LABEL_PINS_PMOD2": "PMOD2 Pins", + + "ELKS_PINS_BUTTONS_MESSAGE0": "pin $[PIN]", + "ELKS_PINS_BUTTONS_TOOLTIP": "Return the selected ELKS button pin", + "ELKS_PINS_BUTTONS_ARGS0_FIELD_DROPDOWN_BUTTON_SW0": "SW0", + "ELKS_PINS_BUTTONS_ARGS0_FIELD_DROPDOWN_BUTTON_SW1": "SW1", + "ELKS_PINS_BUTTONS_ARGS0_FIELD_DROPDOWN_BUTTON_SW2": "SW2", + + "ELKS_PINS_LEDS_MESSAGE0": "pin $[PIN]", + "ELKS_PINS_LEDS_TOOLTIP": "Return the selected ELKS LED pin", + "ELKS_PINS_LEDS_ARGS0_FIELD_DROPDOWN_LED_G": "Green LED", + "ELKS_PINS_LEDS_ARGS0_FIELD_DROPDOWN_LED_Y": "Yellow LED", + "ELKS_PINS_LEDS_ARGS0_FIELD_DROPDOWN_LED_R": "Red LED", + "ELKS_PINS_LEDS_ARGS0_FIELD_DROPDOWN_LED_B": "Blue LED", + + "ELKS_PINS_SMARTLEDS_MESSAGE0": "pin $[PIN]", + "ELKS_PINS_SMARTLEDS_TOOLTIP": "Return the selected ELKS SmartLED pin", + "ELKS_PINS_SMARTLEDS_ARGS0_FIELD_DROPDOWN_ILED_ESP32": "of ESP32 SmartLED", + "ELKS_PINS_SMARTLEDS_ARGS0_FIELD_DROPDOWN_ILED_EXTERNAL": "External SmartLED Connector", + + "ELKS_PINS_POTENTIOMETERS_MESSAGE0": "pin $[PIN]", + "ELKS_PINS_POTENTIOMETERS_TOOLTIP": "Return the selected ELKS potentiometer pin", + "ELKS_PINS_POTENTIOMETERS_ARGS0_FIELD_DROPDOWN_POT0": "POT0", + "ELKS_PINS_POTENTIOMETERS_ARGS0_FIELD_DROPDOWN_POT1": "POT1", + + "ELKS_PINS_BUZZER_MESSAGE0": "pin $[PIN]", + "ELKS_PINS_BUZZER_TOOLTIP": "Return the selected ELKS buzzer pin", + "ELKS_PINS_BUZZER_ARGS0_FIELD_DROPDOWN_BUZZER_PIN_A": "Buzzer Pin A", + "ELKS_PINS_BUZZER_ARGS0_FIELD_DROPDOWN_BUZZER_PIN_B": "Buzzer Pin B", + + "ELKS_PINS_SERVOS_MESSAGE0": "pin $[PIN]", + "ELKS_PINS_SERVOS_TOOLTIP": "Return the selected ELKS servo pin", + "ELKS_PINS_SERVOS_ARGS0_FIELD_DROPDOWN_SER0": "Servo 0", + "ELKS_PINS_SERVOS_ARGS0_FIELD_DROPDOWN_SER1": "Servo 1", + + "ELKS_PINS_I2C_MESSAGE0": "pin $[PIN]", + "ELKS_PINS_I2C_TOOLTIP": "Return the selected ELKS I2C pin", + "ELKS_PINS_I2C_ARGS0_FIELD_DROPDOWN_SDA": "SDA", + "ELKS_PINS_I2C_ARGS0_FIELD_DROPDOWN_SCL": "SCL", + + "ELKS_PINS_PMOD1_MESSAGE0": "PMOD1 pin $[PIN]", + "ELKS_PINS_PMOD1_TOOLTIP": "Return the selected ELKS PMOD1 pin", + "ELKS_PINS_PMOD1_ARGS0_FIELD_DROPDOWN_SIDE1_PIN1": "Side1 Pin1", + "ELKS_PINS_PMOD1_ARGS0_FIELD_DROPDOWN_SIDE1_PIN2": "Side1 Pin2", + "ELKS_PINS_PMOD1_ARGS0_FIELD_DROPDOWN_SIDE1_PIN3": "Side1 Pin3", + "ELKS_PINS_PMOD1_ARGS0_FIELD_DROPDOWN_SIDE1_PIN4": "Side1 Pin4", + "ELKS_PINS_PMOD1_ARGS0_FIELD_DROPDOWN_SIDE2_PIN1": "Side2 Pin1", + "ELKS_PINS_PMOD1_ARGS0_FIELD_DROPDOWN_SIDE2_PIN2": "Side2 Pin2", + "ELKS_PINS_PMOD1_ARGS0_FIELD_DROPDOWN_SIDE2_PIN3": "Side2 Pin3", + "ELKS_PINS_PMOD1_ARGS0_FIELD_DROPDOWN_SIDE2_PIN4": "Side2 Pin4", + + "ELKS_PINS_PMOD2_MESSAGE0": "PMOD2 pin $[PIN]", + "ELKS_PINS_PMOD2_TOOLTIP": "Return the selected ELKS PMOD2 pin", + "ELKS_PINS_PMOD2_ARGS0_FIELD_DROPDOWN_SIDE1_PIN1": "Side1 Pin1", + "ELKS_PINS_PMOD2_ARGS0_FIELD_DROPDOWN_SIDE1_PIN2": "Side1 Pin2", + "ELKS_PINS_PMOD2_ARGS0_FIELD_DROPDOWN_SIDE1_PIN3": "Side1 Pin3", + "ELKS_PINS_PMOD2_ARGS0_FIELD_DROPDOWN_SIDE1_PIN4": "Side1 Pin4", + "ELKS_PINS_PMOD2_ARGS0_FIELD_DROPDOWN_SIDE2_PIN1": "Side2 Pin1", + "ELKS_PINS_PMOD2_ARGS0_FIELD_DROPDOWN_SIDE2_PIN2": "Side2 Pin2", + "ELKS_PINS_PMOD2_ARGS0_FIELD_DROPDOWN_SIDE2_PIN3": "Side2 Pin3", + "ELKS_PINS_PMOD2_ARGS0_FIELD_DROPDOWN_SIDE2_PIN4": "Side2 Pin4" +} diff --git a/elks/package.json b/elks/package.json new file mode 100644 index 0000000..3112c62 --- /dev/null +++ b/elks/package.json @@ -0,0 +1,28 @@ +{ + "name": "elks", + "version": "0.1.0", + "description": "ELKS (ESP Learning Kit Starter)", + "dependencies": { + "@types/jaculus": "0.1.0" + }, + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "type": "module", + "main": "dist/js/index.js", + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "types": "dist/types/index.d.ts", + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + }, + "author": "Jakub Andrysek", + "license": "MIT" +} diff --git a/elks/src/index.ts b/elks/src/index.ts new file mode 100644 index 0000000..dcca834 --- /dev/null +++ b/elks/src/index.ts @@ -0,0 +1,49 @@ +export const Pins = { + SW0: 18, + SW1: 16, + SW2: 42, + LED_G: 17, // incorrect on silkscreen (IO3 -> IO17) + LED_Y: 15, + LED_R: 45, + LED_B: 46, + POT0: 2, + POT1: 1, // analog pin + BUZZER_PIN_A: 0, + BUZZER_PIN_B: 3, + SER0: 35, + SER1: 40, + ILED_ESP32: 48, + ILED_EXTERNAL: 21, + SCL: 47, + SDA: 48, +} as const; + +export const pmod0 = { + side1: { + Pin1: 41, + Pin2: 37, + Pin3: 39, + Pin4: 5, + } as const, + side2: { + Pin1: 36, + Pin2: 38, + Pin3: 4, + Pin4: 6, + } as const, +} as const; + +export const pmod1 = { + side1: { + Pin1: 7, // analog pin + Pin2: 12, + Pin3: 13, + Pin4: 14, + } as const, + side2: { + Pin1: 10, // analog pin + Pin2: 9, // analog pin + Pin3: 8, // analog pin + Pin4: 11, + } as const, +} as const; diff --git a/elks/tsconfig.json b/elks/tsconfig.json new file mode 100644 index 0000000..3a3fbba --- /dev/null +++ b/elks/tsconfig.json @@ -0,0 +1,14 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true + } +} diff --git a/empty-template/blocks/template.json b/empty-template/blocks/template.json deleted file mode 100644 index 0967ef4..0000000 --- a/empty-template/blocks/template.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/empty-template/package.json b/empty-template/package.json deleted file mode 100644 index 6103144..0000000 --- a/empty-template/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "empty-template", - "version": "1.0.0", - "author": "cubicap", - "license": "MIT", - "description": "Minimal template for a new library", - "type": "module", - "main": "dist/js/index.js", - "types": "dist/types/index.d.ts", - "scripts": { - "build": "tsc -b", - "clean": "rm -rf dist" - }, - "jaculus": { - "blocks": "blocks" - } -} diff --git a/empty-template/src/index.ts b/empty-template/src/index.ts deleted file mode 100644 index 6d955fe..0000000 --- a/empty-template/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function add(a: number, b: number): number { - return a + b; -} diff --git a/hd44780/.gitignore b/hd44780/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/hd44780/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/hd44780/README.md b/hd44780/README.md new file mode 100644 index 0000000..948946a --- /dev/null +++ b/hd44780/README.md @@ -0,0 +1,3 @@ +# HD44780 - LCD Display Library + +https://cdn.sparkfun.com/assets/9/5/f/7/b/HD44780.pdf diff --git a/hd44780/blocks/hd44780.jacly.json b/hd44780/blocks/hd44780.jacly.json new file mode 100644 index 0000000..3b8a636 --- /dev/null +++ b/hd44780/blocks/hd44780.jacly.json @@ -0,0 +1,178 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "category": "hd44780", + "parentCategory": "peripherals", + "name": "%T%", + "description": "%T%", + "colour": "#AC8FBC", + "icon": "Monitor", + "priority": 140, + "import": ["import { HD44780 } from \"hd44780\";"], + "contents": [ + { + "kind": "block", + "type": "i2c_constructor" + }, + { + "kind": "block", + "type": "hd44780_constructor", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "hd44780_?" + }, + { + "type": "field_dropdown", + "name": "I2C_INSTANCE", + "instanceof": "i2c_inst" + } + ], + "constructs": "hd44780", + "tooltip": "%T%", + "code": "$[CONSTRUCTED_VAR_NAME] = new HD44780($[I2C_INSTANCE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "hd44780_clear", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "HD44780_INSTANCE", + "instanceof": "hd44780" + } + ], + "tooltip": "%T%", + "code": "$[HD44780_INSTANCE].clear();", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "hd44780_print", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "HD44780_INSTANCE", + "instanceof": "hd44780" + }, + { + "type": "input_value", + "name": "TEXT", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "Hello World" + } + } + } + ], + "tooltip": "%T%", + "code": "$[HD44780_INSTANCE].print($[TEXT]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "hd44780_setCursor", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "HD44780_INSTANCE", + "instanceof": "hd44780" + }, + { + "type": "input_value", + "name": "COL", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "ROW", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + } + ], + "tooltip": "%T%", + "inputsInline": true, + "code": "$[HD44780_INSTANCE].setCursor($[COL], $[ROW]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "hd44780_cursor", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "HD44780_INSTANCE", + "instanceof": "hd44780" + }, + { + "type": "field_dropdown", + "name": "CURSOR_TYPE", + "options": [ + ["%BLOCK_CURSOR%", "true"], + ["%UNDERLINE_CURSOR%", "false"] + ] + } + ], + "tooltip": "%T%", + "code": "$[HD44780_INSTANCE].cursor($[CURSOR_TYPE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "hd44780_createChar", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "HD44780_INSTANCE", + "instanceof": "hd44780" + }, + { + "type": "input_value", + "name": "CHAR_INDEX", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "CHAR_DATA", + "check": "Array" + } + ], + "tooltip": "%T%", + "inputsInline": true, + "code": "$[HD44780_INSTANCE].createChar($[CHAR_INDEX], $[CHAR_DATA]);", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/hd44780/blocks/translations/cs.lang.json b/hd44780/blocks/translations/cs.lang.json new file mode 100644 index 0000000..3364b03 --- /dev/null +++ b/hd44780/blocks/translations/cs.lang.json @@ -0,0 +1,18 @@ +{ + "HD44780_NAME": "HD44780 LCD", + "HD44780_DESCRIPTION": "Knihovna pro LCD displej HD44780 přes I2C.", + "HD44780_CONSTRUCTOR_MESSAGE0": "vytvořit $[CONSTRUCTED_VAR_NAME] HD44780 LCD na I2C sběrnici $[I2C_INSTANCE]", + "HD44780_CONSTRUCTOR_TOOLTIP": "Vytvoří instanci LCD displeje HD44780 na zadané I2C sběrnici", + "HD44780_CLEAR_MESSAGE0": "vymazat $[HD44780_INSTANCE]", + "HD44780_CLEAR_TOOLTIP": "Vymaže LCD displej a vrátí kurzor na počáteční pozici", + "HD44780_PRINT_MESSAGE0": "$[HD44780_INSTANCE] vypsat $[TEXT]", + "HD44780_PRINT_TOOLTIP": "Vypíše text na aktuální pozici kurzoru na LCD", + "HD44780_SETCURSOR_MESSAGE0": "nastavit $[HD44780_INSTANCE] kurzor na sloupec $[COL] řádek $[ROW]", + "HD44780_SETCURSOR_TOOLTIP": "Nastaví pozici kurzoru na LCD (levý horní roh = 0, 0)", + "HD44780_CURSOR_MESSAGE0": "nastavit $[HD44780_INSTANCE] styl kurzoru $[CURSOR_TYPE]", + "HD44780_CURSOR_TOOLTIP": "Nastaví styl kurzoru na blikající blok nebo podtržení", + "HD44780_CURSOR_ARGS0_FIELD_DROPDOWN_BLOCK_CURSOR": "blikající blok", + "HD44780_CURSOR_ARGS0_FIELD_DROPDOWN_UNDERLINE_CURSOR": "podtržení", + "HD44780_CREATECHAR_MESSAGE0": "$[HD44780_INSTANCE] definovat vlastní znak $[CHAR_INDEX] s daty $[CHAR_DATA]", + "HD44780_CREATECHAR_TOOLTIP": "Definuje vlastní znak (0–7) z pole 8 bajtů" +} diff --git a/hd44780/blocks/translations/en.lang.json b/hd44780/blocks/translations/en.lang.json new file mode 100644 index 0000000..ed0cfa2 --- /dev/null +++ b/hd44780/blocks/translations/en.lang.json @@ -0,0 +1,18 @@ +{ + "HD44780_NAME": "HD44780 LCD", + "HD44780_DESCRIPTION": "HD44780 LCD display library for I2C.", + "HD44780_CONSTRUCTOR_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] HD44780 LCD on I2C bus $[I2C_INSTANCE]", + "HD44780_CONSTRUCTOR_TOOLTIP": "Create an HD44780 LCD instance on the specified I2C bus", + "HD44780_CLEAR_MESSAGE0": "clear $[HD44780_INSTANCE]", + "HD44780_CLEAR_TOOLTIP": "Clear the LCD display and return cursor to home position", + "HD44780_PRINT_MESSAGE0": "$[HD44780_INSTANCE] print $[TEXT]", + "HD44780_PRINT_TOOLTIP": "Print text at the current cursor position on the LCD", + "HD44780_SETCURSOR_MESSAGE0": "set $[HD44780_INSTANCE] cursor to column $[COL] row $[ROW]", + "HD44780_SETCURSOR_TOOLTIP": "Set the cursor position on the LCD (top left = 0, 0)", + "HD44780_CURSOR_MESSAGE0": "set $[HD44780_INSTANCE] cursor style $[CURSOR_TYPE]", + "HD44780_CURSOR_TOOLTIP": "Set the cursor style to blinking block or underline", + "HD44780_CURSOR_ARGS0_FIELD_DROPDOWN_BLOCK_CURSOR": "blinking block", + "HD44780_CURSOR_ARGS0_FIELD_DROPDOWN_UNDERLINE_CURSOR": "underline", + "HD44780_CREATECHAR_MESSAGE0": "$[HD44780_INSTANCE] define custom character $[CHAR_INDEX] with data $[CHAR_DATA]", + "HD44780_CREATECHAR_TOOLTIP": "Define a custom character (0–7) from an array of 8 bytes" +} diff --git a/hd44780/package.json b/hd44780/package.json new file mode 100644 index 0000000..a1a7086 --- /dev/null +++ b/hd44780/package.json @@ -0,0 +1,28 @@ +{ + "name": "hd44780", + "version": "0.1.0", + "author": "Jakub Andrysek", + "license": "MIT", + "description": "HD44780 LCD display library", + "type": "module", + "main": "dist/js/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "dependencies": { + "@types/jaculus": "0.1.0" + }, + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/hd44780/src/index.ts b/hd44780/src/index.ts new file mode 100644 index 0000000..59436e3 --- /dev/null +++ b/hd44780/src/index.ts @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2013 Gordon Williams, Pur3 Ltd. See the file LICENSE for copying permission. + * modified from Espruino's VL53L0X module (https://www.espruino.com/HD44780) + */ + +import { I2C } from "i2c"; + +function getData(x: number, c?: number): number[] { + var a = (x & 0xf0) | 8 | (c === undefined ? 1 : 0); + var b = ((x << 4) & 0xf0) | 8 | (c === undefined ? 1 : 0); + return [a, a, a | 4, a | 4, a, a, b, b, b | 4, b | 4, b, b]; +} + +export class HD44780 { + private readonly addr: number; + + constructor( + private readonly i2c: I2C, + addr?: number, + ) { + this.addr = addr || 0x27; + this.init(); + } + + private init() { + this.write(0x33, 1); + this.write(0x32, 1); + this.write(0x28, 1); + this.write(0x0c, 1); + this.write(0x06, 1); + this.write(0x01, 1); + } + + private write(x: number, c?: number) { + this.i2c.writeTo(this.addr, getData(x, c)); + } + + // clear screen + public clear() { + this.write(0x01, 1); + } + + // print text + public print(str: string) { + let buffer: number[][] = []; + for (var i = 0; i < str.length; i++) + buffer.push(getData(str.charCodeAt(i))); + this.i2c.writeTo(this.addr, buffer.flat()); + } + + // flashing block for the current cursor, or underline + public cursor(block: boolean) { + this.write(block ? 0x0f : 0x0e, 1); + } + + // set cursor pos, top left = 0,0 + public setCursor(x: number, y: number) { + var l = [0x00, 0x40, 0x14, 0x54]; + this.write(0x80 | (l[y] + x), 1); + } + + // set special character 0..7, data is an array(8) of bytes, and then return to home addr + public createChar(characterIndex: number, charData: number[]) { + this.write(0x40 | ((characterIndex & 7) << 3), 1); + for (var i = 0; i < 8; i++) this.write(charData[i]); + this.write(0x80, 1); + } +} diff --git a/hd44780/tsconfig.json b/hd44780/tsconfig.json new file mode 100644 index 0000000..ab82ab7 --- /dev/null +++ b/hd44780/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true, + "types": ["jaculus"] + } +} diff --git a/jacly-meta-blocks/.gitignore b/jacly-meta-blocks/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/jacly-meta-blocks/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/jacly-meta-blocks/README.md b/jacly-meta-blocks/README.md new file mode 100644 index 0000000..82a87e1 --- /dev/null +++ b/jacly-meta-blocks/README.md @@ -0,0 +1,4 @@ +# Jacly Meta Blocks + +This is a collection of internal base blocks used by the Jacly. +Those blocks contains base Blockly blocks definitions and generators. diff --git a/jacly-meta-blocks/blocks/functions.jacly.json b/jacly-meta-blocks/blocks/functions.jacly.json new file mode 100644 index 0000000..b7063c4 --- /dev/null +++ b/jacly-meta-blocks/blocks/functions.jacly.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "jacly-meta-blocks", + "category": "functions", + "name": "%T%", + "description": "%T%", + "colour": "#FF6680", + "icon": "SquareFunction", + "priority": 60, + "custom": "PROCEDURE" +} diff --git a/jacly-meta-blocks/blocks/json.jacly.json b/jacly-meta-blocks/blocks/json.jacly.json new file mode 100644 index 0000000..ecc5845 --- /dev/null +++ b/jacly-meta-blocks/blocks/json.jacly.json @@ -0,0 +1,251 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "jacly-meta-blocks", + "category": "json", + "name": "%T%", + "description": "%T%", + "colour": "#d81b60", + "priority": 134, + "contents": [ + { + "kind": "block", + "type": "json_object_create", + "inputs": { + "PAIRS": { + "block": { + "type": "json_object_entry" + } + } + } + }, + { + "kind": "block", + "type": "json_object_to_text", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "VALUE" + } + ], + "output": "String", + "tooltip": "%T%", + "code": "JSON.stringify($[VALUE])" + }, + { + "kind": "block", + "type": "json_text_to_object", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "TEXT", + "check": "String" + } + ], + "output": "JsonObject", + "tooltip": "%T%", + "code": "JSON.parse($[TEXT])", + "inputs": { + "TEXT": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "{\"key\":\"value\"}" + } + } + } + } + }, + { + "kind": "block", + "type": "json_object_clear", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "OBJECT", + "check": "JsonObject" + } + ], + "previousStatement": null, + "nextStatement": null, + "inputsInline": true, + "tooltip": "%T%", + "code": "(() => { const __jaclyObject = $[OBJECT]; Object.keys(__jaclyObject).forEach((__jaclyKey) => delete __jaclyObject[__jaclyKey]); })();" + }, + { + "kind": "block", + "type": "json_object_contains_key", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "OBJECT", + "check": "JsonObject" + }, + { + "type": "input_value", + "name": "KEY" + } + ], + "output": "Boolean", + "inputsInline": true, + "tooltip": "%T%", + "code": "Object.prototype.hasOwnProperty.call($[OBJECT], $[KEY])", + "inputs": { + "KEY": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "key" + } + } + } + } + }, + { + "kind": "block", + "type": "json_object_get_key", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "KEY" + }, + { + "type": "input_value", + "name": "OBJECT", + "check": "JsonObject" + } + ], + "output": null, + "inputsInline": true, + "tooltip": "%T%", + "code": "($[OBJECT][$[KEY]])", + "inputs": { + "KEY": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "key" + } + } + } + } + }, + { + "kind": "block", + "type": "json_object_set_key", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "OBJECT", + "check": "JsonObject" + }, + { + "type": "input_value", + "name": "KEY" + }, + { + "type": "input_value", + "name": "VALUE" + } + ], + "previousStatement": null, + "nextStatement": null, + "inputsInline": true, + "tooltip": "%T%", + "code": "$[OBJECT][$[KEY]] = $[VALUE];", + "inputs": { + "KEY": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "key" + } + } + }, + "VALUE": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "value" + } + } + } + } + }, + { + "kind": "block", + "type": "json_object_delete_key", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "OBJECT", + "check": "JsonObject" + }, + { + "type": "input_value", + "name": "KEY" + } + ], + "previousStatement": null, + "nextStatement": null, + "inputsInline": true, + "tooltip": "%T%", + "code": "delete $[OBJECT][$[KEY]];", + "inputs": { + "KEY": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "key" + } + } + } + } + }, + { + "kind": "block", + "type": "json_object_entry", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "KEY" + }, + { + "type": "input_value", + "name": "VALUE" + } + ], + "previousStatement": "JsonObjectEntry", + "nextStatement": "JsonObjectEntry", + "inputsInline": true, + "hideInToolbox": true, + "tooltip": "%T%", + "code": "[$[KEY]]: $[VALUE],", + "inputs": { + "KEY": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "key" + } + } + }, + "VALUE": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "value" + } + } + } + } + } + ] +} diff --git a/jacly-meta-blocks/blocks/lists.jacly.json b/jacly-meta-blocks/blocks/lists.jacly.json new file mode 100644 index 0000000..68e9d20 --- /dev/null +++ b/jacly-meta-blocks/blocks/lists.jacly.json @@ -0,0 +1,109 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "jacly-meta-blocks", + "category": "lists", + "name": "%T%", + "description": "%T%", + "colour": "#e65722", + "icon": "ListOrdered", + "priority": 40, + "contents": [ + { + "kind": "block", + "type": "lists_create_with" + }, + { + "kind": "block", + "type": "lists_create_empty", + "gap": "32" + }, + { + "kind": "block", + "type": "lists_repeat", + "inputs": { + "NUM": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 5 + } + } + } + } + }, + { + "kind": "block", + "type": "lists_isEmpty" + }, + { + "kind": "block", + "type": "lists_length" + }, + { + "kind": "block", + "type": "lists_indexOf", + "inputs": { + "VALUE": { + "block": { + "type": "variables_get" + } + } + } + }, + { + "kind": "block", + "type": "lists_getIndex", + "inputs": { + "VALUE": { + "block": { + "type": "variables_get" + } + } + } + }, + { + "kind": "block", + "type": "lists_setIndex", + "inputs": { + "LIST": { + "block": { + "type": "variables_get" + } + } + } + }, + { + "kind": "block", + "type": "lists_getSublist", + "inputs": { + "LIST": { + "block": { + "type": "variables_get" + } + } + } + }, + { + "kind": "block", + "type": "lists_split", + "inputs": { + "DELIM": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "," + } + } + } + } + }, + { + "kind": "block", + "type": "lists_sort" + }, + { + "kind": "block", + "type": "lists_reverse" + } + ] +} diff --git a/jacly-meta-blocks/blocks/logic.jacly.json b/jacly-meta-blocks/blocks/logic.jacly.json new file mode 100644 index 0000000..2b78a9a --- /dev/null +++ b/jacly-meta-blocks/blocks/logic.jacly.json @@ -0,0 +1,145 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "jacly-meta-blocks", + "category": "logic", + "name": "%T%", + "description": "%T%", + "colour": "#00a4a6", + "icon": "Scale", + "priority": 25, + "contents": [ + { + "kind": "block", + "type": "controls_if", + "inputs": { + "IF0": { + "shadow": { + "type": "logic_boolean", + "fields": { + "BOOL": "TRUE" + } + } + } + } + }, + { + "kind": "block", + "type": "logic_compare" + }, + { + "kind": "block", + "type": "logic_operation", + "inputs": { + "A": { + "shadow": { + "type": "logic_boolean", + "fields": { + "BOOL": "TRUE" + } + } + }, + "B": { + "shadow": { + "type": "logic_boolean", + "fields": { + "BOOL": "TRUE" + } + } + } + } + }, + { + "kind": "block", + "type": "logic_negate", + "inputs": { + "BOOL": { + "shadow": { + "type": "logic_boolean", + "fields": { + "BOOL": "TRUE" + } + } + } + } + }, + { + "kind": "block", + "type": "logic_boolean" + }, + { + "kind": "block", + "type": "logic_ternary", + "inputs": { + "IF": { + "shadow": { + "type": "logic_boolean", + "fields": { + "BOOL": "TRUE" + } + } + } + } + }, + { + "kind": "block", + "type": "logic_to_boolean", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "VALUE" + } + ], + "output": "Boolean", + "tooltip": "%T%", + "code": "Boolean($[VALUE])" + }, + { + "kind": "block", + "type": "logic_calc", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CALC", + "text": "(a && b) || c" + }, + { + "type": "input_value", + "name": "A", + "check": "Boolean", + "shadow": { + "type": "logic_boolean" + } + }, + { + "type": "input_value", + "name": "B", + "check": "Boolean", + "shadow": { + "type": "logic_boolean" + } + }, + { + "type": "input_value", + "name": "C", + "check": "Boolean", + "shadow": { + "type": "logic_boolean" + } + }, + { + "type": "input_value", + "name": "D", + "check": "Boolean", + "shadow": { + "type": "logic_boolean" + } + } + ], + "output": "Boolean", + "tooltip": "%T%", + "code": "((a, b, c, d) => ($[CALC]))($[A], $[B], $[C], $[D])" + } + ] +} diff --git a/jacly-meta-blocks/blocks/loops.jacly.json b/jacly-meta-blocks/blocks/loops.jacly.json new file mode 100644 index 0000000..54cd0bf --- /dev/null +++ b/jacly-meta-blocks/blocks/loops.jacly.json @@ -0,0 +1,78 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "jacly-meta-blocks", + "category": "loops", + "name": "%T%", + "description": "%T%", + "colour": "#00aa00", + "icon": "Repeat", + "priority": 20, + "contents": [ + { + "kind": "block", + "type": "controls_repeat_ext", + "inputs": { + "TIMES": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 10 + } + } + } + } + }, + { + "kind": "block", + "type": "controls_whileUntil", + "inputs": { + "BOOL": { + "shadow": { + "type": "logic_boolean", + "fields": { + "BOOL": "TRUE" + } + } + } + } + }, + { + "kind": "block", + "type": "controls_for", + "inputs": { + "FROM": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + }, + "TO": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 10 + } + } + }, + "BY": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + } + } + }, + { + "kind": "block", + "type": "controls_forEach" + }, + { + "kind": "block", + "type": "controls_flow_statements" + } + ] +} diff --git a/jacly-meta-blocks/blocks/math.jacly.json b/jacly-meta-blocks/blocks/math.jacly.json new file mode 100644 index 0000000..271e521 --- /dev/null +++ b/jacly-meta-blocks/blocks/math.jacly.json @@ -0,0 +1,454 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "jacly-meta-blocks", + "category": "math", + "name": "%T%", + "description": "%T%", + "colour": "#9400d3", + "icon": "Calculator", + "priority": 35, + "contents": [ + { + "kind": "block", + "type": "math_number", + "fields": { + "NUM": 42 + } + }, + { + "kind": "block", + "type": "math_arithmetic", + "inputs": { + "A": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + }, + "B": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + } + } + }, + { + "kind": "block", + "type": "math_single", + "inputs": { + "NUM": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 9 + } + } + } + } + }, + { + "kind": "block", + "type": "math_trig", + "inputs": { + "NUM": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 45 + } + } + } + } + }, + { + "kind": "block", + "type": "math_constant" + }, + { + "kind": "block", + "type": "math_number_property", + "inputs": { + "NUMBER_TO_CHECK": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + } + } + }, + { + "kind": "block", + "type": "math_number_between", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "VALUE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "LOW", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": -10 + } + } + }, + { + "type": "input_value", + "name": "HIGH", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 10 + } + } + } + ], + "inputsInline": true, + "output": "Boolean", + "tooltip": "%T%", + "code": "($[VALUE] >= $[LOW] && $[VALUE] <= $[HIGH])" + }, + { + "kind": "block", + "type": "math_round", + "fields": { + "OP": "ROUND" + }, + "inputs": { + "NUM": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 3.1 + } + } + } + } + }, + { + "kind": "block", + "type": "math_round_decimal", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "VALUE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0.42 + } + } + }, + { + "type": "input_value", + "name": "DECIMALS", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + } + ], + "inputsInline": true, + "output": "Number", + "tooltip": "%T%", + "code": "parseFloat(($[VALUE]).toFixed($[DECIMALS]))" + }, + { + "kind": "block", + "type": "math_on_list", + "fields": { + "OP": "SUM" + } + }, + { + "kind": "block", + "type": "math_modulo", + "inputs": { + "DIVIDEND": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 64 + } + } + }, + "DIVISOR": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 10 + } + } + } + } + }, + { + "kind": "block", + "type": "math_constrain", + "inputs": { + "VALUE": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 50 + } + } + }, + "LOW": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + }, + "HIGH": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 100 + } + } + } + } + }, + { + "kind": "block", + "type": "math_random_int", + "inputs": { + "FROM": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + }, + "TO": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 100 + } + } + } + } + }, + { + "kind": "block", + "type": "math_random_float" + }, + { + "kind": "block", + "type": "math_atan2", + "inputs": { + "X": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + }, + "Y": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + } + } + }, + { + "kind": "block", + "type": "math_change", + "inputs": { + "DELTA": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + } + } + }, + { + "kind": "block", + "type": "math_to_number", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "VALUE" + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "Number($[VALUE])" + }, + { + "kind": "block", + "type": "math_to_integer", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "VALUE" + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "parseInt($[VALUE])" + }, + { + "kind": "block", + "type": "math_map_value", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "VALUE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "FROM_LOW", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "FROM_HIGH", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1023 + } + } + }, + { + "type": "input_value", + "name": "TO_LOW", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "TO_HIGH", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 255 + } + } + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "(($[VALUE] - $[FROM_LOW]) * ($[TO_HIGH] - $[TO_LOW]) / ($[FROM_HIGH] - $[FROM_LOW])) + $[TO_LOW]" + }, + { + "kind": "block", + "type": "math_calc", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CALC", + "text": "a + b * c" + }, + { + "type": "input_value", + "name": "A", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + }, + { + "type": "input_value", + "name": "B", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 2 + } + } + }, + { + "type": "input_value", + "name": "C", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 3 + } + } + }, + { + "type": "input_value", + "name": "D", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 4 + } + } + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "((a, b, c, d) => ($[CALC]))($[A], $[B], $[C], $[D])" + } + ] +} diff --git a/jacly-meta-blocks/blocks/text.jacly.json b/jacly-meta-blocks/blocks/text.jacly.json new file mode 100644 index 0000000..559c2b3 --- /dev/null +++ b/jacly-meta-blocks/blocks/text.jacly.json @@ -0,0 +1,157 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "jacly-meta-blocks", + "category": "text", + "name": "%T%", + "description": "%T%", + "colour": "#b8860b", + "icon": "BookType", + "priority": 30, + "contents": [ + { + "kind": "block", + "type": "text" + }, + { + "kind": "block", + "type": "text_join", + "inputs": { + "ADD0": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "" + } + } + }, + "ADD1": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "" + } + } + } + } + }, + { + "kind": "block", + "type": "text_append", + "inputs": { + "TEXT": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "Hello" + } + } + } + } + }, + { + "kind": "block", + "type": "text_length", + "inputs": { + "VALUE": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "abc" + } + } + } + } + }, + { + "kind": "block", + "type": "text_isEmpty", + "inputs": { + "VALUE": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "" + } + } + } + } + }, + { + "kind": "block", + "type": "text_indexOf", + "inputs": { + "VALUE": { + "block": { + "type": "variables_get" + } + }, + "FIND": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "abc" + } + } + } + } + }, + { + "kind": "block", + "type": "text_charAt", + "inputs": { + "VALUE": { + "block": { + "type": "variables_get" + } + }, + "AT": { + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + } + } + }, + { + "kind": "block", + "type": "text_getSubstring", + "inputs": { + "STRING": { + "block": { + "type": "variables_get" + } + } + } + }, + { + "kind": "block", + "type": "text_changeCase", + "inputs": { + "TEXT": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "abc" + } + } + } + } + }, + { + "kind": "block", + "type": "text_to_string", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "VALUE" + } + ], + "output": "String", + "tooltip": "%T%", + "code": "String($[VALUE])" + } + ] +} diff --git a/jacly-meta-blocks/blocks/text_ext.jacly.json b/jacly-meta-blocks/blocks/text_ext.jacly.json new file mode 100644 index 0000000..d1d1368 --- /dev/null +++ b/jacly-meta-blocks/blocks/text_ext.jacly.json @@ -0,0 +1,203 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "jacly-meta-blocks", + "category": "text_ext", + "parentCategory": "text", + "name": "%T%", + "description": "%T%", + "colour": "#b8860b", + "icon": "BookType", + "priority": 30, + "contents": [ + { + "kind": "block", + "type": "text_trim", + "inputs": { + "TEXT": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "abc" + } + } + } + } + }, + { + "kind": "block", + "type": "text_count", + "inputs": { + "SUB": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "a" + } + } + }, + "TEXT": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "abc" + } + } + } + } + }, + { + "kind": "block", + "type": "text_replace", + "inputs": { + "FROM": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "a" + } + } + }, + "TO": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "b" + } + } + }, + "TEXT": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "abc" + } + } + } + } + }, + { + "kind": "block", + "type": "text_reverse", + "inputs": { + "TEXT": { + "shadow": { + "type": "text", + "fields": { + "TEXT": "abc" + } + } + } + } + }, + { + "kind": "block", + "type": "text_split_part", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "TEXT", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "a,b,c" + } + } + }, + { + "type": "input_value", + "name": "SEP", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "," + } + } + }, + { + "type": "input_value", + "name": "INDEX", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1 + } + } + } + ], + "inputsInline": true, + "output": "String", + "tooltip": "%T%", + "code": "($[TEXT].split($[SEP])[$[INDEX]] ?? \"\")" + }, + { + "kind": "block", + "type": "text_starts_with", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "TEXT", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "hello" + } + } + }, + { + "type": "input_value", + "name": "PREFIX", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "he" + } + } + } + ], + "inputsInline": true, + "output": "Boolean", + "tooltip": "%T%", + "code": "$[TEXT].startsWith($[PREFIX])" + }, + { + "kind": "block", + "type": "text_ends_with", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "TEXT", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "hello" + } + } + }, + { + "type": "input_value", + "name": "SUFFIX", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "lo" + } + } + } + ], + "inputsInline": true, + "output": "Boolean", + "tooltip": "%T%", + "code": "$[TEXT].endsWith($[SUFFIX])" + } + ] +} diff --git a/jacly-meta-blocks/blocks/translations/cs.lang.json b/jacly-meta-blocks/blocks/translations/cs.lang.json new file mode 100644 index 0000000..6eac46e --- /dev/null +++ b/jacly-meta-blocks/blocks/translations/cs.lang.json @@ -0,0 +1,100 @@ +{ + "LISTS_NAME": "Seznamy", + "LISTS_DESCRIPTION": "Bloky pro práci se seznamy.", + + "LOGIC_NAME": "Logika", + "LOGIC_DESCRIPTION": "Logické bloky pro rozhodování a porovnávání.", + + "LOOPS_NAME": "Smyčky", + "LOOPS_DESCRIPTION": "Bloky pro opakování akcí.", + + "MATH_NAME": "Matematika", + "MATH_DESCRIPTION": "Matematické bloky pro výpočty a operace.", + + "FUNCTIONS_NAME": "Funkce", + "FUNCTIONS_DESCRIPTION": "Bloky pro definování a volání funkcí.", + + "TEXT_NAME": "Text", + "TEXT_DESCRIPTION": "Bloky pro práci s textovými řetězci.", + + "TEXT_EXT_NAME": "Rozšířený text", + "TEXT_EXT_DESCRIPTION": "Bloky pro práci s textovými řetězci.", + + "VARIABLES_NAME": "Proměnné", + "VARIABLES_DESCRIPTION": "Bloky pro vytváření a správu proměnných.", + + "CONSTANTS_NAME": "Konstanty", + "CONSTANTS_DESCRIPTION": "Bloky pro vytváření a správu konstant.", + + "RENAME_CONSTANT": "Přejmenovat konstantu...", + "RENAME_CONSTANT_TITLE": "Přejmenuj všech '%1' konstant na:", + "NEW_CONSTANT": "Vytvořit konstantu...", + "NEW_CONSTANT_TYPE_TITLE": "Nový typ konstanty:", + "NEW_CONSTANT_TITLE": "Nový název konstanty:", + "CONSTANT_ALREADY_EXISTS": "Konstanta jménem '%1' již existuje.", + "CONSTANT_ALREADY_EXISTS_FOR_ANOTHER_TYPE": "Konstanta pojmenovaná jako '%1' již existuje pro jiný typ: '%2'.", + "CONSTANT_ALREADY_EXISTS_FOR_A_PARAMETER": "Konstanta pojmenovaná '%1' již jako parametr v proceduře '%2' existuje.", + "DELETE_CONSTANT_CONFIRMATION": "Odstranit %1 použití konstanty '%2'?", + "CANNOT_DELETE_CONSTANT_PROCEDURE": "Konstantu '%1' není možné odstranit, protože je součástí definice funkce '%2'.", + "DELETE_CONSTANT": "Odstranit konstantu '%1'", + + "CONSTANTS_GET_HELPURL": "https://cs.wikipedia.org/wiki/Konstanta", + "CONSTANTS_GET_TOOLTIP": "Vrátí hodnotu této konstanty.", + "CONSTANTS_GET_CREATE_SET": "Vytvořit \"nastavit %1\"", + "CONSTANTS_SET_HELPURL": "https://cs.wikipedia.org/wiki/Konstanta", + "CONSTANTS_SET": "nastavit konst. %1 na %2", + "CONSTANTS_SET_TOOLTIP": "Nastaví tuto konstantu, aby se rovnala vstupu.", + "CONSTANTS_SET_CREATE_GET": "Vytvořit \"získej %1\"", + "CONSTANTS_SET_DUPLICATE_WARNING": "Tato konstanta je již nastavena jiným blokem.", + + "LOGIC_TO_BOOLEAN_MESSAGE0": "na logickou hodnotu", + "LOGIC_TO_BOOLEAN_TOOLTIP": "Převede hodnotu na Boolean.", + "LOGIC_CALC_MESSAGE0": "vyhodnotit $[CALC]\na: $[A]\nb: $[B]\nc: $[C]\nd: $[D]", + "LOGIC_CALC_TOOLTIP": "Vyhodnotí logický výraz pomocí vstupních hodnot. Použij a, b, c, d jako zástupce pro vstupy.", + + "MATH_ROUND_DECIMAL_MESSAGE0": "zaokrouhlit $[VALUE] na $[DECIMALS] desetinných míst", + "MATH_ROUND_DECIMAL_TOOLTIP": "Zaokrouhlí číslo na zadaný počet desetinných míst.", + "MATH_NUMBER_BETWEEN_MESSAGE0": "je $[VALUE] mezi $[LOW] a $[HIGH] ?", + "MATH_NUMBER_BETWEEN_TOOLTIP": "Vrátí true, pokud je číslo mezi dolní a horní hranicí, včetně.", + + "MATH_TO_NUMBER_MESSAGE0": "na číslo", + "MATH_TO_NUMBER_TOOLTIP": "Převede hodnotu na číslo.", + "MATH_TO_INTEGER_MESSAGE0": "na celé číslo", + "MATH_TO_INTEGER_TOOLTIP": "Převede hodnotu na celé číslo.", + + "TEXT_TO_STRING_MESSAGE0": "na text", + "TEXT_TO_STRING_TOOLTIP": "Převede hodnotu na text.", + + "TEXT_SPLIT_PART_MESSAGE0": "index $[INDEX] z $[TEXT] rozdělená podle $[SEP]", + "TEXT_SPLIT_PART_TOOLTIP": "Rozdělí text separátorem a vrátí n-tou část (počítáno od 0). Pokud část neexistuje, vrátí prázdný řetězec.", + "TEXT_STARTS_WITH_MESSAGE0": "$[TEXT] začíná na $[PREFIX]", + "TEXT_STARTS_WITH_TOOLTIP": "Vrátí true, pokud text začíná zadaným prefixem.", + "TEXT_ENDS_WITH_MESSAGE0": "$[TEXT] končí na $[SUFFIX]", + "TEXT_ENDS_WITH_TOOLTIP": "Vrátí true, pokud text končí zadaným sufixem.", + + "MATH_MAP_VALUE_MESSAGE0": "mapovat $[VALUE] z rozsahu [$[FROM_LOW], $[FROM_HIGH]] do rozsahu [$[TO_LOW], $[TO_HIGH]]", + "MATH_MAP_VALUE_TOOLTIP": "Mapuje hodnotu z jednoho rozsahu do druhého.", + "MATH_CALC_MESSAGE0": "vyhodnotit $[CALC]\na: $[A]\nb: $[B]\nc: $[C]\nd: $[D]", + "MATH_CALC_TOOLTIP": "Vyhodnotí matematický výraz pomocí vstupních hodnot. Použij a, b, c, d jako zástupce pro vstupy.", + + "JSON_NAME": "JSON", + "JSON_DESCRIPTION": "Bloky pro práci s JSON textem a objektovými hodnotami.", + "JSON_OBJECT_CREATE_MESSAGE0": "vytvořit objekt", + "JSON_OBJECT_CREATE_TOOLTIP": "Vytvoří JSON objekt. Pomocí + přidáš dvojice klíč/hodnota.", + "JSON_OBJECT_TO_TEXT_MESSAGE0": "JSON na text $[VALUE]", + "JSON_OBJECT_TO_TEXT_TOOLTIP": "Převede hodnotu na JSON text.", + "JSON_TEXT_TO_OBJECT_MESSAGE0": "text na JSON $[TEXT]", + "JSON_TEXT_TO_OBJECT_TOOLTIP": "Zpracuje text jako JSON objekt.", + "JSON_OBJECT_CLEAR_MESSAGE0": "vymazat objekt $[OBJECT]", + "JSON_OBJECT_CLEAR_TOOLTIP": "Odstraní z objektu všechny klíče.", + "JSON_OBJECT_CONTAINS_KEY_MESSAGE0": "objekt $[OBJECT] obsahuje klíč $[KEY]", + "JSON_OBJECT_CONTAINS_KEY_TOOLTIP": "Zkontroluje, zda objekt obsahuje klíč.", + "JSON_OBJECT_GET_KEY_MESSAGE0": "získat klíč $[KEY] v objektu $[OBJECT]", + "JSON_OBJECT_GET_KEY_TOOLTIP": "Vrátí hodnotu uloženou pod klíčem.", + "JSON_OBJECT_SET_KEY_MESSAGE0": "v objektu $[OBJECT] nastavit klíč $[KEY] na hodnotu $[VALUE]", + "JSON_OBJECT_SET_KEY_TOOLTIP": "Nastaví klíči hodnotu.", + "JSON_OBJECT_DELETE_KEY_MESSAGE0": "v objektu $[OBJECT] smazat klíč $[KEY]", + "JSON_OBJECT_DELETE_KEY_TOOLTIP": "Smaže klíč z objektu.", + "JSON_OBJECT_ENTRY_MESSAGE0": "klíč $[KEY] hodnota $[VALUE]", + "JSON_OBJECT_ENTRY_TOOLTIP": "Jedna dvojice klíč/hodnota uvnitř JSON objektu." +} diff --git a/jacly-meta-blocks/blocks/translations/en.lang.json b/jacly-meta-blocks/blocks/translations/en.lang.json new file mode 100644 index 0000000..fd4d688 --- /dev/null +++ b/jacly-meta-blocks/blocks/translations/en.lang.json @@ -0,0 +1,100 @@ +{ + "LISTS_NAME": "Lists", + "LISTS_DESCRIPTION": "List blocks for performing list operations.", + + "LOGIC_NAME": "Logic", + "LOGIC_DESCRIPTION": "Logic control blocks for making decisions and comparisons.", + + "LOOPS_NAME": "Loops", + "LOOPS_DESCRIPTION": "Loop blocks for repeating actions.", + + "MATH_NAME": "Math", + "MATH_DESCRIPTION": "Math blocks for performing calculations and operations.", + + "FUNCTIONS_NAME": "Functions", + "FUNCTIONS_DESCRIPTION": "Procedure blocks for defining and calling functions.", + + "TEXT_NAME": "Text", + "TEXT_DESCRIPTION": "Blocks for manipulating text strings.", + + "TEXT_EXT_NAME": "Extended Text", + "TEXT_EXT_DESCRIPTION": "Blocks for manipulating text strings.", + + "VARIABLES_NAME": "Variables", + "VARIABLES_DESCRIPTION": "Variable blocks for creating and managing variables.", + + "CONSTANTS_NAME": "Constants", + "CONSTANTS_DESCRIPTION": "Constant blocks for creating and managing constants.", + + "RENAME_CONSTANT": "Rename constant...", + "RENAME_CONSTANT_TITLE": "Rename all '%1' constants to:", + "NEW_CONSTANT": "Create constant...", + "NEW_CONSTANT_TYPE_TITLE": "New constant type:", + "NEW_CONSTANT_TITLE": "New constant name:", + "CONSTANT_ALREADY_EXISTS": "A constant named '%1' already exists.", + "CONSTANT_ALREADY_EXISTS_FOR_ANOTHER_TYPE": "A constant named '%1' already exists for another type: '%2'.", + "CONSTANT_ALREADY_EXISTS_FOR_A_PARAMETER": "A constant named '%1' already exists as a parameter in the procedure '%2'.", + "DELETE_CONSTANT_CONFIRMATION": "Delete %1 uses of the '%2' constant?", + "CANNOT_DELETE_CONSTANT_PROCEDURE": "Can't delete the constant '%1' because it's part of the definition of the function '%2'", + "DELETE_CONSTANT": "Delete the '%1' constant", + + "CONSTANTS_GET_HELPURL": "https://en.wikipedia.org/wiki/Constant_(mathematics)", + "CONSTANTS_GET_TOOLTIP": "Returns the value of this constant.", + "CONSTANTS_GET_CREATE_SET": "Create 'set %1'", + "CONSTANTS_SET_HELPURL": "https://en.wikipedia.org/wiki/Constant_(mathematics)", + "CONSTANTS_SET": "set const %1 to %2", + "CONSTANTS_SET_TOOLTIP": "Sets this constant to be equal to the input.", + "CONSTANTS_SET_CREATE_GET": "Create 'get %1'", + "CONSTANTS_SET_DUPLICATE_WARNING": "This constant is already set by another block.", + + "LOGIC_TO_BOOLEAN_MESSAGE0": "to Boolean", + "LOGIC_TO_BOOLEAN_TOOLTIP": "Convert a value to a Boolean.", + "LOGIC_CALC_MESSAGE0": "evaluate $[CALC]\na: $[A]\nb: $[B]\nc: $[C]\nd: $[D]", + "LOGIC_CALC_TOOLTIP": "Evaluate a logical expression using the input values. Use a, b, c, d as placeholders for the inputs.", + + "MATH_ROUND_DECIMAL_MESSAGE0": "round $[VALUE] to $[DECIMALS] decimal places", + "MATH_ROUND_DECIMAL_TOOLTIP": "Round a number to the specified number of decimal places.", + "MATH_NUMBER_BETWEEN_MESSAGE0": "is $[VALUE] in between $[LOW] and $[HIGH] ?", + "MATH_NUMBER_BETWEEN_TOOLTIP": "Returns true if the number is between the lower and upper bounds, inclusive.", + + "MATH_TO_NUMBER_MESSAGE0": "to Number", + "MATH_TO_NUMBER_TOOLTIP": "Convert a value to a Number.", + "MATH_TO_INTEGER_MESSAGE0": "to Integer", + "MATH_TO_INTEGER_TOOLTIP": "Convert a value to an Integer.", + + "TEXT_TO_STRING_MESSAGE0": "to String", + "TEXT_TO_STRING_TOOLTIP": "Convert a value to a String.", + + "TEXT_SPLIT_PART_MESSAGE0": "index $[INDEX] of $[TEXT] split by $[SEP]", + "TEXT_SPLIT_PART_TOOLTIP": "Split text by a separator and return the nth part (0-based). Returns empty string if the part does not exist.", + "TEXT_STARTS_WITH_MESSAGE0": "$[TEXT] starts with $[PREFIX]", + "TEXT_STARTS_WITH_TOOLTIP": "Returns true if the text starts with the given prefix.", + "TEXT_ENDS_WITH_MESSAGE0": "$[TEXT] ends with $[SUFFIX]", + "TEXT_ENDS_WITH_TOOLTIP": "Returns true if the text ends with the given suffix.", + + "MATH_MAP_VALUE_MESSAGE0": "map $[VALUE] from range [$[FROM_LOW], $[FROM_HIGH]] to range [$[TO_LOW], $[TO_HIGH]]", + "MATH_MAP_VALUE_TOOLTIP": "Map a value from one range to another.", + "MATH_CALC_MESSAGE0": "evaluate $[CALC]\na: $[A]\nb: $[B]\nc: $[C]\nd: $[D]", + "MATH_CALC_TOOLTIP": "Evaluate a mathematical expression using the input values. Use a, b, c, d as placeholders for the inputs.", + + "JSON_NAME": "JSON", + "JSON_DESCRIPTION": "Blocks for JSON text and object values.", + "JSON_OBJECT_CREATE_MESSAGE0": "create object", + "JSON_OBJECT_CREATE_TOOLTIP": "Create a JSON object. Use + to add key/value entries.", + "JSON_OBJECT_TO_TEXT_MESSAGE0": "JSON to text $[VALUE]", + "JSON_OBJECT_TO_TEXT_TOOLTIP": "Convert a value to JSON text.", + "JSON_TEXT_TO_OBJECT_MESSAGE0": "text to JSON $[TEXT]", + "JSON_TEXT_TO_OBJECT_TOOLTIP": "Parse text into a JSON object.", + "JSON_OBJECT_CLEAR_MESSAGE0": "clear object $[OBJECT]", + "JSON_OBJECT_CLEAR_TOOLTIP": "Delete all keys from an object.", + "JSON_OBJECT_CONTAINS_KEY_MESSAGE0": "object $[OBJECT] contains key $[KEY]", + "JSON_OBJECT_CONTAINS_KEY_TOOLTIP": "Check whether an object has a key.", + "JSON_OBJECT_GET_KEY_MESSAGE0": "get key $[KEY] in object $[OBJECT]", + "JSON_OBJECT_GET_KEY_TOOLTIP": "Get the value stored under a key.", + "JSON_OBJECT_SET_KEY_MESSAGE0": "in object $[OBJECT] set key $[KEY] value $[VALUE]", + "JSON_OBJECT_SET_KEY_TOOLTIP": "Set a key to a value.", + "JSON_OBJECT_DELETE_KEY_MESSAGE0": "in object $[OBJECT] delete key $[KEY]", + "JSON_OBJECT_DELETE_KEY_TOOLTIP": "Delete a key from an object.", + "JSON_OBJECT_ENTRY_MESSAGE0": "key $[KEY] value $[VALUE]", + "JSON_OBJECT_ENTRY_TOOLTIP": "One key/value pair inside a JSON object." +} diff --git a/jacly-meta-blocks/blocks/variables.jacly.json b/jacly-meta-blocks/blocks/variables.jacly.json new file mode 100644 index 0000000..042c233 --- /dev/null +++ b/jacly-meta-blocks/blocks/variables.jacly.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "jacly-meta-blocks", + "category": "variables", + "name": "%T%", + "description": "%T%", + "colour": "#FF8C1A", + "icon": "Variable", + "priority": 50, + "custom": "VARIABLE" +} diff --git a/jacly-meta-blocks/package.json b/jacly-meta-blocks/package.json new file mode 100644 index 0000000..4f00956 --- /dev/null +++ b/jacly-meta-blocks/package.json @@ -0,0 +1,18 @@ +{ + "name": "jacly-meta-blocks", + "version": "0.1.0", + "author": "Jakub Andrysek", + "license": "MIT", + "description": "This is a collection of internal base blocks used by the Jacly. Those blocks contains base Blockly blocks definitions and generators.", + "type": "module", + "files": [ + "blocks/**" + ], + "scripts": { + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/jacly-meta-blocks/tsconfig.json b/jacly-meta-blocks/tsconfig.json new file mode 100644 index 0000000..ab82ab7 --- /dev/null +++ b/jacly-meta-blocks/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true, + "types": ["jaculus"] + } +} diff --git a/lm75a/.gitignore b/lm75a/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/lm75a/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/lm75a/README.md b/lm75a/README.md new file mode 100644 index 0000000..9b67c38 --- /dev/null +++ b/lm75a/README.md @@ -0,0 +1,3 @@ +# LM75A Temperature Sensor Library + +https://www.ti.com/lit/gpn/LM75A diff --git a/lm75a/blocks/lm75a.jacly.json b/lm75a/blocks/lm75a.jacly.json new file mode 100644 index 0000000..2f7c44c --- /dev/null +++ b/lm75a/blocks/lm75a.jacly.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "category": "lm75a", + "parentCategory": "peripherals", + "name": "%T%", + "description": "%T%", + "colour": "#AC8FBC", + "icon": "Thermometer", + "priority": 140, + "import": ["import { LM75A } from \"lm75a\";"], + "contents": [ + { + "kind": "block", + "type": "i2c_constructor" + }, + { + "kind": "block", + "type": "lm75a_constructor", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "lm75a_?" + }, + { + "type": "field_dropdown", + "name": "I2C_INSTANCE", + "instanceof": "i2c_inst" + } + ], + "constructs": "lm75a", + "tooltip": "%T%", + "code": "let $[CONSTRUCTED_VAR_NAME] = new LM75A($[I2C_INSTANCE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "lm75a_read", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SENSOR_INSTANCE", + "instanceof": "lm75a" + } + ], + "tooltip": "%T%", + "code": "$[SENSOR_INSTANCE].readTemperature()", + "output": "Number" + } + ] +} diff --git a/lm75a/blocks/translations/cs.lang.json b/lm75a/blocks/translations/cs.lang.json new file mode 100644 index 0000000..307180f --- /dev/null +++ b/lm75a/blocks/translations/cs.lang.json @@ -0,0 +1,8 @@ +{ + "LM75A_NAME": "LM75A", + "LM75A_DESCRIPTION": "LM75A knihovna pro teplotní senzor LM75A", + "LM75A_CONSTRUCTOR_MESSAGE0": "vytvořit $[CONSTRUCTED_VAR_NAME] LM75A na I2C sběrnici $[I2C_INSTANCE]", + "LM75A_CONSTRUCTOR_TOOLTIP": "Vytvoří instanci LM75A senzoru na zadané I2C sběrnici", + "LM75A_READ_MESSAGE0": "$[SENSOR_INSTANCE] měření v °C", + "LM75A_READ_TOOLTIP": "Provede měření teploty a vrátí výsledek ve stupních Celsia" +} diff --git a/lm75a/blocks/translations/en.lang.json b/lm75a/blocks/translations/en.lang.json new file mode 100644 index 0000000..2bfdf9f --- /dev/null +++ b/lm75a/blocks/translations/en.lang.json @@ -0,0 +1,8 @@ +{ + "LM75A_NAME": "LM75A", + "LM75A_DESCRIPTION": "LM75A temperature sensor library", + "LM75A_CONSTRUCTOR_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] LM75A on I2C bus $[I2C_INSTANCE]", + "LM75A_CONSTRUCTOR_TOOLTIP": "Create LM75A sensor instance on specified I2C bus", + "LM75A_READ_MESSAGE0": "$[SENSOR_INSTANCE] measure in °C", + "LM75A_READ_TOOLTIP": "Perform a temperature measurement and return the result in degrees Celsius" +} diff --git a/lm75a/package.json b/lm75a/package.json new file mode 100644 index 0000000..732e5ef --- /dev/null +++ b/lm75a/package.json @@ -0,0 +1,28 @@ +{ + "name": "lm75a", + "version": "0.1.0", + "author": "Jakub Andrysek", + "license": "MIT", + "description": "LM75A temperature sensor library", + "type": "module", + "main": "dist/js/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "dependencies": { + "@types/jaculus": "0.1.0" + }, + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/lm75a/src/index.ts b/lm75a/src/index.ts new file mode 100644 index 0000000..63863ee --- /dev/null +++ b/lm75a/src/index.ts @@ -0,0 +1,55 @@ +import * as i2c from "i2c"; + +const ADDR_BASE = 0b1001_000; + +enum Reg { + Conf = 0x01, // Configuration register + Temp = 0x00, // Temperature register + Tos = 0x03, // Overtemperature shutdown threshold + Thyst = 0x02, // Hysteresis register +} + +enum ConfMask { + // reserved + // reserved + // reserved + OS_F_QUE = 0b11 << 3, // Fault queue + OS_POL = 0b1 << 2, // OS polarity + OS_COMP_INT = 0b1 << 1, // OS operation mode + SHUTDOWN = 0b1 << 0, // Device operation mode +} + +export class LM75A { + readonly bus: i2c.I2C; + readonly addr: number; + + /** + * Creates a new instance of the LM75A temperature sensor. + * @param bus The I2C bus used for communication + * @param addrBits Selects the I2C address (addr | (addrBits & 0b111)) + */ + constructor(bus: i2c.I2C, addrBits: number) { + this.bus = bus; + this.addr = ADDR_BASE | (addrBits & 0b111); + this.init(); + } + + /** + * Initializes the sensor + */ + private init() { + this.bus.writeTo(this.addr, [Reg.Conf, 0x00]); + } + + /** + * Reads the temperature in degrees Celsius. + */ + readTemperature(): number { + let value = this.bus.writeRead(this.addr, Reg.Temp, 2); + let deg = ((value[1] | (value[0] << 8)) >> 5) / 8.0; + if (value[0] & 0x80) { + deg -= 256; + } + return deg; + } +} diff --git a/lm75a/tsconfig.json b/lm75a/tsconfig.json new file mode 100644 index 0000000..ab82ab7 --- /dev/null +++ b/lm75a/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true, + "types": ["jaculus"] + } +} diff --git a/package.json b/package.json index 2d6640e..14cb8f2 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,22 @@ { - "name": "jaculus-libraries-core", - "version": "1.0.0", - "description": "Core libraries for the Jaculus - JavaScript runtime for embedded devices", - "license": "MIT", - "author": "Jaculus team", - "type": "module", - "packageManager": "pnpm@10.15.0", - "scripts": { - "build": "pnpm -r build", - "clean": "pnpm -r clean" - }, - "devDependencies": { - "typescript": "^5.9.2" - } + "name": "jaculus-libraries-core", + "version": "0.1.0", + "description": "Core libraries for the Jaculus - JavaScript runtime for embedded devices", + "license": "MIT", + "author": "Jaculus team", + "type": "module", + "packageManager": "pnpm@11.0.9", + "scripts": { + "install-libs": "pnpm run -r run install", + "build-libs": "pnpm run -r build", + "clean-libs": "pnpm run -r clean", + "format": "prettier --write ." + }, + "devDependencies": { + "prettier": "3.8.3" + }, + "dependencies": { + "copyfiles": "^2.4.1", + "typescript": "^6.0.3" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2da8961..ccbcb80 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,20 +7,340 @@ settings: importers: .: - devDependencies: + dependencies: + copyfiles: + specifier: ^2.4.1 + version: 2.4.1 typescript: - specifier: ^5.9.2 - version: 5.9.2 - - empty-template: {} + specifier: ^6.0.3 + version: 6.0.3 + devDependencies: + prettier: + specifier: 3.8.3 + version: 3.8.3 packages: - typescript@5.9.2: - resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + copyfiles@2.4.1: + resolution: {integrity: sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==} + hasBin: true + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + isarray@0.0.1: + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + noms@0.0.0: + resolution: {integrity: sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} + hasBin: true + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + readable-stream@1.0.34: + resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string_decoder@0.10.31: + resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true + untildify@4.0.0: + resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} + engines: {node: '>=8'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + snapshots: - typescript@5.9.2: {} + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + balanced-match@1.0.2: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + concat-map@0.0.1: {} + + copyfiles@2.4.1: + dependencies: + glob: 7.2.3 + minimatch: 3.1.2 + mkdirp: 1.0.4 + noms: 0.0.0 + through2: 2.0.5 + untildify: 4.0.0 + yargs: 16.2.0 + + core-util-is@1.0.3: {} + + emoji-regex@8.0.0: {} + + escalade@3.2.0: {} + + fs.realpath@1.0.0: {} + + get-caller-file@2.0.5: {} + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + is-fullwidth-code-point@3.0.0: {} + + isarray@0.0.1: {} + + isarray@1.0.0: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + mkdirp@1.0.4: {} + + noms@0.0.0: + dependencies: + inherits: 2.0.4 + readable-stream: 1.0.34 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + path-is-absolute@1.0.1: {} + + prettier@3.8.3: {} + + process-nextick-args@2.0.1: {} + + readable-stream@1.0.34: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 0.0.1 + string_decoder: 0.10.31 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + require-directory@2.1.1: {} + + safe-buffer@5.1.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string_decoder@0.10.31: {} + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + through2@2.0.5: + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + + typescript@6.0.3: {} + + untildify@4.0.0: {} + + util-deprecate@1.0.2: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + xtend@4.0.2: {} + + y18n@5.0.8: {} + + yargs-parser@20.2.9: {} + + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6137e68..f989998 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1 @@ -packages: - - "*" +verifyDepsBeforeRun: false diff --git a/r-encoder/.gitignore b/r-encoder/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/r-encoder/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/r-encoder/README.md b/r-encoder/README.md new file mode 100644 index 0000000..73281c4 --- /dev/null +++ b/r-encoder/README.md @@ -0,0 +1 @@ +# Rotary encoder library diff --git a/r-encoder/blocks/r_encoder.jacly.json b/r-encoder/blocks/r_encoder.jacly.json new file mode 100644 index 0000000..70733ef --- /dev/null +++ b/r-encoder/blocks/r_encoder.jacly.json @@ -0,0 +1,143 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "category": "r_encoder", + "parentCategory": "peripherals", + "name": "%T%", + "description": "%T%", + "colour": "#059669", + "icon": "RotateCw", + "priority": 136, + "import": ["import { RotaryEncoder } from \"r-encoder\";"], + "contents": [ + { + "kind": "label", + "text": "%SETUP%" + }, + { + "kind": "block", + "type": "rEncoder_constructor", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "rEncoder_?" + }, + { + "type": "input_value", + "name": "PIN_LEVEL", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 12 + } + } + }, + { + "type": "input_value", + "name": "PIN_EDGE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 13 + } + } + } + ], + "constructs": "rEncoder", + "tooltip": "%T%", + "code": "$[CONSTRUCTED_VAR_NAME] = new RotaryEncoder($[PIN_LEVEL], $[PIN_EDGE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%READ%" + }, + { + "kind": "block", + "type": "rEncoder_read", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "R_ENCODER_INSTANCE", + "instanceof": "rEncoder" + } + ], + "tooltip": "%T%", + "output": "Number", + "code": "$[R_ENCODER_INSTANCE].read()" + }, + { + "kind": "label", + "text": "%OTHER%" + }, + { + "kind": "block", + "type": "rEncoder_clear", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "R_ENCODER_INSTANCE", + "instanceof": "rEncoder" + } + ], + "tooltip": "%T%", + "code": "$[R_ENCODER_INSTANCE].clear();", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "rEncoder_start", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "R_ENCODER_INSTANCE", + "instanceof": "rEncoder" + } + ], + "tooltip": "%T%", + "code": "$[R_ENCODER_INSTANCE].start();", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "rEncoder_stop", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "R_ENCODER_INSTANCE", + "instanceof": "rEncoder" + } + ], + "tooltip": "%T%", + "code": "$[R_ENCODER_INSTANCE].stop();", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "rEncoder_close", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "R_ENCODER_INSTANCE", + "instanceof": "rEncoder" + } + ], + "tooltip": "%T%", + "code": "$[R_ENCODER_INSTANCE].close();", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/r-encoder/blocks/translations/cs.lang.json b/r-encoder/blocks/translations/cs.lang.json new file mode 100644 index 0000000..996b83f --- /dev/null +++ b/r-encoder/blocks/translations/cs.lang.json @@ -0,0 +1,20 @@ +{ + "R_ENCODER_NAME": "Rotační enkodér", + "R_ENCODER_DESCRIPTION": "Knihovna pro řízení rotačního enkodéru založená na čítači pulzů", + "R_ENCODER_LABEL_SETUP": "Nastavení (je potřeba pro použití ostatních bloků rotačního enkodéru)", + "R_ENCODER_LABEL_READ": "Čtení", + "R_ENCODER_LABEL_OTHER": "Ostatní operace s rotačním enkodérem", + + "RENCODER_CONSTRUCTOR_MESSAGE0": "vytvořit $[CONSTRUCTED_VAR_NAME] rotační enkodér\n na pinu úrovně $[PIN_LEVEL] pinu hrany $[PIN_EDGE]", + "RENCODER_CONSTRUCTOR_TOOLTIP": "Vytvoří novou instanci rotačního enkodéru", + "RENCODER_READ_MESSAGE0": "přečíst hodnotu z rotačního enkodéru $[R_ENCODER_INSTANCE]", + "RENCODER_READ_TOOLTIP": "Přečte aktuální hodnotu rotačního enkodéru", + "RENCODER_CLEAR_MESSAGE0": "vynulovat rotační enkodér $[R_ENCODER_INSTANCE]", + "RENCODER_CLEAR_TOOLTIP": "Vynuluje rotační enkodér na 0", + "RENCODER_START_MESSAGE0": "spustit rotační enkodér $[R_ENCODER_INSTANCE]", + "RENCODER_START_TOOLTIP": "Spustí rotační enkodér", + "RENCODER_STOP_MESSAGE0": "zastavit rotační enkodér $[R_ENCODER_INSTANCE]", + "RENCODER_STOP_TOOLTIP": "Zastaví rotační enkodér", + "RENCODER_CLOSE_MESSAGE0": "zavřít rotační enkodér $[R_ENCODER_INSTANCE]", + "RENCODER_CLOSE_TOOLTIP": "Zavře rotační enkodér" +} diff --git a/r-encoder/blocks/translations/en.lang.json b/r-encoder/blocks/translations/en.lang.json new file mode 100644 index 0000000..e291861 --- /dev/null +++ b/r-encoder/blocks/translations/en.lang.json @@ -0,0 +1,20 @@ +{ + "R_ENCODER_NAME": "Rotary Encoder", + "R_ENCODER_DESCRIPTION": "Rotary encoder control library based on PulseCounter", + "R_ENCODER_LABEL_SETUP": "Setup (this is required to use other Rotary Encoder blocks)", + "R_ENCODER_LABEL_READ": "Reading", + "R_ENCODER_LABEL_OTHER": "Other Rotary Encoder operations", + + "RENCODER_CONSTRUCTOR_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] rotary encoder\n on level pin $[PIN_LEVEL] edge pin $[PIN_EDGE]", + "RENCODER_CONSTRUCTOR_TOOLTIP": "Create a new Rotary Encoder instance", + "RENCODER_READ_MESSAGE0": "read value from rotary encoder $[R_ENCODER_INSTANCE]", + "RENCODER_READ_TOOLTIP": "Read the current value of the rotary encoder", + "RENCODER_CLEAR_MESSAGE0": "clear rotary encoder $[R_ENCODER_INSTANCE]", + "RENCODER_CLEAR_TOOLTIP": "Reset the rotary encoder to 0", + "RENCODER_START_MESSAGE0": "start rotary encoder $[R_ENCODER_INSTANCE]", + "RENCODER_START_TOOLTIP": "Start the rotary encoder", + "RENCODER_STOP_MESSAGE0": "stop rotary encoder $[R_ENCODER_INSTANCE]", + "RENCODER_STOP_TOOLTIP": "Stop the rotary encoder", + "RENCODER_CLOSE_MESSAGE0": "close rotary encoder $[R_ENCODER_INSTANCE]", + "RENCODER_CLOSE_TOOLTIP": "Close the rotary encoder" +} diff --git a/r-encoder/package.json b/r-encoder/package.json new file mode 100644 index 0000000..e10009b --- /dev/null +++ b/r-encoder/package.json @@ -0,0 +1,28 @@ +{ + "name": "r-encoder", + "version": "0.1.0", + "author": "Milan Novak", + "license": "MIT", + "description": "R-Encoder library", + "type": "module", + "main": "dist/js/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "dependencies": { + "@types/jaculus": "0.1.0" + }, + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/r-encoder/src/index.ts b/r-encoder/src/index.ts new file mode 100644 index 0000000..f1674f8 --- /dev/null +++ b/r-encoder/src/index.ts @@ -0,0 +1,23 @@ +import { PulseCounter, LevelAction, EdgeAction } from "pulseCounter"; + +/** + * RotaryEncoder simplifies the usage of a rotary encoder + * by preconfiguring the PulseCounter class with appropriate + * level and edge actions for a rotary encoder. + */ +export class RotaryEncoder extends PulseCounter { + constructor(pinLevel: number, pinEdge: number) { + super({ + pinLevel: pinLevel, + pinEdge: pinEdge, + levelMode: { + low: LevelAction.Inverse, + high: LevelAction.Keep, + }, + edgeMode: { + pos: EdgeAction.Increase, + neg: EdgeAction.Decrease, + }, + }); + } +} diff --git a/r-encoder/tsconfig.json b/r-encoder/tsconfig.json new file mode 100644 index 0000000..ab82ab7 --- /dev/null +++ b/r-encoder/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true, + "types": ["jaculus"] + } +} diff --git a/readline/.gitignore b/readline/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/readline/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/readline/README.md b/readline/README.md new file mode 100644 index 0000000..65d8ac8 --- /dev/null +++ b/readline/README.md @@ -0,0 +1 @@ +# Readline diff --git a/readline/blocks/readline.jacly.json b/readline/blocks/readline.jacly.json new file mode 100644 index 0000000..2cc12c1 --- /dev/null +++ b/readline/blocks/readline.jacly.json @@ -0,0 +1,95 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "category": "readline", + "parentCategory": "communication", + "name": "%T%", + "description": "%T%", + "colour": "#8C510C", + "icon": "NotebookText", + "priority": 140, + "import": ["import { readline } from \"readline\";"], + "contents": [ + { + "kind": "label", + "text": "%READ_LINES%" + }, + { + "kind": "block", + "type": "readline_onLine", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "ECHO", + "options": [ + ["%ECHO_OFF%", "false"], + ["%ECHO_ON%", "true"] + ] + }, + { + "type": "input_statement", + "name": "CODE" + } + ], + "callbackVars": [ + { + "identifier": "lineText", + "message": "%LINE_TEXT%", + "type": "String", + "codeName": "line" + }, + { + "identifier": "lineLength", + "message": "%LINE_LENGTH%", + "type": "Number", + "codeName": "line.length" + } + ], + "isProgramStart": true, + "tooltip": "%T%", + "code": "{\n\tconst streamReadline = new readline($[ECHO]);\n\tasync function streamOnLineLoop() {\n\t\ttry {\n\t\t\tconst line = await streamReadline.read();\n$[CODE]\n\t\t\tstreamOnLineLoop();\n\t\t} catch (_reason) {\n\t\t\tconsole.warn(\"stdin line read failed:\", _reason);\n\t\t\tstreamOnLineLoop();\n\t\t}\n\t}\n\tstreamOnLineLoop();\n}" + }, + { + "kind": "block", + "type": "readline_keyValue", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "ECHO", + "options": [ + ["%ECHO_OFF%", "false"], + ["%ECHO_ON%", "true"] + ] + }, + { + "type": "input_statement", + "name": "CODE" + } + ], + "callbackVars": [ + { + "identifier": "keyText", + "message": "%KEY%", + "type": "String", + "codeName": "keyText" + }, + { + "identifier": "valueText", + "message": "%VALUE_TEXT%", + "type": "String", + "codeName": "valueText" + }, + { + "identifier": "valueNumber", + "message": "%VALUE_NUMBER%", + "type": "Number", + "codeName": "valueNumber" + } + ], + "isProgramStart": true, + "tooltip": "%T%", + "code": "{\n\tconst streamReadline = new readline($[ECHO]);\n\tasync function streamOnLineKeyValueLoop() {\n\t\ttry {\n\t\t\tconst line = await streamReadline.read();\n\t\t\tconst _colonIdx = line.indexOf(\":\");\n\t\t\tconst keyText = _colonIdx >= 0 ? line.substring(0, _colonIdx).trim() : line.trim();\n\t\t\tconst _rawValue = _colonIdx >= 0 ? line.substring(_colonIdx + 1).trim() : \"\";\n\t\t\tconst valueText = _rawValue;\n\t\t\tconst valueNumber = parseFloat(_rawValue);\n$[CODE]\n\t\t\tstreamOnLineKeyValueLoop();\n\t\t} catch (_reason) {\n\t\t\tconsole.warn(\"stdin line read failed:\", _reason);\n\t\t\tstreamOnLineKeyValueLoop();\n\t\t}\n\t}\n\tstreamOnLineKeyValueLoop();\n}" + } + ] +} diff --git a/readline/blocks/translations/cs.lang.json b/readline/blocks/translations/cs.lang.json new file mode 100644 index 0000000..20ddb77 --- /dev/null +++ b/readline/blocks/translations/cs.lang.json @@ -0,0 +1,20 @@ +{ + "READLINE_NAME": "Čtení řádků", + "READLINE_DESCRIPTION": "Knihovna pro čtení řádků ze standardního vstupu", + "READLINE_LABEL_READ_LINES": "Čtení řádků ze standardního vstupu", + "READLINE_LABEL_OUTPUT": "Výstup", + "READLINE_LABEL_ADVANCED": "Pokročilé", + "READLINE_ONLINE_MESSAGE0": "když se přečte řádek ze standardního vstupu s echem $[ECHO], proveď\n$[CODE]", + "READLINE_ONLINE_TOOLTIP": "Průběžně čte řádky ze standardního vstupu a pro každý spustí připojený kód.", + "READLINE_ONLINE_ARGS0_FIELD_DROPDOWN_ECHO_ON": "zapnuto", + "READLINE_ONLINE_ARGS0_FIELD_DROPDOWN_ECHO_OFF": "vypnuto", + "READLINE_ONLINE_CALLBACK_LINE_TEXT": "text řádku", + "READLINE_ONLINE_CALLBACK_LINE_LENGTH": "délka řádku", + "READLINE_KEYVALUE_MESSAGE0": "když se přečte řádek klíč-hodnota ze standardního vstupu s echem $[ECHO], proveď\n$[CODE]", + "READLINE_KEYVALUE_TOOLTIP": "Průběžně čte řádky ze standardního vstupu, každý zparsuje jako 'klíč: hodnota' a spustí připojený kód. valueNumber je NaN, pokud hodnota není číslo.", + "READLINE_KEYVALUE_ARGS0_FIELD_DROPDOWN_ECHO_ON": "zapnuto", + "READLINE_KEYVALUE_ARGS0_FIELD_DROPDOWN_ECHO_OFF": "vypnuto", + "READLINE_KEYVALUE_CALLBACK_KEY": "klíč", + "READLINE_KEYVALUE_CALLBACK_VALUE_TEXT": "hodnota (text)", + "READLINE_KEYVALUE_CALLBACK_VALUE_NUMBER": "hodnota (číslo)" +} diff --git a/readline/blocks/translations/en.lang.json b/readline/blocks/translations/en.lang.json new file mode 100644 index 0000000..607b080 --- /dev/null +++ b/readline/blocks/translations/en.lang.json @@ -0,0 +1,20 @@ +{ + "READLINE_NAME": "Readline", + "READLINE_DESCRIPTION": "Readline library for handling standard input", + "READLINE_LABEL_READ_LINES": "Read lines from standard input", + "READLINE_LABEL_OUTPUT": "Output", + "READLINE_LABEL_ADVANCED": "Advanced", + "READLINE_ONLINE_MESSAGE0": "when a line is read from standard input with echo $[ECHO] do\n$[CODE]", + "READLINE_ONLINE_TOOLTIP": "Keep reading lines from standard input and run the attached code for each line.", + "READLINE_ONLINE_ARGS0_FIELD_DROPDOWN_ECHO_ON": "on", + "READLINE_ONLINE_ARGS0_FIELD_DROPDOWN_ECHO_OFF": "off", + "READLINE_ONLINE_CALLBACK_LINE_TEXT": "line text", + "READLINE_ONLINE_CALLBACK_LINE_LENGTH": "line length", + "READLINE_KEYVALUE_MESSAGE0": "when a key-value line is read from standard input with echo $[ECHO] do\n$[CODE]", + "READLINE_KEYVALUE_TOOLTIP": "Keep reading lines from standard input, parse each as 'key: value', and run the attached code. valueNumber is NaN if the value is not a number.", + "READLINE_KEYVALUE_ARGS0_FIELD_DROPDOWN_ECHO_ON": "on", + "READLINE_KEYVALUE_ARGS0_FIELD_DROPDOWN_ECHO_OFF": "off", + "READLINE_KEYVALUE_CALLBACK_KEY": "key", + "READLINE_KEYVALUE_CALLBACK_VALUE_TEXT": "value (text)", + "READLINE_KEYVALUE_CALLBACK_VALUE_NUMBER": "value (number)" +} diff --git a/readline/package.json b/readline/package.json new file mode 100644 index 0000000..43910e7 --- /dev/null +++ b/readline/package.json @@ -0,0 +1,28 @@ +{ + "name": "readline", + "version": "0.1.0", + "author": "Jakub Andrysek", + "license": "MIT", + "description": "Readline library for Jaculus", + "type": "module", + "main": "dist/js/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "dependencies": { + "@types/jaculus": "0.1.0" + }, + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/readline/src/index.ts b/readline/src/index.ts new file mode 100644 index 0000000..1edbf25 --- /dev/null +++ b/readline/src/index.ts @@ -0,0 +1,85 @@ +import { stdout, stdin } from "stdio"; + +/** + * A class for reading standard input line by line. + */ + +export class readline { + private buffer: string = ""; + private promise: Promise | null = null; + private resolve: ((value: string) => void) | null = null; + private reject: ((reason: any) => void) | null = null; + private closed: boolean = false; + private echo: boolean; + + private onGet(str: string) { + if (this.echo) { + stdout.write(str); + } + + if (str == "\n") { + if (this.resolve) { + this.resolve(this.buffer); + } + + this.buffer = ""; + this.promise = null; + this.resolve = null; + this.reject = null; + + return; + } + + this.buffer += str; + + if (!this.closed) { + stdin + .get() + .then((data) => this.onGet(data)) + .catch((reason) => { + if (this.reject) { + this.reject(reason); + } + }); + } + } + + constructor(echo: boolean = false) { + this.echo = echo; + } + + public read(): Promise { + if (this.promise != null) { + return Promise.reject("Already reading"); + } + + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + + stdin + .get() + .then((data) => this.onGet(data)) + .catch((reason) => { + if (this.reject) { + this.reject(reason); + } + }); + + return this.promise; + } + + public close() { + this.closed = true; + + if (this.reject) { + this.reject("Stopped"); + } + + this.buffer = ""; + this.promise = null; + this.resolve = null; + this.reject = null; + } +} diff --git a/readline/tsconfig.json b/readline/tsconfig.json new file mode 100644 index 0000000..ab82ab7 --- /dev/null +++ b/readline/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true, + "types": ["jaculus"] + } +} diff --git a/robutek/.gitignore b/robutek/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/robutek/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/robutek/README.md b/robutek/README.md new file mode 100644 index 0000000..e889a04 --- /dev/null +++ b/robutek/README.md @@ -0,0 +1,3 @@ +# Robůtek + +Robůtek is robotic platform designed for teaching programming and robotics, and also as an extendable platform for building your own robots with minimal entry requirements. diff --git a/robutek/blocks/robutek2.jacly.json b/robutek/blocks/robutek2.jacly.json new file mode 100644 index 0000000..3a61b57 --- /dev/null +++ b/robutek/blocks/robutek2.jacly.json @@ -0,0 +1,232 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "robutek", + "category": "robutek2", + "name": "%T%", + "description": "%T%", + "colour": "#96a609", + "icon": "Bot", + "priority": 140, + "import": [ + "import { createRobutek } from \"robutek\";", + "import { I2C1 } from \"i2c\";\n" + ], + "contents": [ + { + "kind": "block", + "type": "robutek2_constructor", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "robutek2_?" + }, + { + "type": "input_value", + "name": "ROBUTEK_DIAMETER", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 83 + } + } + }, + { + "type": "input_value", + "name": "WHEEL_DIAMETER", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 33.3 + } + } + } + ], + "constructs": "robutek2", + "virtualInstances": [ + { + "instanceof": "d_drive", + "name": "differentialDrive", + "connection": "$[CONSTRUCTED_VAR_NAME]" + }, + { + "instanceof": "motor", + "name": "leftMotor", + "connection": "$[CONSTRUCTED_VAR_NAME].leftMotor" + }, + { + "instanceof": "motor", + "name": "rightMotor", + "connection": "$[CONSTRUCTED_VAR_NAME].rightMotor" + }, + { + "instanceof": "i2c_inst", + "name": "i2c", + "connection": "I2C1" + }, + { + "instanceof": "bq2589", + "name": "batteryChecker", + "connection": "$[CONSTRUCTED_VAR_NAME].bq2589" + } + ], + "tooltip": "%T%", + "code": "$[CONSTRUCTED_VAR_NAME] = createRobutek(\"V2\", { robutekDiameter: $[ROBUTEK_DIAMETER], wheelDiameter: $[WHEEL_DIAMETER] });", + "previousStatement": null, + "nextStatement": null, + "next": { + "block": { + "type": "differential_drive_setSpeed", + "next": { + "block": { + "type": "differential_drive_setRamp" + } + } + } + } + }, + { + "kind": "label", + "text": "%PERIPHERALS%" + }, + { + "kind": "block", + "type": "robutek2_readSensor", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "ROBUTEK2_INSTANCE", + "instanceof": "robutek2" + }, + { + "type": "field_dropdown", + "name": "SENSOR", + "options": [ + ["%WHEEL_FL%", "WheelFL"], + ["%WHEEL_FR%", "WheelFR"], + ["%WHEEL_BL%", "WheelBL"], + ["%WHEEL_BR%", "WheelBR"], + ["%LINE_FL%", "LineFL"], + ["%LINE_FR%", "LineFR"], + ["%LINE_BL%", "LineBL"], + ["%LINE_BR%", "LineBR"] + ] + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[ROBUTEK2_INSTANCE].readSensor(\"$[SENSOR]\")" + }, + { + "kind": "block", + "type": "robutek2_setStatusLED", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "ROBUTEK2_INSTANCE", + "instanceof": "robutek2" + }, + { + "type": "input_value", + "name": "STATE", + "check": "Boolean", + "shadow": { + "type": "logic_boolean", + "fields": { + "BOOL": 1 + } + } + } + ], + "tooltip": "%T%", + "code": "$[ROBUTEK2_INSTANCE].setStatusLED($[STATE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%MOVEMENT%" + }, + { + "kind": "block", + "type": "differential_drive_move_distance" + }, + { + "kind": "block", + "type": "differential_drive_move_time" + }, + { + "kind": "block", + "type": "differential_drive_move_infinite" + }, + { + "kind": "block", + "type": "differential_drive_rotate" + }, + { + "kind": "block", + "type": "differential_drive_stop" + }, + { + "kind": "label", + "text": "%HEADING%" + }, + { + "kind": "block", + "type": "differential_drive_getHeading" + }, + { + "kind": "block", + "type": "differential_drive_getHeadingClamped" + }, + { + "kind": "block", + "type": "differential_drive_resetHeading" + }, + { + "kind": "label", + "text": "%SPEED_AND_RAMP%" + }, + { + "kind": "block", + "type": "differential_drive_setSpeed" + }, + { + "kind": "block", + "type": "differential_drive_getSpeed" + }, + { + "kind": "block", + "type": "differential_drive_setRamp" + }, + { + "kind": "block", + "type": "differential_drive_getRamp" + }, + { + "kind": "label", + "text": "%ADDITIONAL%" + }, + { + "kind": "block", + "type": "robutek2_close", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "ROBUTEK2_INSTANCE", + "instanceof": "robutek2" + } + ], + "tooltip": "%T%", + "code": "await $[ROBUTEK2_INSTANCE].close();", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/robutek/blocks/robutek2_pins.jacly.json b/robutek/blocks/robutek2_pins.jacly.json new file mode 100644 index 0000000..cb5eab1 --- /dev/null +++ b/robutek/blocks/robutek2_pins.jacly.json @@ -0,0 +1,164 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "robutek", + "category": "robutek2_pins", + "parentCategory": "robutek2", + "name": "%T%", + "description": "%T%", + "colour": "#96a609", + "icon": "Bot", + "priority": 140, + "contents": [ + { + "kind": "label", + "text": "%PINS%" + }, + { + "kind": "label", + "text": "%PINS_LED%" + }, + { + "kind": "block", + "type": "robutek2_pins_led", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PIN", + "options": [ + ["%STATUS_LED%", "46"], + ["%ILED%", "48"], + ["%ILED_CONNECTOR%", "36"] + ] + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[PIN]" + }, + + { + "kind": "label", + "text": "%PINS_BUTTONS%" + }, + { + "kind": "block", + "type": "robutek2_pins_buttons", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PIN", + "options": [ + ["%BUTTON_LEFT%", "2"], + ["%BUTTON_RIGHT%", "0"] + ] + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[PIN]" + }, + + { + "kind": "label", + "text": "%PINS_SERVO%" + }, + { + "kind": "block", + "type": "robutek2_pins_servo", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PIN", + "options": [ + ["%SERVO1%", "21"], + ["%SERVO2%", "38"] + ] + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[PIN]" + }, + + { + "kind": "label", + "text": "%PINS_LINE_SENS%" + }, + { + "kind": "block", + "type": "robutek2_pins_line_sens", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PIN", + "options": [ + ["%LINE_SENS1%", "4"], + ["%LINE_SENS2%", "5"], + ["%LINE_SENS3%", "6"], + ["%LINE_SENS4%", "7"], + ["%LINE_SENS_SW%", "8"], + ["%LINE_SENS_EN%", "47"] + ] + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[PIN]" + }, + { + "kind": "label", + "text": "%PINS_MOTOR_ENC%" + }, + { + "kind": "block", + "type": "robutek2_pins_motor_enc", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PIN", + "options": [ + ["%MOTOR1A%", "11"], + ["%MOTOR1B%", "12"], + ["%MOTOR2A%", "45"], + ["%MOTOR2B%", "13"], + ["%ENC1A%", "40"], + ["%ENC1B%", "39"], + ["%ENC2A%", "41"], + ["%ENC2B%", "42"] + ] + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[PIN]" + }, + + { + "kind": "label", + "text": "%PINS_I2C%" + }, + { + "kind": "block", + "type": "robutek2_pins_i2c", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "PIN", + "options": [ + ["%SDA%", "10"], + ["%SCL%", "3"] + ] + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "$[PIN]" + } + ] +} diff --git a/robutek/blocks/translations/cs.lang.json b/robutek/blocks/translations/cs.lang.json new file mode 100644 index 0000000..331927b --- /dev/null +++ b/robutek/blocks/translations/cs.lang.json @@ -0,0 +1,80 @@ +{ + "ROBUTEK2_NAME": "Robůtek V2", + "ROBUTEK2_DESCRIPTION": "Robůtek V2 je robotická platforma navržená pro výuku programování a robotiky", + + "ROBUTEK2_CONSTRUCTOR_MESSAGE0": "vytvořit $[CONSTRUCTED_VAR_NAME] Robůtek V2\nprůměr robůtka (mm): $[ROBUTEK_DIAMETER] průměr kola (mm): $[WHEEL_DIAMETER]", + "ROBUTEK2_CONSTRUCTOR_TOOLTIP": "Vytvoří novou instanci Robůtka V2", + + "ROBUTEK2_LABEL_PERIPHERALS": "Periferie", + "ROBUTEK2_READSENSOR_MESSAGE0": "na $[ROBUTEK2_INSTANCE] přečíst senzor $[SENSOR]", + "ROBUTEK2_READSENSOR_TOOLTIP": "Přečte hodnotu ze zadaného senzoru", + "ROBUTEK2_READSENSOR_ARGS0_FIELD_DROPDOWN_WHEEL_FL": "Kolo přední levé", + "ROBUTEK2_READSENSOR_ARGS0_FIELD_DROPDOWN_WHEEL_FR": "Kolo přední pravé", + "ROBUTEK2_READSENSOR_ARGS0_FIELD_DROPDOWN_WHEEL_BL": "Kolo zadní levé", + "ROBUTEK2_READSENSOR_ARGS0_FIELD_DROPDOWN_WHEEL_BR": "Kolo zadní pravé", + "ROBUTEK2_READSENSOR_ARGS0_FIELD_DROPDOWN_LINE_FL": "Čára přední levá", + "ROBUTEK2_READSENSOR_ARGS0_FIELD_DROPDOWN_LINE_FR": "Čára přední pravá", + "ROBUTEK2_READSENSOR_ARGS0_FIELD_DROPDOWN_LINE_BL": "Čára zadní levá", + "ROBUTEK2_READSENSOR_ARGS0_FIELD_DROPDOWN_LINE_BR": "Čára zadní pravá", + "ROBUTEK2_SETSTATUSLED_MESSAGE0": "na $[ROBUTEK2_INSTANCE] nastavit stavovou LED na $[STATE]", + "ROBUTEK2_SETSTATUSLED_TOOLTIP": "Nastaví stav stavové LED (0 = vypnuto, 1 = zapnuto)", + + "ROBUTEK2_LABEL_MOVEMENT": "Pohyb", + "ROBUTEK2_LABEL_HEADING": "Směr", + "ROBUTEK2_LABEL_SPEED_AND_RAMP": "Rychlost a rampa", + + "ROBUTEK2_LABEL_ADDITIONAL": "Další", + "ROBUTEK2_CLOSE_MESSAGE0": "na $[ROBUTEK2_INSTANCE] zavřít a zastavit", + "ROBUTEK2_CLOSE_TOOLTIP": "Zavře instanci Robůtka V2 a zastaví robota", + + "ROBUTEK2_PINS_NAME": "Robůtek V2 piny", + "ROBUTEK2_PINS_DESCRIPTION": "Konstanty pinů pro Robůtek V2", + "ROBUTEK2_PINS_LABEL_PINS": "Piny", + + "ROBUTEK2_PINS_LABEL_PINS_LED": "LED piny", + "ROBUTEK2_PINS_LED_MESSAGE0": "pin $[PIN]", + "ROBUTEK2_PINS_LED_TOOLTIP": "Vrátí číslo vybraného LED pinu Robůtek V2", + "ROBUTEK2_PINS_LED_ARGS0_FIELD_DROPDOWN_STATUS_LED": "Stavová LED", + "ROBUTEK2_PINS_LED_ARGS0_FIELD_DROPDOWN_ILED": "Interní SmartLED", + "ROBUTEK2_PINS_LED_ARGS0_FIELD_DROPDOWN_ILED_CONNECTOR": "Externí konektor SmartLED", + + "ROBUTEK2_PINS_LABEL_PINS_BUTTONS": "Tlačítka piny", + "ROBUTEK2_PINS_BUTTONS_MESSAGE0": "pin $[PIN]", + "ROBUTEK2_PINS_BUTTONS_TOOLTIP": "Vrátí číslo vybraného pinu tlačítka Robůtek V2", + "ROBUTEK2_PINS_BUTTONS_ARGS0_FIELD_DROPDOWN_BUTTON_LEFT": "Levé tlačítko", + "ROBUTEK2_PINS_BUTTONS_ARGS0_FIELD_DROPDOWN_BUTTON_RIGHT": "Pravé tlačítko", + + "ROBUTEK2_PINS_LABEL_PINS_SERVO": "Servo piny", + "ROBUTEK2_PINS_SERVO_MESSAGE0": "pin $[PIN]", + "ROBUTEK2_PINS_SERVO_TOOLTIP": "Vrátí číslo vybraného servo pinu Robůtek V2", + "ROBUTEK2_PINS_SERVO_ARGS0_FIELD_DROPDOWN_SERVO1": "Servo 1", + "ROBUTEK2_PINS_SERVO_ARGS0_FIELD_DROPDOWN_SERVO2": "Servo 2", + + "ROBUTEK2_PINS_LABEL_PINS_LINE_SENS": "Piny čárových senzorů", + "ROBUTEK2_PINS_LINE_SENS_MESSAGE0": "pin $[PIN]", + "ROBUTEK2_PINS_LINE_SENS_TOOLTIP": "Vrátí číslo vybraného pinu čárového senzoru Robůtek V2", + "ROBUTEK2_PINS_LINE_SENS_ARGS0_FIELD_DROPDOWN_LINE_SENS1": "Čárový senzor 1", + "ROBUTEK2_PINS_LINE_SENS_ARGS0_FIELD_DROPDOWN_LINE_SENS2": "Čárový senzor 2", + "ROBUTEK2_PINS_LINE_SENS_ARGS0_FIELD_DROPDOWN_LINE_SENS3": "Čárový senzor 3", + "ROBUTEK2_PINS_LINE_SENS_ARGS0_FIELD_DROPDOWN_LINE_SENS4": "Čárový senzor 4", + "ROBUTEK2_PINS_LINE_SENS_ARGS0_FIELD_DROPDOWN_LINE_SENS_SW": "Přepínač senzorů", + "ROBUTEK2_PINS_LINE_SENS_ARGS0_FIELD_DROPDOWN_LINE_SENS_EN": "Povolení senzorů", + + "ROBUTEK2_PINS_LABEL_PINS_MOTOR_ENC": "Motory a enkodéry piny", + "ROBUTEK2_PINS_MOTOR_ENC_MESSAGE0": "pin $[PIN]", + "ROBUTEK2_PINS_MOTOR_ENC_TOOLTIP": "Vrátí číslo vybraného pinu motoru nebo enkodéru Robůtek V2", + "ROBUTEK2_PINS_MOTOR_ENC_ARGS0_FIELD_DROPDOWN_MOTOR1A": "Motor 1A", + "ROBUTEK2_PINS_MOTOR_ENC_ARGS0_FIELD_DROPDOWN_MOTOR1B": "Motor 1B", + "ROBUTEK2_PINS_MOTOR_ENC_ARGS0_FIELD_DROPDOWN_MOTOR2A": "Motor 2A", + "ROBUTEK2_PINS_MOTOR_ENC_ARGS0_FIELD_DROPDOWN_MOTOR2B": "Motor 2B", + "ROBUTEK2_PINS_MOTOR_ENC_ARGS0_FIELD_DROPDOWN_ENC1A": "Enkodér 1A", + "ROBUTEK2_PINS_MOTOR_ENC_ARGS0_FIELD_DROPDOWN_ENC1B": "Enkodér 1B", + "ROBUTEK2_PINS_MOTOR_ENC_ARGS0_FIELD_DROPDOWN_ENC2A": "Enkodér 2A", + "ROBUTEK2_PINS_MOTOR_ENC_ARGS0_FIELD_DROPDOWN_ENC2B": "Enkodér 2B", + + "ROBUTEK2_PINS_LABEL_PINS_I2C": "I2C piny", + "ROBUTEK2_PINS_I2C_MESSAGE0": "pin $[PIN]", + "ROBUTEK2_PINS_I2C_TOOLTIP": "Vrátí číslo vybraného I2C pinu Robůtek V2", + "ROBUTEK2_PINS_I2C_ARGS0_FIELD_DROPDOWN_SDA": "SDA", + "ROBUTEK2_PINS_I2C_ARGS0_FIELD_DROPDOWN_SCL": "SCL" +} diff --git a/robutek/blocks/translations/en.lang.json b/robutek/blocks/translations/en.lang.json new file mode 100644 index 0000000..6882adb --- /dev/null +++ b/robutek/blocks/translations/en.lang.json @@ -0,0 +1,80 @@ +{ + "ROBUTEK2_NAME": "Robůtek V2", + "ROBUTEK2_DESCRIPTION": "Robůtek V2 is robotic platform designed for teaching programming and robotics", + + "ROBUTEK2_CONSTRUCTOR_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] Robůtek V2\nrobot diameter (mm): $[ROBUTEK_DIAMETER] wheel diameter (mm): $[WHEEL_DIAMETER]", + "ROBUTEK2_CONSTRUCTOR_TOOLTIP": "Create new Robůtek V2 instance", + + "ROBUTEK2_LABEL_PERIPHERALS": "Peripherals", + "ROBUTEK2_READSENSOR_MESSAGE0": "on $[ROBUTEK2_INSTANCE] read sensor $[SENSOR]", + "ROBUTEK2_READSENSOR_TOOLTIP": "Read value from the specified sensor", + "ROBUTEK2_READSENSOR_ARGS0_FIELD_DROPDOWN_WHEEL_FL": "Wheel Front Left", + "ROBUTEK2_READSENSOR_ARGS0_FIELD_DROPDOWN_WHEEL_FR": "Wheel Front Right", + "ROBUTEK2_READSENSOR_ARGS0_FIELD_DROPDOWN_WHEEL_BL": "Wheel Back Left", + "ROBUTEK2_READSENSOR_ARGS0_FIELD_DROPDOWN_WHEEL_BR": "Wheel Back Right", + "ROBUTEK2_READSENSOR_ARGS0_FIELD_DROPDOWN_LINE_FL": "Line Front Left", + "ROBUTEK2_READSENSOR_ARGS0_FIELD_DROPDOWN_LINE_FR": "Line Front Right", + "ROBUTEK2_READSENSOR_ARGS0_FIELD_DROPDOWN_LINE_BL": "Line Back Left", + "ROBUTEK2_READSENSOR_ARGS0_FIELD_DROPDOWN_LINE_BR": "Line Back Right", + "ROBUTEK2_SETSTATUSLED_MESSAGE0": "on $[ROBUTEK2_INSTANCE] set status LED to $[STATE]", + "ROBUTEK2_SETSTATUSLED_TOOLTIP": "Set the status LED state (0 = off, 1 = on)", + + "ROBUTEK2_LABEL_MOVEMENT": "Movement", + "ROBUTEK2_LABEL_HEADING": "Heading", + "ROBUTEK2_LABEL_SPEED_AND_RAMP": "Speed and Ramp", + + "ROBUTEK2_LABEL_ADDITIONAL": "Additional", + "ROBUTEK2_CLOSE_MESSAGE0": "on $[ROBUTEK2_INSTANCE] close and stop", + "ROBUTEK2_CLOSE_TOOLTIP": "Close the Robůtek V2 instance and stop the robot", + + "ROBUTEK2_PINS_NAME": "Robůtek V2 Pins", + "ROBUTEK2_PINS_DESCRIPTION": "Pin constants for Robůtek V2", + "ROBUTEK2_PINS_LABEL_PINS": "Pins", + + "ROBUTEK2_PINS_LABEL_PINS_LED": "LED Pins", + "ROBUTEK2_PINS_LED_MESSAGE0": "pin $[PIN]", + "ROBUTEK2_PINS_LED_TOOLTIP": "Return the selected Robůtek V2 LED pin number", + "ROBUTEK2_PINS_LED_ARGS0_FIELD_DROPDOWN_STATUS_LED": "Status LED", + "ROBUTEK2_PINS_LED_ARGS0_FIELD_DROPDOWN_ILED": "Internal SmartLED", + "ROBUTEK2_PINS_LED_ARGS0_FIELD_DROPDOWN_ILED_CONNECTOR": "External connector SmartLED", + + "ROBUTEK2_PINS_LABEL_PINS_BUTTONS": "Button Pins", + "ROBUTEK2_PINS_BUTTONS_MESSAGE0": "pin $[PIN]", + "ROBUTEK2_PINS_BUTTONS_TOOLTIP": "Return the selected Robůtek V2 button pin number", + "ROBUTEK2_PINS_BUTTONS_ARGS0_FIELD_DROPDOWN_BUTTON_LEFT": "Left Button", + "ROBUTEK2_PINS_BUTTONS_ARGS0_FIELD_DROPDOWN_BUTTON_RIGHT": "Right Button", + + "ROBUTEK2_PINS_LABEL_PINS_SERVO": "Servo Pins", + "ROBUTEK2_PINS_SERVO_MESSAGE0": "pin $[PIN]", + "ROBUTEK2_PINS_SERVO_TOOLTIP": "Return the selected Robůtek V2 servo pin number", + "ROBUTEK2_PINS_SERVO_ARGS0_FIELD_DROPDOWN_SERVO1": "Servo 1", + "ROBUTEK2_PINS_SERVO_ARGS0_FIELD_DROPDOWN_SERVO2": "Servo 2", + + "ROBUTEK2_PINS_LABEL_PINS_LINE_SENS": "Line Sensor Pins", + "ROBUTEK2_PINS_LINE_SENS_MESSAGE0": "pin $[PIN]", + "ROBUTEK2_PINS_LINE_SENS_TOOLTIP": "Return the selected Robůtek V2 line sensor pin number", + "ROBUTEK2_PINS_LINE_SENS_ARGS0_FIELD_DROPDOWN_LINE_SENS1": "Line Sensor 1", + "ROBUTEK2_PINS_LINE_SENS_ARGS0_FIELD_DROPDOWN_LINE_SENS2": "Line Sensor 2", + "ROBUTEK2_PINS_LINE_SENS_ARGS0_FIELD_DROPDOWN_LINE_SENS3": "Line Sensor 3", + "ROBUTEK2_PINS_LINE_SENS_ARGS0_FIELD_DROPDOWN_LINE_SENS4": "Line Sensor 4", + "ROBUTEK2_PINS_LINE_SENS_ARGS0_FIELD_DROPDOWN_LINE_SENS_SW": "Sensor Switch", + "ROBUTEK2_PINS_LINE_SENS_ARGS0_FIELD_DROPDOWN_LINE_SENS_EN": "Sensor Enable", + + "ROBUTEK2_PINS_LABEL_PINS_MOTOR_ENC": "Motor & Encoder Pins", + "ROBUTEK2_PINS_MOTOR_ENC_MESSAGE0": "pin $[PIN]", + "ROBUTEK2_PINS_MOTOR_ENC_TOOLTIP": "Return the selected Robůtek V2 motor or encoder pin number", + "ROBUTEK2_PINS_MOTOR_ENC_ARGS0_FIELD_DROPDOWN_MOTOR1A": "Motor 1A", + "ROBUTEK2_PINS_MOTOR_ENC_ARGS0_FIELD_DROPDOWN_MOTOR1B": "Motor 1B", + "ROBUTEK2_PINS_MOTOR_ENC_ARGS0_FIELD_DROPDOWN_MOTOR2A": "Motor 2A", + "ROBUTEK2_PINS_MOTOR_ENC_ARGS0_FIELD_DROPDOWN_MOTOR2B": "Motor 2B", + "ROBUTEK2_PINS_MOTOR_ENC_ARGS0_FIELD_DROPDOWN_ENC1A": "Encoder 1A", + "ROBUTEK2_PINS_MOTOR_ENC_ARGS0_FIELD_DROPDOWN_ENC1B": "Encoder 1B", + "ROBUTEK2_PINS_MOTOR_ENC_ARGS0_FIELD_DROPDOWN_ENC2A": "Encoder 2A", + "ROBUTEK2_PINS_MOTOR_ENC_ARGS0_FIELD_DROPDOWN_ENC2B": "Encoder 2B", + + "ROBUTEK2_PINS_LABEL_PINS_I2C": "I2C Pins", + "ROBUTEK2_PINS_I2C_MESSAGE0": "pin $[PIN]", + "ROBUTEK2_PINS_I2C_TOOLTIP": "Return the selected Robůtek V2 I2C pin number", + "ROBUTEK2_PINS_I2C_ARGS0_FIELD_DROPDOWN_SDA": "SDA", + "ROBUTEK2_PINS_I2C_ARGS0_FIELD_DROPDOWN_SCL": "SCL" +} diff --git a/robutek/package.json b/robutek/package.json new file mode 100644 index 0000000..ba9b983 --- /dev/null +++ b/robutek/package.json @@ -0,0 +1,31 @@ +{ + "name": "robutek", + "version": "0.1.0", + "author": "Jakub Andrysek", + "license": "MIT", + "description": "Robůtek is robotic platform designed for teaching programming and robotics", + "type": "module", + "main": "dist/js/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "dependencies": { + "@types/jaculus": "0.1.0", + "differential-drive": "0.1.0", + "bq2589": "0.1.0", + "colors": "0.1.0" + }, + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/robutek/src/index.ts b/robutek/src/index.ts new file mode 100644 index 0000000..da459fb --- /dev/null +++ b/robutek/src/index.ts @@ -0,0 +1,303 @@ +import * as adc from "adc"; +import * as gpio from "gpio"; +import * as motor from "motor"; +import * as ledc from "ledc"; +import { DifferentialDrive } from "differential-drive"; +import { I2C1 } from "i2c"; +import { BQ2589 } from "bq2589"; + +enum PinsV1 { + StatusLED = 46, + + // The first LED of the strip is on the board, the LED strip + // connected to the ILED connector has indexes offset by 1. + ILED = 48, + + ButtonLeft = 2, + ButtonRight = 0, + + Servo1 = 21, + Servo2 = 38, + + Sens1 = 5, + Sens2 = 4, + Sens3 = 6, + Sens4 = 7, + + SensSW = 8, + SensEN = 47, + + Motor1A = 11, + Motor1B = 12, + Motor2A = 45, + Motor2B = 13, + Enc1A = 39, + Enc1B = 40, + Enc2A = 42, + Enc2B = 41, +} + +enum PinsV2 { + StatusLED = 46, + + ILED = 48, + ILEDConnector = 36, + + ButtonLeft = 2, + ButtonRight = 0, + + Servo1 = 21, + Servo2 = 38, + + Sens1 = 4, + Sens2 = 5, + Sens3 = 6, + Sens4 = 7, + + SensSW = 8, + SensEN = 47, + + Motor1A = 11, + Motor1B = 12, + Motor2A = 45, + Motor2B = 13, + Enc1A = 40, + Enc1B = 39, + Enc2A = 41, + Enc2B = 42, + + SDA = 10, + SCL = 3, +} + +const RegV1: motor.RegParams = { + kp: 7000, + ki: 350, + kd: 350, + kv: 166, + ka: 11000, + kc: 72, + maxIOut: 1023, + unwindFactor: 1, +}; + +const RegV2: motor.RegParams = { + kp: 7000, + ki: 350, + kd: 400, + kv: 166, + ka: 16000, + kc: 72, + maxIOut: 1023, + unwindFactor: 1, +}; + +type RobutekConfig = { + robutekDiameter: number; + wheelDiameter: number; +}; + +type RobutekLedcConfig = { + timer: number; + channels: [number, number, number, number]; +}; + +const EncoderTicksV1 = 812; +const EncoderTicksV2 = 560; + +export type SensorType = + | "WheelFL" // Wheel Front Left ... + | "WheelFR" // Wheel Front Right + | "WheelBL" + | "WheelBR" + | "LineFL" + | "LineFR" + | "LineBL" + | "LineBR"; + +export class Robutek< + PinsType extends typeof PinsV1 | typeof PinsV2, +> extends DifferentialDrive { + public readonly Pins: PinsType; + public bq2589?: BQ2589; + + readonly PenPos = { + Down: 512 + 50, + Up: 512 - 180, + Unload: 150, + }; + + constructor( + pins: PinsType, + encTicks: number, + reg: motor.RegParams, + robutekConfig: RobutekConfig, + private ledcConfig: RobutekLedcConfig, + ) { + const wheelCircumference = Math.PI * robutekConfig.wheelDiameter; + ledc.configureTimer(ledcConfig.timer, 20000, 10); + + const leftMotorPins: motor.MotorPins = { + motA: pins.Motor1A, + motB: pins.Motor1B, + encA: pins.Enc1A, + encB: pins.Enc1B, + }; + const rightMotorPins: motor.MotorPins = { + motA: pins.Motor2A, + motB: pins.Motor2B, + encA: pins.Enc2A, + encB: pins.Enc2B, + }; + + const leftMotorLedc: motor.LedcConfig = { + timer: ledcConfig.timer, + channelA: ledcConfig.channels[0], + channelB: ledcConfig.channels[1], + }; + const rightMotorLedc: motor.LedcConfig = { + timer: ledcConfig.timer, + channelA: ledcConfig.channels[2], + channelB: ledcConfig.channels[3], + }; + + const leftMotor = new motor.Motor({ + pins: leftMotorPins, + ledc: leftMotorLedc, + encTicks: encTicks, + reg, + circumference: wheelCircumference, + }); + const rightMotor = new motor.Motor({ + pins: rightMotorPins, + ledc: rightMotorLedc, + encTicks: encTicks, + reg, + circumference: wheelCircumference, + }); + + super(leftMotor, rightMotor, robutekConfig.robutekDiameter); + this.stop(); + + adc.configure(pins.Sens1, adc.Attenuation.Db0); + adc.configure(pins.Sens2, adc.Attenuation.Db0); + adc.configure(pins.Sens3, adc.Attenuation.Db0); + adc.configure(pins.Sens4, adc.Attenuation.Db0); + + gpio.pinMode(pins.SensEN, gpio.PinMode.OUTPUT); + gpio.write(pins.SensEN, 1); + + gpio.pinMode(pins.SensSW, gpio.PinMode.OUTPUT); + + gpio.pinMode(pins.StatusLED, gpio.PinMode.OUTPUT); + + this.Pins = pins; + this.setStatusLED(0); + } + + private sw: number = 0; + public switchSensors(toValue: number) { + if (toValue == this.sw) { + return; + } + this.sw = toValue; + gpio.write(this.Pins.SensSW, toValue); + + // don't do this at home + const start = Date.now(); + while (Date.now() - start < 2); + } + + public readSensor(sensor: SensorType): number { + switch (sensor) { + case "WheelFL": + this.switchSensors(0); + return adc.read(this.Pins.Sens1); + case "WheelFR": + this.switchSensors(0); + return adc.read(this.Pins.Sens2); + case "WheelBL": + this.switchSensors(0); + return adc.read(this.Pins.Sens3); + case "WheelBR": + this.switchSensors(0); + return adc.read(this.Pins.Sens4); + case "LineFL": + this.switchSensors(1); + return adc.read(this.Pins.Sens1); + case "LineFR": + this.switchSensors(1); + return adc.read(this.Pins.Sens2); + case "LineBL": + this.switchSensors(1); + return adc.read(this.Pins.Sens3); + case "LineBR": + this.switchSensors(1); + return adc.read(this.Pins.Sens4); + default: + throw new Error("Invalid sensor type"); + } + } + + public setStatusLED(state: number) { + gpio.write(this.Pins.StatusLED, state); + } + + public close() { + this.leftMotor.close(); + this.rightMotor.close(); + gpio.pinMode(this.Pins.SensEN, gpio.PinMode.DISABLE); + gpio.pinMode(this.Pins.SensSW, gpio.PinMode.DISABLE); + ledc.stopTimer(this.ledcConfig.timer); + } +} + +export const defaultLedcConfig: RobutekLedcConfig = { + timer: 0, + channels: [0, 1, 2, 3], +}; + +export const defaultRobutekConfig: RobutekConfig = { + robutekDiameter: 83, // mm + wheelDiameter: 33.3, // mm +}; + +export function createRobutek( + version: "V1", + robutekConfig?: RobutekConfig, + ledcConfig?: RobutekLedcConfig, +): Robutek; +export function createRobutek( + version: "V2", + robutekConfig?: RobutekConfig, + ledcConfig?: RobutekLedcConfig, +): Robutek; +export function createRobutek( + version: "V1" | "V2", + robutekConfig: RobutekConfig = defaultRobutekConfig, + ledcConfig: RobutekLedcConfig = defaultLedcConfig, +): Robutek { + if (version === "V1") { + return new Robutek( + PinsV1, + EncoderTicksV1, + RegV1, + robutekConfig, + ledcConfig, + ) as Robutek; + } else { + const robutek = new Robutek( + PinsV2, + EncoderTicksV2, + RegV2, + robutekConfig, + ledcConfig, + ) as Robutek; + I2C1.setup({ scl: robutek.Pins.SCL, sda: robutek.Pins.SDA }); + robutek.bq2589 = new BQ2589(I2C1); + robutek.bq2589.startBatteryChecker((status) => + robutek.setStatusLED(status), + ); + return robutek; + } +} diff --git a/robutek/tsconfig.json b/robutek/tsconfig.json new file mode 100644 index 0000000..ab82ab7 --- /dev/null +++ b/robutek/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true, + "types": ["jaculus"] + } +} diff --git a/servo/.gitignore b/servo/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/servo/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/servo/README.md b/servo/README.md new file mode 100644 index 0000000..98c99bb --- /dev/null +++ b/servo/README.md @@ -0,0 +1 @@ +# Servo motor control library diff --git a/servo/blocks/servo.jacly.json b/servo/blocks/servo.jacly.json new file mode 100644 index 0000000..64a4f7d --- /dev/null +++ b/servo/blocks/servo.jacly.json @@ -0,0 +1,99 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "category": "servo", + "parentCategory": "movement", + "name": "%T%", + "description": "%T%", + "colour": "#AC8FBC", + "icon": "ServerCog", + "priority": 140, + "import": ["import { Servo } from \"servo\";"], + "contents": [ + { + "kind": "label", + "text": "%SETUP%" + }, + { + "kind": "block", + "type": "servo_constructor", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "servo_?" + }, + { + "type": "input_value", + "name": "SERVO_PIN", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 42 + } + } + }, + { + "type": "input_value", + "name": "TIMER", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "CHANNEL", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + } + ], + "constructs": "servo", + "tooltip": "%T%", + "inputsInline": true, + "code": "$[CONSTRUCTED_VAR_NAME] = new Servo($[SERVO_PIN], $[TIMER], $[CHANNEL]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "label", + "text": "%WRITE%" + }, + { + "kind": "block", + "type": "servo_write", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SERVO_INSTANCE", + "instanceof": "servo" + }, + { + "type": "input_value", + "name": "POSITION", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + } + ], + "tooltip": "%T%", + "code": "$[SERVO_INSTANCE].write($[POSITION]);", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/servo/blocks/translations/cs.lang.json b/servo/blocks/translations/cs.lang.json new file mode 100644 index 0000000..d480005 --- /dev/null +++ b/servo/blocks/translations/cs.lang.json @@ -0,0 +1,11 @@ +{ + "SERVO_NAME": "Servo", + "SERVO_DESCRIPTION": "Knihovna pro řízení servo motorů", + "SERVO_LABEL_SETUP": "Nastavení (je potřeba pro vytvoření instance servo motoru)", + "SERVO_LABEL_WRITE": "Zapsat do servo motoru (nastavit pozici)", + + "SERVO_CONSTRUCTOR_MESSAGE0": "vytvořit $[CONSTRUCTED_VAR_NAME] servo na pinu $[SERVO_PIN]\n s časovačem $[TIMER] a kanálem $[CHANNEL]", + "SERVO_CONSTRUCTOR_TOOLTIP": "Vytvoří novou instanci servo motoru", + "SERVO_WRITE_MESSAGE0": "nastavit $[SERVO_INSTANCE] na pozici $[POSITION] (0-1023)", + "SERVO_WRITE_TOOLTIP": "Nastaví servo na zadanou pozici" +} diff --git a/servo/blocks/translations/en.lang.json b/servo/blocks/translations/en.lang.json new file mode 100644 index 0000000..bceb068 --- /dev/null +++ b/servo/blocks/translations/en.lang.json @@ -0,0 +1,11 @@ +{ + "SERVO_NAME": "Servo", + "SERVO_DESCRIPTION": "Servo motor control library", + "SERVO_LABEL_SETUP": "Setup (this is required to create a servo instance)", + "SERVO_LABEL_WRITE": "Write to servo (set position)", + + "SERVO_CONSTRUCTOR_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] servo on pin $[SERVO_PIN]\n with timer $[TIMER] and channel $[CHANNEL]", + "SERVO_CONSTRUCTOR_TOOLTIP": "Create a new Servo instance", + "SERVO_WRITE_MESSAGE0": "set $[SERVO_INSTANCE] to position $[POSITION] (0-1023)", + "SERVO_WRITE_TOOLTIP": "Set the servo to the specified position" +} diff --git a/servo/package.json b/servo/package.json new file mode 100644 index 0000000..59c6e23 --- /dev/null +++ b/servo/package.json @@ -0,0 +1,28 @@ +{ + "name": "servo", + "version": "0.1.0", + "author": "Jakub Andrysek", + "license": "MIT", + "description": "Servo motor control library", + "type": "module", + "main": "dist/js/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "dependencies": { + "@types/jaculus": "0.1.0" + }, + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/servo/src/index.ts b/servo/src/index.ts new file mode 100644 index 0000000..deeef96 --- /dev/null +++ b/servo/src/index.ts @@ -0,0 +1,34 @@ +import * as ledc from "ledc"; + +/** + * A class for controlling a servo motor. + */ +export class Servo { + private channel: number; // the PWM channel to use + + /** + * Create a new servo object. + * @param pin The pin the servo is connected to. + * @param timer The timer to use for PWM. + * @param channel The channel to use for PWM. + */ + constructor(pin: number, timer: number, channel: number) { + this.channel = channel; + ledc.configureTimer(timer, 50, 12); // 50Hz is the frequency of a servo, 12 is the resolution of the timer + ledc.configureChannel(channel, pin, timer, 1023); // 1023 is the resolution of a servo + } + + /** + * Set the servo position. + * @param value The position to set the servo to, from 0-1023. + */ + write(value: number) { + // map the value from 0-1023 to 0.5-2.5ms + const ms = (value / 1023) * 2 + 0.5; // 0.5-2.5ms is the range of a servo + + // convert to a duty cycle + const duty = (ms / 20) * 1023; // 20ms is the period of a servo + + ledc.setDuty(this.channel, duty); // set the duty cycle to the servo + } +} diff --git a/servo/tsconfig.json b/servo/tsconfig.json new file mode 100644 index 0000000..ab82ab7 --- /dev/null +++ b/servo/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true, + "types": ["jaculus"] + } +} diff --git a/smartled-ext/.gitignore b/smartled-ext/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/smartled-ext/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/smartled-ext/README.md b/smartled-ext/README.md new file mode 100644 index 0000000..46a57c6 --- /dev/null +++ b/smartled-ext/README.md @@ -0,0 +1 @@ +# Colors utility functions and types diff --git a/smartled-ext/blocks/smartled_ext.jacly.json b/smartled-ext/blocks/smartled_ext.jacly.json new file mode 100644 index 0000000..be41c69 --- /dev/null +++ b/smartled-ext/blocks/smartled_ext.jacly.json @@ -0,0 +1,233 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "smartled-ext", + "category": "smartled_ext", + "parentCategory": "smartled", + "name": "%T%", + "description": "%T%", + "colour": "#00A4A6", + "icon": "Lightbulb", + "priority": 135, + "import": [ + "import { SmartLedExt } from \"smartled-ext\";", + "import * as colors from \"colors\";" + ], + "contents": [ + { + "kind": "block", + "type": "strip_ext_constructor", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "stripExt_?" + }, + { + "type": "field_dropdown", + "name": "STRIP_INSTANCE", + "instanceof": "strip" + } + ], + "hideInToolbox": true, + "constructs": "stripExt", + "tooltip": "%T%", + "code": "$[CONSTRUCTED_VAR_NAME] = new SmartLedExt($[STRIP_INSTANCE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "strip_ext_constructor_range", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "stripExt_?" + }, + { + "type": "field_dropdown", + "name": "STRIP_INSTANCE", + "instanceof": "strip" + }, + { + "type": "input_value", + "name": "INDEX_FROM", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { "NUM": 0 } + } + }, + { + "type": "input_value", + "name": "INDEX_TO", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { "NUM": 7 } + } + } + ], + "constructs": "stripExt", + "tooltip": "%T%", + "code": "$[CONSTRUCTED_VAR_NAME] = new SmartLedExt($[STRIP_INSTANCE], $[INDEX_FROM], $[INDEX_TO]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "strip_ext_set_all", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "STRIP_EXT_INSTANCE", + "instanceof": "stripExt" + }, + { + "type": "input_value", + "name": "COLOR_RGB", + "check": "Color", + "block": { + "type": "dynamic_color_rgb" + } + } + ], + "tooltip": "%T%", + "inputsInline": true, + "code": "$[STRIP_EXT_INSTANCE].setAll($[COLOR_RGB]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "strip_ext_max_brightness", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "STRIP_EXT_INSTANCE", + "instanceof": "stripExt" + }, + { + "type": "input_value", + "name": "BRIGHTNESS", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { "NUM": 255 } + } + } + ], + "tooltip": "%T%", + "inputsInline": true, + "code": "$[STRIP_EXT_INSTANCE].maxBrightness = $[BRIGHTNESS];", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "strip_ext_max_brightness_percent", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "STRIP_EXT_INSTANCE", + "instanceof": "stripExt" + }, + { + "type": "input_value", + "name": "PERCENT", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { "NUM": 100 } + } + } + ], + "tooltip": "%T%", + "inputsInline": true, + "code": "$[STRIP_EXT_INSTANCE].setMaxBrightnessPercent($[PERCENT]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "strip_ext_linearize", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "STRIP_EXT_INSTANCE", + "instanceof": "stripExt" + }, + { + "type": "field_dropdown", + "name": "ENABLED", + "options": [ + ["%LINEARIZE_ENABLED%", "true"], + ["%LINEARIZE_DISABLED%", "false"] + ] + } + ], + "tooltip": "%T%", + "inputsInline": true, + "code": "$[STRIP_EXT_INSTANCE].doLinearize = $[ENABLED];", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "strip_ext_set", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "STRIP_EXT_INSTANCE", + "instanceof": "stripExt" + }, + { + "type": "input_value", + "name": "INDEX", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { "NUM": 0 } + } + }, + { + "type": "input_value", + "name": "COLOR_RGB", + "check": "Color", + "block": { + "type": "dynamic_color_rgb" + } + } + ], + "tooltip": "%T%", + "inputsInline": true, + "code": "$[STRIP_EXT_INSTANCE].set($[INDEX], $[COLOR_RGB]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "strip_ext_clear", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "STRIP_EXT_INSTANCE", + "instanceof": "stripExt" + } + ], + "tooltip": "%T%", + "inputsInline": true, + "code": "$[STRIP_EXT_INSTANCE].clear();", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/smartled-ext/blocks/translations/cs.lang.json b/smartled-ext/blocks/translations/cs.lang.json new file mode 100644 index 0000000..237ae03 --- /dev/null +++ b/smartled-ext/blocks/translations/cs.lang.json @@ -0,0 +1,29 @@ +{ + "SMARTLED_EXT_NAME": "SmartLED Ext", + "SMARTLED_EXT_DESCRIPTION": "Rozšířené bloky pro ovládání WS2812 LED pásků s omezením jasu a gama korekcí.", + + "STRIP_EXT_CONSTRUCTOR_MESSAGE0": "vytvoř $[CONSTRUCTED_VAR_NAME] pásky (ext) z $[STRIP_INSTANCE]", + "STRIP_EXT_CONSTRUCTOR_TOOLTIP": "Vytvoří nový SmartLedExt pásky obal z existujícího SmartLed pásku", + "STRIP_EXT_CONSTRUCTOR_RANGE_MESSAGE0": "vytvoř $[CONSTRUCTED_VAR_NAME] pásky (ext) z $[STRIP_INSTANCE] indexy $[INDEX_FROM] až $[INDEX_TO] (oba včetně)", + "STRIP_EXT_CONSTRUCTOR_RANGE_TOOLTIP": "Vytvoří nový SmartLedExt pásky obal pro rozsah LED. Počáteční i koncový index jsou včetně.", + + "STRIP_EXT_SET_ALL_MESSAGE0": "nastav všechny LED pásku $[STRIP_EXT_INSTANCE] na barvu $[COLOR_RGB]", + "STRIP_EXT_SET_ALL_TOOLTIP": "Nastaví všechny LED v pásku na danou barvu", + + "STRIP_EXT_MAX_BRIGHTNESS_MESSAGE0": "nastav maximální jas pásku $[STRIP_EXT_INSTANCE] na $[BRIGHTNESS] (0-255)", + "STRIP_EXT_MAX_BRIGHTNESS_TOOLTIP": "Nastaví maximální jas pásku (0 = vypnuto, 255 = plný jas)", + + "STRIP_EXT_MAX_BRIGHTNESS_PERCENT_MESSAGE0": "nastav maximální jas pásku $[STRIP_EXT_INSTANCE] na $[PERCENT] %", + "STRIP_EXT_MAX_BRIGHTNESS_PERCENT_TOOLTIP": "Nastaví maximální jas jako procento (0 = vypnuto, 100 = plný jas)", + + "STRIP_EXT_LINEARIZE_MESSAGE0": "nastav gama linearizaci pásku $[STRIP_EXT_INSTANCE] na $[ENABLED]", + "STRIP_EXT_LINEARIZE_TOOLTIP": "Zapne nebo vypne gama korekci pro lineárně vnímané kroky jasu na WS2812 / NeoPixel páscích", + "STRIP_EXT_LINEARIZE_ARGS0_FIELD_DROPDOWN_LINEARIZE_ENABLED": "zapnuto", + "STRIP_EXT_LINEARIZE_ARGS0_FIELD_DROPDOWN_LINEARIZE_DISABLED": "vypnuto", + + "STRIP_EXT_SET_MESSAGE0": "nastav LED pásku $[STRIP_EXT_INSTANCE] na indexu $[INDEX] na barvu $[COLOR_RGB]", + "STRIP_EXT_SET_TOOLTIP": "Nastaví jednu LED v pásku na danou barvu", + + "STRIP_EXT_CLEAR_MESSAGE0": "vypni všechny LED pásku $[STRIP_EXT_INSTANCE]", + "STRIP_EXT_CLEAR_TOOLTIP": "Vypne všechny LED v pásku" +} diff --git a/smartled-ext/blocks/translations/en.lang.json b/smartled-ext/blocks/translations/en.lang.json new file mode 100644 index 0000000..7950850 --- /dev/null +++ b/smartled-ext/blocks/translations/en.lang.json @@ -0,0 +1,29 @@ +{ + "SMARTLED_EXT_NAME": "SmartLED Ext", + "SMARTLED_EXT_DESCRIPTION": "Extended WS2812 smart LED control blocks with brightness limiting and gamma correction.", + + "STRIP_EXT_CONSTRUCTOR_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] strip (ext) from $[STRIP_INSTANCE]", + "STRIP_EXT_CONSTRUCTOR_TOOLTIP": "Create a new SmartLedExt strip wrapper from an existing SmartLed strip", + "STRIP_EXT_CONSTRUCTOR_RANGE_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] strip (ext) from $[STRIP_INSTANCE] indices $[INDEX_FROM] to $[INDEX_TO] (inclusive)", + "STRIP_EXT_CONSTRUCTOR_RANGE_TOOLTIP": "Create a new SmartLedExt strip wrapper for a range of LEDs. Both start and end indices are inclusive.", + + "STRIP_EXT_SET_ALL_MESSAGE0": "set all LEDs of $[STRIP_EXT_INSTANCE] to color $[COLOR_RGB]", + "STRIP_EXT_SET_ALL_TOOLTIP": "Set all LEDs in the strip to the given color", + + "STRIP_EXT_MAX_BRIGHTNESS_MESSAGE0": "set max brightness of $[STRIP_EXT_INSTANCE] to $[BRIGHTNESS] (0-255)", + "STRIP_EXT_MAX_BRIGHTNESS_TOOLTIP": "Set the maximum brightness for the strip (0 = off, 255 = full)", + + "STRIP_EXT_MAX_BRIGHTNESS_PERCENT_MESSAGE0": "set max brightness of $[STRIP_EXT_INSTANCE] to $[PERCENT] %", + "STRIP_EXT_MAX_BRIGHTNESS_PERCENT_TOOLTIP": "Set the maximum brightness as a percentage (0 = off, 100 = full)", + + "STRIP_EXT_LINEARIZE_MESSAGE0": "set gamma linearization of $[STRIP_EXT_INSTANCE] to $[ENABLED]", + "STRIP_EXT_LINEARIZE_TOOLTIP": "Enable or disable gamma correction for perceptually linear brightness steps on WS2812 / NeoPixel strips", + "STRIP_EXT_LINEARIZE_ARGS0_FIELD_DROPDOWN_LINEARIZE_ENABLED": "enabled", + "STRIP_EXT_LINEARIZE_ARGS0_FIELD_DROPDOWN_LINEARIZE_DISABLED": "disabled", + + "STRIP_EXT_SET_MESSAGE0": "set LED of $[STRIP_EXT_INSTANCE] at index $[INDEX] to color $[COLOR_RGB]", + "STRIP_EXT_SET_TOOLTIP": "Set a single LED in the strip to the given color", + + "STRIP_EXT_CLEAR_MESSAGE0": "turn off all LEDs of $[STRIP_EXT_INSTANCE]", + "STRIP_EXT_CLEAR_TOOLTIP": "Turn off all LEDs in the strip" +} diff --git a/smartled-ext/package.json b/smartled-ext/package.json new file mode 100644 index 0000000..658cb16 --- /dev/null +++ b/smartled-ext/package.json @@ -0,0 +1,29 @@ +{ + "name": "smartled-ext", + "version": "0.1.0", + "author": "Jakub Andrysek", + "license": "MIT", + "description": "Extended SmartLED utility functions and types", + "type": "module", + "main": "dist/js/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "dependencies": { + "@types/jaculus": "0.1.0", + "colors": "0.1.0" + }, + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/smartled-ext/src/index.ts b/smartled-ext/src/index.ts new file mode 100644 index 0000000..0a5b739 --- /dev/null +++ b/smartled-ext/src/index.ts @@ -0,0 +1,124 @@ +import { SmartLed } from "smartled"; +import { linearize, stretchChannelsEvenly } from "colors"; +export { + LedType, + LED_WS2812, + LED_WS2812B, + LED_WS2812B_2020, + LED_SK6812, + LED_WS2813, +} from "smartled"; + +export interface SetOptions { + /** Override the strip's linearize setting for this call. */ + linearize?: boolean; + /** Override the strip's maxBrightness for this call (0-255). */ + maxBrightness?: number; +} + +export class SmartLedExt { + protected smartLed: SmartLed; + protected indexFrom: number = 0; + protected indexTo: number; + private _maxBrightness: number = 255; + /** + * Get the current maximum brightness for the strip (0-255). Applied as a multiplier to all colors. + */ + public get maxBrightness(): number { + return this._maxBrightness; + } + + /** + * Set the maximum brightness for the strip (0-255). Applied as a multiplier to all colors. + * Values outside the range will be clamped. + */ + public set maxBrightness(value: number) { + this._maxBrightness = Math.round(Math.min(Math.max(value, 0), 255)); + } + + /** + * When true, gamma linearization is applied before sending each color. + * Recommended for WS2812 / NeoPixel strips to get perceptually linear steps. + */ + public doLinearize: boolean = true; + + // TODO: fix ranges + constructor(smartLed: SmartLed, indexFrom?: number, indexTo?: number) { + this.smartLed = smartLed; + // nut public number for now set to infinity + const count = Infinity; // smartLed.count; + if (indexFrom !== undefined || indexTo !== undefined) { + const from = indexFrom ?? 0; + const to = indexTo ?? count - 1; + if (from < 0 || to >= count || from > to) { + throw new RangeError( + "Invalid indexFrom/indexTo range for SmartLedExt", + ); + } + this.indexFrom = from; + this.indexTo = to; + } else { + this.indexFrom = 0; + this.indexTo = count - 1; + } + + // console.log(`Leds ${this.indexFrom} to ${this.indexTo} mapped to SmartLed with ${count} leds`); + } + + /** Apply the configured pipeline (brightness limit + optional linearization) to a color. */ + private applyPipeline(color: Rgb, opts?: SetOptions): Rgb { + const maxBr = opts?.maxBrightness ?? this.maxBrightness; + const doLin = opts?.linearize ?? this.doLinearize; + let c = maxBr < 255 ? stretchChannelsEvenly(color, maxBr) : color; + if (doLin) c = linearize(c); + return c; + } + + /** + * Set the color of a single LED, applying the brightness limiter and + * linearization pipeline. + * @param index LED index (0 - (indexTo - indexFrom)) + * @param color RGB color + * @param opts Per-call overrides for linearize / maxBrightness + */ + public set(index: number, color: Rgb, opts?: SetOptions): void { + if (index < 0 || index > this.indexTo - this.indexFrom) { + throw new RangeError("LED index out of range for this SmartLedExt"); + } + this.smartLed.set( + this.indexFrom + index, + this.applyPipeline(color, opts), + ); + } + + /** + * Set the color of all LEDs in the strip. + * @param color RGB color + * @param opts Per-call overrides for linearize / maxBrightness + */ + public setAll(color: Rgb, opts?: SetOptions): void { + const processed = this.applyPipeline(color, opts); + for (let i = this.indexFrom; i <= this.indexTo; i++) { + this.smartLed.set(i, processed); + } + } + + /** + * Turn off all LEDs in the strip. + */ + public clear(): void { + for (let i = this.indexFrom; i <= this.indexTo; i++) { + this.smartLed.set(i, 0); + } + } + + /** + * Set maxBrightness as a percentage (0-100). + * @param percent Brightness percentage (0 = off, 100 = full) + */ + public setMaxBrightnessPercent(percent: number): void { + this.maxBrightness = Math.round( + (Math.min(Math.max(percent, 0), 100) * 255) / 100, + ); + } +} diff --git a/smartled-ext/tsconfig.json b/smartled-ext/tsconfig.json new file mode 100644 index 0000000..ab82ab7 --- /dev/null +++ b/smartled-ext/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true, + "types": ["jaculus"] + } +} diff --git a/template-jacly-elks/.gitignore b/template-jacly-elks/.gitignore new file mode 100644 index 0000000..3e2e84b --- /dev/null +++ b/template-jacly-elks/.gitignore @@ -0,0 +1,2 @@ +build/ +node_modules/ diff --git a/template-jacly-elks/package.json b/template-jacly-elks/package.json new file mode 100644 index 0000000..d35f89b --- /dev/null +++ b/template-jacly-elks/package.json @@ -0,0 +1,25 @@ +{ + "name": "template-jacly-elks", + "version": "0.1.0", + "description": "Jacly Block template for ELKS (ESP Learning Kit Starter)", + "type": "module", + "main": "./build/index.js", + "files": [ + "src/**/*" + ], + "dependencies": { + "@types/jaculus": "0.1.0", + "basic": "0.1.0", + "jacly-meta-blocks": "0.1.0", + "colors": "0.1.0", + "elks": "0.1.0" + }, + "jaculus": { + "packageFormat": 1, + "projectType": "jacly", + "template": true, + "registry": [ + "https://registry.jaculus.org/" + ] + } +} diff --git a/template-jacly-elks/src/index.jacly b/template-jacly-elks/src/index.jacly new file mode 100644 index 0000000..fc510b0 --- /dev/null +++ b/template-jacly-elks/src/index.jacly @@ -0,0 +1,490 @@ +{ + "blocks": { + "languageVersion": 0, + "blocks": [ + { + "type": "basic_onStart", + "id": "hQFI0yMV.M4I)wpjC#Xv", + "x": 210, + "y": 50, + "extraState": { + "package": "basic" + }, + "inputs": { + "CODE": { + "block": { + "type": "gpio_pinMode", + "id": "V}@0+*mC/q^.m}I#j@$N", + "extraState": { + "package": "@types/jaculus" + }, + "fields": { + "MODE": "gpio.PinMode.INPUT" + }, + "inputs": { + "PIN": { + "shadow": { + "type": "math_number", + "id": "p3N:]ob%R,4s#)?Hg!/I", + "fields": { + "NUM": 5 + } + }, + "block": { + "type": "elks_pins_buttons", + "id": ".yZ)uf{+#.j^WBy2+3sY", + "extraState": { + "package": "elks" + }, + "fields": { + "PIN": "elks.Pins.SW0" + } + } + } + }, + "next": { + "block": { + "type": "gpio_pinMode", + "id": "3[hRXA,rE,fpX#|Wt=Ur", + "extraState": { + "package": "@types/jaculus" + }, + "fields": { + "MODE": "gpio.PinMode.OUTPUT" + }, + "inputs": { + "PIN": { + "shadow": { + "type": "math_number", + "id": "595K:Y5n0Tz_n4Y#-_5c", + "fields": { + "NUM": 5 + } + }, + "block": { + "type": "elks_pins_leds", + "id": "%8yb[-:t0}im?GJ?Ria{", + "extraState": { + "package": "elks" + }, + "fields": { + "PIN": "elks.Pins.LED_G" + } + } + } + }, + "next": { + "block": { + "type": "adc_configure", + "id": "Mf^Ax.wT|HW8lBO_3M5T", + "extraState": { + "package": "@types/jaculus" + }, + "fields": { + "ATTEN": "adc.Attenuation.Db12" + }, + "inputs": { + "PIN": { + "shadow": { + "type": "math_number", + "id": "~RT+:cdFR5u*X:QhFxC^", + "fields": { + "NUM": 1 + } + }, + "block": { + "type": "elks_pins_potentiometers", + "id": "8ol+g[^VUPnrKxwwJH0~", + "extraState": { + "package": "elks" + }, + "fields": { + "PIN": "elks.Pins.POT0" + } + } + } + }, + "next": { + "block": { + "type": "strip_constructor", + "id": ":#uhp[QynRGA$6+gE9bU", + "extraState": { + "package": "@types/jaculus" + }, + "fields": { + "CONSTRUCTED_VAR_NAME": "strip", + "LED_TYPE": "LED_WS2812" + }, + "inputs": { + "LED_PIN": { + "shadow": { + "type": "math_number", + "id": "=ZDFT;}}N`Q=@${-Iu,m", + "fields": { + "NUM": 48 + } + }, + "block": { + "type": "elks_pins_smartleds", + "id": "}s,SN%9^?:?o.n2|DSc6", + "extraState": { + "package": "elks" + }, + "fields": { + "PIN": "elks.Pins.ILED_ESP32" + } + } + }, + "LED_COUNT": { + "shadow": { + "type": "math_number", + "id": "P,7Q8T6Bh-,xH)Wve8m,", + "fields": { + "NUM": 1 + } + } + } + }, + "next": { + "block": { + "type": "strip_set", + "id": "9s47F}{64KYobNW1x-Wc", + "extraState": { + "package": "@types/jaculus", + "instanceName": "strip" + }, + "fields": { + "STRIP_INSTANCE": "strip" + }, + "inputs": { + "INDEX": { + "shadow": { + "type": "math_number", + "id": "{yB$FPF%:W[7dplz2.4C", + "fields": { + "NUM": 0 + } + } + }, + "COLOR_RGB": { + "block": { + "type": "dynamic_color_rgb", + "id": "3AmTUa%gPAY;@Lp2iasm", + "extraState": { + "mode": "NAMES", + "colorName": "green" + }, + "fields": { + "MODE": "NAMES", + "COLOR_NAME": "green" + } + } + } + }, + "next": { + "block": { + "type": "strip_show", + "id": "]HO8_8]EeU{|$bRGm)qI", + "extraState": { + "package": "@types/jaculus", + "instanceName": "strip" + }, + "fields": { + "STRIP_INSTANCE": "strip" + }, + "next": { + "block": { + "type": "basic_console_log", + "id": "G([U~A6.`;oRhOY.nd?I", + "extraState": { + "package": "basic" + }, + "fields": { + "MODE": "log" + }, + "inputs": { + "TEXT": { + "shadow": { + "type": "text", + "id": "RG*h,*o*T9|Hv!3|s4yP", + "fields": { + "TEXT": "init" + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + { + "type": "gpio_onEvent", + "id": "T4eCz9j^C~R;lA.!8i;}", + "x": 210, + "y": 510, + "extraState": { + "package": "@types/jaculus" + }, + "fields": { + "EVENT": "\"falling\"" + }, + "inputs": { + "PIN": { + "shadow": { + "type": "math_number", + "id": "q,?GdSiDD?Jzfl0dwN]z", + "fields": { + "NUM": 5 + } + }, + "block": { + "type": "elks_pins_buttons", + "id": "72+))xSnkUMKLtxjkBg/", + "extraState": { + "package": "elks" + }, + "fields": { + "PIN": "elks.Pins.SW0" + } + } + }, + "CALLBACK_VAR_millis": { + "block": { + "type": "gpio_onEvent_millis", + "id": "@8H)u~WDr%GPf1=8Zmti" + } + }, + "CALLBACK_VAR_micros": { + "block": { + "type": "gpio_onEvent_micros", + "id": "t^?7C:{b6NBy?hBwZO{~" + } + }, + "CODE": { + "block": { + "type": "gpio_write", + "id": "/z1E9v-?BHVg)iKV]A|U", + "extraState": { + "package": "@types/jaculus" + }, + "inputs": { + "PIN": { + "shadow": { + "type": "math_number", + "id": "Uq.f/bC(Vnq*3r{27v!-", + "fields": { + "NUM": 5 + } + }, + "block": { + "type": "elks_pins_leds", + "id": "VVyh%SZ-@!rk`b9T(7xq", + "extraState": { + "package": "elks" + }, + "fields": { + "PIN": "elks.Pins.LED_G" + } + } + }, + "VALUE": { + "shadow": { + "type": "math_number", + "id": "JsGQ5gl$Ji_o)tzPk:R0", + "fields": { + "NUM": 1 + } + } + } + }, + "next": { + "block": { + "type": "strip_set", + "id": "^m@,(+s*V`;}qh/4_EQF", + "extraState": { + "package": "@types/jaculus", + "instanceName": "strip" + }, + "fields": { + "STRIP_INSTANCE": "strip" + }, + "inputs": { + "INDEX": { + "shadow": { + "type": "math_number", + "id": "1%/h6to^,xoYYoL;!h4l", + "fields": { + "NUM": 0 + } + } + }, + "COLOR_RGB": { + "block": { + "type": "dynamic_color_rgb", + "id": "BkKksH)I;S`x(-Mi21IU", + "inline": false, + "extraState": { + "mode": "HSL" + }, + "fields": { + "MODE": "HSL" + }, + "inputs": { + "H_INPUT": { + "shadow": { + "type": "math_number", + "id": "rjZ5j{OLZHb|xJeRczWv", + "fields": { + "NUM": 0 + } + }, + "block": { + "type": "adc_read", + "id": "s=ryI1Twib=c%y8Z)n?K", + "extraState": { + "package": "@types/jaculus" + }, + "inputs": { + "PIN": { + "shadow": { + "type": "math_number", + "id": "3V78=AC5E`RW{^R1!PSp", + "fields": { + "NUM": 1 + } + }, + "block": { + "type": "elks_pins_potentiometers", + "id": ":X`ddJ7/8{]WxI*|3s`b", + "extraState": { + "package": "elks" + }, + "fields": { + "PIN": "elks.Pins.POT0" + } + } + } + } + } + }, + "S_INPUT": { + "shadow": { + "type": "math_number", + "id": "P.UmU|Y`KF[|3IwgT?}s", + "fields": { + "NUM": 100 + } + } + }, + "L_INPUT": { + "shadow": { + "type": "math_number", + "id": "6Km#_Wm]iT/KQ`+^wN^3", + "fields": { + "NUM": 50 + } + } + } + } + } + } + }, + "next": { + "block": { + "type": "strip_show", + "id": "v?nCgVLcYl`EnQO!(KIP", + "extraState": { + "package": "@types/jaculus", + "instanceName": "strip" + }, + "fields": { + "STRIP_INSTANCE": "strip" + }, + "next": { + "block": { + "type": "basic_sleep", + "id": "b@(vR6.`H^w-/jqyt0{0", + "extraState": { + "package": "basic" + }, + "inputs": { + "TIME": { + "shadow": { + "type": "math_number", + "id": "6#@kIOV$}`VRSlu61|Z`", + "fields": { + "NUM": 500 + } + } + } + }, + "next": { + "block": { + "type": "gpio_write", + "id": "nMZPKKa1o,^whXcLURv9", + "extraState": { + "package": "@types/jaculus" + }, + "inputs": { + "PIN": { + "shadow": { + "type": "math_number", + "id": "QUDn4#mYH]p]Z$fu^6:t", + "fields": { + "NUM": 5 + } + }, + "block": { + "type": "elks_pins_leds", + "id": "9w8M/t$3wwN_jsjaipJr", + "extraState": { + "package": "elks" + }, + "fields": { + "PIN": "elks.Pins.LED_G" + } + } + }, + "VALUE": { + "shadow": { + "type": "math_number", + "id": "/[Z+-hjmYq[|8(f+Qm2)", + "fields": { + "NUM": 0 + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + ] + }, + "variables": [ + { + "name": "adc", + "id": "Hz-ewxsKd)/mve+#J^i]" + } + ] +} diff --git a/template-jacly-robutek2/.gitignore b/template-jacly-robutek2/.gitignore new file mode 100644 index 0000000..3e2e84b --- /dev/null +++ b/template-jacly-robutek2/.gitignore @@ -0,0 +1,2 @@ +build/ +node_modules/ diff --git a/template-jacly-robutek2/package.json b/template-jacly-robutek2/package.json new file mode 100644 index 0000000..99be066 --- /dev/null +++ b/template-jacly-robutek2/package.json @@ -0,0 +1,25 @@ +{ + "name": "template-jacly-robutek2", + "version": "0.1.0", + "description": "Jacly Block template for Robutek2", + "type": "module", + "main": "./build/index.js", + "files": [ + "src/**/*" + ], + "dependencies": { + "@types/jaculus": "0.1.0", + "basic": "0.1.0", + "jacly-meta-blocks": "0.1.0", + "colors": "0.1.0", + "robutek": "0.1.0" + }, + "jaculus": { + "packageFormat": 1, + "projectType": "jacly", + "template": true, + "registry": [ + "https://registry.jaculus.org/" + ] + } +} diff --git a/template-jacly-robutek2/src/index.jacly b/template-jacly-robutek2/src/index.jacly new file mode 100644 index 0000000..24402c0 --- /dev/null +++ b/template-jacly-robutek2/src/index.jacly @@ -0,0 +1,625 @@ +{ + "blocks": { + "languageVersion": 0, + "blocks": [ + { + "type": "basic_onStart", + "id": ":9ns(fy,^*$.]|L1d.Dk", + "x": -90, + "y": -30, + "extraState": { + "package": "basic" + }, + "inputs": { + "CODE": { + "block": { + "type": "robutek2_constructor", + "id": "v9A[u=!jVFmY9!p.+%ct", + "extraState": { + "package": "robutek" + }, + "fields": { + "CONSTRUCTED_VAR_NAME": "robutek2" + }, + "inputs": { + "ROBUTEK_DIAMETER": { + "shadow": { + "type": "math_number", + "id": "Ws`V%o#@3IZ3z`TaGs7^", + "fields": { + "NUM": 83 + } + } + }, + "WHEEL_DIAMETER": { + "shadow": { + "type": "math_number", + "id": "!iwh~2.^^M]XU+i]?RW]", + "fields": { + "NUM": 33.3 + } + } + } + }, + "next": { + "block": { + "type": "differential_drive_setSpeed", + "id": "CtR$mSj1:s!11)nsf2Zo", + "extraState": { + "package": "differential-drive", + "instanceName": "robutek2.differentialDrive" + }, + "fields": { + "DRIVE_INSTANCE": "robutek2.differentialDrive" + }, + "inputs": { + "SPEED": { + "shadow": { + "type": "math_number", + "id": ")^rROnbeK$~~qB3~P#:|", + "fields": { + "NUM": 100 + } + } + } + }, + "next": { + "block": { + "type": "differential_drive_setRamp", + "id": "Ej0V?86xO0Fkqp(SG$|-", + "extraState": { + "package": "differential-drive", + "instanceName": "robutek2.differentialDrive" + }, + "fields": { + "DRIVE_INSTANCE": "robutek2.differentialDrive" + }, + "inputs": { + "RAMP": { + "shadow": { + "type": "math_number", + "id": "Aw72^,ZBi-})3$N8Dj3`", + "fields": { + "NUM": 2000 + } + } + } + }, + "next": { + "block": { + "type": "gpio_pinMode", + "id": "xWu@(Q.E(GdO;Ei#k%6-", + "extraState": { + "package": "@types/jaculus" + }, + "fields": { + "MODE": "gpio.PinMode.INPUT" + }, + "inputs": { + "PIN": { + "shadow": { + "type": "math_number", + "id": "U`4-WR_h#!f%CD=HtS~k", + "fields": { + "NUM": 5 + } + }, + "block": { + "type": "robutek2_pins_buttons", + "id": "oCCKZ;0iQNGyCtZfbd`Z", + "extraState": { + "package": "robutek" + }, + "fields": { + "PIN": "2" + } + } + } + }, + "next": { + "block": { + "type": "gpio_pinMode", + "id": "mhm-Mi!,Fp1[XO-$rqa1", + "extraState": { + "package": "@types/jaculus" + }, + "fields": { + "MODE": "gpio.PinMode.OUTPUT" + }, + "inputs": { + "PIN": { + "shadow": { + "type": "math_number", + "id": "d~};GYz{5+xQnurO8.U^", + "fields": { + "NUM": 5 + } + }, + "block": { + "type": "robutek2_pins_led", + "id": "CCK.$RP+_n/sLi`|4e1m", + "extraState": { + "package": "robutek" + }, + "fields": { + "PIN": "46" + } + } + } + }, + "next": { + "block": { + "type": "basic_console_log", + "id": "z.vsi5J*]xISTyV(PenY", + "extraState": { + "package": "basic" + }, + "fields": { + "MODE": "log" + }, + "inputs": { + "TEXT": { + "shadow": { + "type": "text", + "id": "(6K3q`^AfHi;G~``GR;n", + "fields": { + "TEXT": "init" + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + { + "type": "basic_forever", + "id": "[=R7Kk6ylDDm*ivbk%UA", + "x": 610, + "y": -30, + "extraState": { + "package": "basic" + }, + "inputs": { + "CODE": { + "block": { + "type": "basic_console_keyval", + "id": ":UCW6a,Z{+Hk-]Qq2(;:", + "extraState": { + "package": "basic" + }, + "inputs": { + "KEY": { + "shadow": { + "type": "text", + "id": "ik@e%cy^pX1tWT1guBhv", + "fields": { + "TEXT": "left_e" + } + } + }, + "VALUE": { + "shadow": { + "type": "text", + "id": "KU!y]XS9N.Ty{FtO6]#Q", + "fields": { + "TEXT": "" + } + }, + "block": { + "type": "motor_getPosition", + "id": "`A8_xb)La3Ob/fb`1@H7", + "extraState": { + "package": "@types/jaculus", + "instanceName": "robutek2.leftMotor" + }, + "fields": { + "MOTOR_INSTANCE": "robutek2.leftMotor" + } + } + } + }, + "next": { + "block": { + "type": "basic_console_keyval", + "id": "..FMYjLP7!:g2.s8Ww~P", + "extraState": { + "package": "basic" + }, + "inputs": { + "KEY": { + "shadow": { + "type": "text", + "id": "IXX3y9^dpi+.V({azFJ@", + "fields": { + "TEXT": "right_e" + } + } + }, + "VALUE": { + "shadow": { + "type": "text", + "id": "nbXvO@gJPLlv~a(1H.ia", + "fields": { + "TEXT": "" + } + }, + "block": { + "type": "motor_getPosition", + "id": "aSXxA#,PiNj5tr*DFIw:", + "extraState": { + "package": "@types/jaculus", + "instanceName": "robutek2.rightMotor" + }, + "fields": { + "MOTOR_INSTANCE": "robutek2.rightMotor" + } + } + } + }, + "next": { + "block": { + "type": "basic_console_keyval", + "id": "fV~?AOv|O8mvm^Bt$$ja", + "extraState": { + "package": "basic" + }, + "inputs": { + "KEY": { + "shadow": { + "type": "text", + "id": "V:UwK7DCTjPuqXS2fq!E", + "fields": { + "TEXT": "line_f_left" + } + } + }, + "VALUE": { + "shadow": { + "type": "text", + "id": "BSwpS!$uCNq_mS9s5r7K", + "fields": { + "TEXT": "" + } + }, + "block": { + "type": "robutek2_readSensor", + "id": ")3xp${qU-Nri=t5q7#Br", + "extraState": { + "package": "robutek", + "instanceName": "robutek2" + }, + "fields": { + "ROBUTEK2_INSTANCE": "robutek2", + "SENSOR": "LineFL" + } + } + } + }, + "next": { + "block": { + "type": "basic_console_keyval", + "id": "FM=BXr(3c7yJuI{^0]~/", + "extraState": { + "package": "basic" + }, + "inputs": { + "KEY": { + "shadow": { + "type": "text", + "id": "x9wJv:eL/3F5vsQHLc2;", + "fields": { + "TEXT": "line_f_right" + } + } + }, + "VALUE": { + "shadow": { + "type": "text", + "id": "cc{j%i7{]ED90ac+$Gk;", + "fields": { + "TEXT": "" + } + }, + "block": { + "type": "robutek2_readSensor", + "id": "@(:~g%)~R#dN;cG#9j:v", + "extraState": { + "package": "robutek", + "instanceName": "robutek2" + }, + "fields": { + "ROBUTEK2_INSTANCE": "robutek2", + "SENSOR": "LineFR" + } + } + } + }, + "next": { + "block": { + "type": "basic_sleep", + "id": "R%(ue}}[,slbBIisnYh]", + "extraState": { + "package": "basic" + }, + "inputs": { + "TIME": { + "shadow": { + "type": "math_number", + "id": "Gc#4CQFS(mAq1mR7b5p+", + "fields": { + "NUM": 100 + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + { + "type": "gpio_onEvent", + "id": "e_wjoCa`w%sra|!99-:{", + "x": -90, + "y": 650, + "extraState": { + "package": "@types/jaculus" + }, + "fields": { + "EVENT": "\"falling\"" + }, + "inputs": { + "PIN": { + "shadow": { + "type": "math_number", + "id": "[qcaOPx6!B4i{B)rKUfy", + "fields": { + "NUM": 5 + } + }, + "block": { + "type": "robutek2_pins_buttons", + "id": "*nhxBztDFqi]%/cfX#j~", + "extraState": { + "package": "robutek" + }, + "fields": { + "PIN": "2" + } + } + }, + "CALLBACK_VAR_millis": { + "block": { + "type": "gpio_onEvent_millis", + "id": "@D{yq@rP#0Nv:tLz/Vl(" + } + }, + "CALLBACK_VAR_micros": { + "block": { + "type": "gpio_onEvent_micros", + "id": "T6vZXvq$y:3T(*SwV/ye" + } + }, + "CODE": { + "block": { + "type": "gpio_write", + "id": "*PF3/$`sHVo@sS|^GX+1", + "extraState": { + "package": "@types/jaculus" + }, + "inputs": { + "PIN": { + "shadow": { + "type": "math_number", + "id": "qH7G[5r*Y`m1g5XZ2rV3", + "fields": { + "NUM": 5 + } + }, + "block": { + "type": "robutek2_pins_led", + "id": "-tBzJuJx?Cc$|=dG3()e", + "extraState": { + "package": "robutek" + }, + "fields": { + "PIN": "46" + } + } + }, + "VALUE": { + "shadow": { + "type": "math_number", + "id": "tpxIZ$K[Y~|;}a=Gb|Su", + "fields": { + "NUM": 1 + } + } + } + }, + "next": { + "block": { + "type": "basic_sleep", + "id": "0b}_2Jo^%ak%pWrJP9by", + "extraState": { + "package": "basic" + }, + "inputs": { + "TIME": { + "shadow": { + "type": "math_number", + "id": "wn@v-EY_}OD]=QC(=YfO", + "fields": { + "NUM": 1000 + } + } + } + }, + "next": { + "block": { + "type": "gpio_write", + "id": ":*{3?ECFR^Z`(IY;4{eY", + "extraState": { + "package": "@types/jaculus" + }, + "inputs": { + "PIN": { + "shadow": { + "type": "math_number", + "id": "KH6$e).LWi$$cnVrhNur", + "fields": { + "NUM": 5 + } + }, + "block": { + "type": "robutek2_pins_led", + "id": "`qR9tN.~3^txTHO?f(Bf", + "extraState": { + "package": "robutek" + }, + "fields": { + "PIN": "46" + } + } + }, + "VALUE": { + "shadow": { + "type": "math_number", + "id": "2kpG3x%R*]VO2fR7gaW!", + "fields": { + "NUM": 0 + } + } + } + }, + "next": { + "block": { + "type": "procedures_callnoreturn", + "id": "eAa2D8XyX6gyTLN5O~{$", + "extraState": { + "name": "square" + } + } + } + } + } + } + } + } + } + } + }, + { + "type": "procedures_defnoreturn", + "id": "2Jm4wx8%`axOsl2vXa4F", + "x": 610, + "y": 650, + "icons": { + "comment": { + "text": "Describe this function...", + "pinned": false, + "height": 80, + "width": 160 + } + }, + "fields": { + "NAME": "square" + }, + "inputs": { + "STACK": { + "block": { + "type": "controls_repeat_ext", + "id": "vW5?mApa/|DJyqkV7!l@", + "inputs": { + "TIMES": { + "shadow": { + "type": "math_number", + "id": "Z{B==F[;m:g|8M|r{_uK", + "fields": { + "NUM": 4 + } + } + }, + "DO": { + "block": { + "type": "differential_drive_move_distance", + "id": "|eEvbY7rF}3g7l,M6)Hm", + "extraState": { + "package": "differential-drive", + "instanceName": "robutek2.differentialDrive" + }, + "fields": { + "DRIVE_INSTANCE": "robutek2.differentialDrive" + }, + "inputs": { + "CURVE": { + "shadow": { + "type": "math_number", + "id": "@#@}P~07n+{q0C25N~|:", + "fields": { + "NUM": 0 + } + } + }, + "DISTANCE": { + "shadow": { + "type": "math_number", + "id": "I,M#sv^GSrsd3]jofnr+", + "fields": { + "NUM": 100 + } + } + } + }, + "next": { + "block": { + "type": "differential_drive_rotate", + "id": "u-wS6Q{`^+qSOSPtVC18", + "extraState": { + "package": "differential-drive", + "instanceName": "robutek2.differentialDrive" + }, + "fields": { + "DRIVE_INSTANCE": "robutek2.differentialDrive" + }, + "inputs": { + "ANGLE": { + "shadow": { + "type": "math_number", + "id": "@.:aN9c5a@~C;Ova0)9C", + "fields": { + "NUM": 90 + } + } + } + } + } + } + } + } + } + } + } + } + } + ] + } +} diff --git a/template-jacly/.gitignore b/template-jacly/.gitignore new file mode 100644 index 0000000..3e2e84b --- /dev/null +++ b/template-jacly/.gitignore @@ -0,0 +1,2 @@ +build/ +node_modules/ diff --git a/template-jacly/package.json b/template-jacly/package.json new file mode 100644 index 0000000..4db90ed --- /dev/null +++ b/template-jacly/package.json @@ -0,0 +1,24 @@ +{ + "name": "template-jacly", + "version": "0.1.0", + "description": "Default Jacly Block template project", + "type": "module", + "main": "./build/index.js", + "files": [ + "src/**/*" + ], + "dependencies": { + "@types/jaculus": "0.1.0", + "basic": "0.1.0", + "jacly-meta-blocks": "0.1.0", + "colors": "0.1.0" + }, + "jaculus": { + "packageFormat": 1, + "projectType": "jacly", + "template": true, + "registry": [ + "https://registry.jaculus.org/" + ] + } +} diff --git a/template-jacly/src/index.jacly b/template-jacly/src/index.jacly new file mode 100644 index 0000000..cc87027 --- /dev/null +++ b/template-jacly/src/index.jacly @@ -0,0 +1,19 @@ +{ + "blocks": { + "languageVersion": 0, + "blocks": [ + { + "type": "basic_onStart", + "id": "N#c!0i;vxI:x1JWmcoX:", + "x": 70, + "y": 30 + }, + { + "type": "basic_forever", + "id": "rH^*3,Q-@Jd2x$b}K1fz", + "x": 370, + "y": 30 + } + ] + } +} diff --git a/template-jaculus-robutek2/.gitignore b/template-jaculus-robutek2/.gitignore new file mode 100644 index 0000000..3e2e84b --- /dev/null +++ b/template-jaculus-robutek2/.gitignore @@ -0,0 +1,2 @@ +build/ +node_modules/ diff --git a/template-jaculus-robutek2/package.json b/template-jaculus-robutek2/package.json new file mode 100644 index 0000000..d85d93b --- /dev/null +++ b/template-jaculus-robutek2/package.json @@ -0,0 +1,23 @@ +{ + "name": "template-jaculus-robutek2", + "version": "0.1.0", + "description": "Jaculus TS template for Robutek2", + "type": "module", + "main": "./build/index.js", + "files": [ + "src/**/*" + ], + "dependencies": { + "@types/jaculus": "0.1.0", + "colors": "0.1.0", + "robutek": "0.1.0" + }, + "jaculus": { + "packageFormat": 1, + "projectType": "code", + "template": true, + "registry": [ + "https://registry.jaculus.org/" + ] + } +} diff --git a/template-jaculus-robutek2/src/index.ts b/template-jaculus-robutek2/src/index.ts new file mode 100644 index 0000000..a89c67f --- /dev/null +++ b/template-jaculus-robutek2/src/index.ts @@ -0,0 +1,16 @@ +import { createRobutek } from "robutek"; + +const robutek = createRobutek("V2"); +robutek.setSpeed(100); +robutek.setRamp(2000); + +setInterval(() => { + const fl = robutek.readSensor("LineFL"); + const fr = robutek.readSensor("LineFR"); + console.log(`Sensors FL: ${fl.toFixed(2)}, FR: ${fr.toFixed(2)}`); +}, 1000); + +for (let i = 0; i < 4; i++) { + await robutek.move(0, { distance: 100 }); + await robutek.rotate(90); +} diff --git a/template-jaculus-robutek2/tsconfig.json b/template-jaculus-robutek2/tsconfig.json new file mode 100644 index 0000000..da20ce4 --- /dev/null +++ b/template-jaculus-robutek2/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "sourceMap": false, + "outDir": "build", + "rootDir": "src", + "types": ["jaculus"] + } +} diff --git a/template-jaculus-smartled/.gitignore b/template-jaculus-smartled/.gitignore new file mode 100644 index 0000000..3e2e84b --- /dev/null +++ b/template-jaculus-smartled/.gitignore @@ -0,0 +1,2 @@ +build/ +node_modules/ diff --git a/template-jaculus-smartled/package.json b/template-jaculus-smartled/package.json new file mode 100644 index 0000000..334ec25 --- /dev/null +++ b/template-jaculus-smartled/package.json @@ -0,0 +1,22 @@ +{ + "name": "template-jaculus-smartled", + "version": "0.1.0", + "description": "Jaculus TS template for SmartLed", + "type": "module", + "main": "./build/index.js", + "files": [ + "src/**/*" + ], + "dependencies": { + "@types/jaculus": "0.1.0", + "colors": "0.1.0" + }, + "jaculus": { + "packageFormat": 1, + "projectType": "code", + "template": true, + "registry": [ + "https://registry.jaculus.org/" + ] + } +} diff --git a/template-jaculus-smartled/src/index.ts b/template-jaculus-smartled/src/index.ts new file mode 100644 index 0000000..34bb269 --- /dev/null +++ b/template-jaculus-smartled/src/index.ts @@ -0,0 +1,8 @@ +import { LED_WS2812B, SmartLed } from "smartled"; +import * as colors from "colors"; + +const ledStrip = new SmartLed(48, 1, LED_WS2812B); // led strip on pin 48 with 1 LED + +ledStrip.clear(); // clear the strip (turn off all LEDs) +ledStrip.set(0, colors.red); // set the first LED (index 0) to red color +ledStrip.show(); // update the strip to show the changes diff --git a/template-jaculus-smartled/tsconfig.json b/template-jaculus-smartled/tsconfig.json new file mode 100644 index 0000000..da20ce4 --- /dev/null +++ b/template-jaculus-smartled/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "sourceMap": false, + "outDir": "build", + "rootDir": "src", + "types": ["jaculus"] + } +} diff --git a/template-jaculus/.gitignore b/template-jaculus/.gitignore new file mode 100644 index 0000000..3e2e84b --- /dev/null +++ b/template-jaculus/.gitignore @@ -0,0 +1,2 @@ +build/ +node_modules/ diff --git a/template-jaculus/package.json b/template-jaculus/package.json new file mode 100644 index 0000000..c4db91b --- /dev/null +++ b/template-jaculus/package.json @@ -0,0 +1,22 @@ +{ + "name": "template-jaculus", + "version": "0.1.0", + "description": "Default Jaculus TS template project", + "type": "module", + "main": "./build/index.js", + "files": [ + "src/**/*" + ], + "dependencies": { + "@types/jaculus": "0.1.0", + "colors": "0.1.0" + }, + "jaculus": { + "packageFormat": 1, + "projectType": "code", + "template": true, + "registry": [ + "https://registry.jaculus.org/" + ] + } +} diff --git a/template-jaculus/src/index.ts b/template-jaculus/src/index.ts new file mode 100644 index 0000000..e97301d --- /dev/null +++ b/template-jaculus/src/index.ts @@ -0,0 +1,16 @@ +import * as gpio from "gpio"; + +/** + * This example blinks an LED on pin 46. + */ + +const LED_PIN = 46; + +gpio.pinMode(LED_PIN, gpio.PinMode.OUTPUT); + +let state = false; + +setInterval(() => { + gpio.write(LED_PIN, state ? 1 : 0); + state = !state; +}, 1000); diff --git a/template-jaculus/tsconfig.json b/template-jaculus/tsconfig.json new file mode 100644 index 0000000..da20ce4 --- /dev/null +++ b/template-jaculus/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "sourceMap": false, + "outDir": "build", + "rootDir": "src", + "types": ["jaculus"] + } +} diff --git a/utils-i2c/.gitignore b/utils-i2c/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/utils-i2c/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/utils-i2c/README.md b/utils-i2c/README.md new file mode 100644 index 0000000..6d99bf7 --- /dev/null +++ b/utils-i2c/README.md @@ -0,0 +1 @@ +# Jaculus Utilities Library for I2C Communication diff --git a/utils-i2c/blocks/translations/cs.lang.json b/utils-i2c/blocks/translations/cs.lang.json new file mode 100644 index 0000000..54e7ca3 --- /dev/null +++ b/utils-i2c/blocks/translations/cs.lang.json @@ -0,0 +1,10 @@ +{ + "UTILS_I2C_NAME": "I2C Nástroje", + "UTILS_I2C_DESCRIPTION": "Užitečné bloky pro I2C periferie, jako je skenování zařízení na sběrnici.", + + "UTILS_I2C_SCAN_MESSAGE0": "skenovat I2C sběrnici $[I2C_INSTANCE]", + "UTILS_I2C_SCAN_TOOLTIP": "Skenuje I2C sběrnici pro připojená zařízení a vrací pole jejich adres", + + "UTILS_I2C_SCAN_PRINT_MESSAGE0": "vytisknout sken I2C sběrnice $[I2C_INSTANCE]", + "UTILS_I2C_SCAN_PRINT_TOOLTIP": "Skenuje I2C sběrnici a vytiskne adresy připojených zařízení do konzole" +} diff --git a/utils-i2c/blocks/translations/en.lang.json b/utils-i2c/blocks/translations/en.lang.json new file mode 100644 index 0000000..9118391 --- /dev/null +++ b/utils-i2c/blocks/translations/en.lang.json @@ -0,0 +1,10 @@ +{ + "UTILS_I2C_NAME": "I2C Utils", + "UTILS_I2C_DESCRIPTION": "Utility blocks for I2C peripherals, such as scanning for devices on the bus.", + + "UTILS_I2C_SCAN_MESSAGE0": "scan I2C bus of $[I2C_INSTANCE]", + "UTILS_I2C_SCAN_TOOLTIP": "Scan the I2C bus for connected devices and return an array of their addresses", + + "UTILS_I2C_SCAN_PRINT_MESSAGE0": "print scan of I2C bus of $[I2C_INSTANCE]", + "UTILS_I2C_SCAN_PRINT_TOOLTIP": "Scan the I2C bus and print the addresses of connected devices to the console" +} diff --git a/utils-i2c/blocks/utils_i2c.jacly.json b/utils-i2c/blocks/utils_i2c.jacly.json new file mode 100644 index 0000000..a299c3c --- /dev/null +++ b/utils-i2c/blocks/utils_i2c.jacly.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "package": "utils-i2c", + "category": "utils_i2c", + "parentCategory": "protocols", + "name": "%T%", + "description": "%T%", + "colour": "#0F5ACA", + "icon": "Cable", + "priority": 135, + "import": ["import { i2cScan, i2cScanPrint } from \"utils-i2c\";"], + "contents": [ + { + "kind": "block", + "type": "utils_i2c_scan", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "I2C_INSTANCE", + "instanceof": "i2c_inst" + } + ], + "output": "Array", + "tooltip": "%T%", + "code": "i2cScan($[I2C_INSTANCE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "utils_i2c_scan_print", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "I2C_INSTANCE", + "instanceof": "i2c_inst" + } + ], + "tooltip": "%T%", + "code": "i2cScanPrint($[I2C_INSTANCE]);", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/utils-i2c/package.json b/utils-i2c/package.json new file mode 100644 index 0000000..e9ed0d1 --- /dev/null +++ b/utils-i2c/package.json @@ -0,0 +1,24 @@ +{ + "name": "utils-i2c", + "version": "0.1.0", + "author": "Jakub Andrysek", + "license": "MIT", + "description": "Jaculus utilities library for I2C communication", + "type": "module", + "main": "dist/js/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "dependencies": { + "@types/jaculus": "0.1.0" + }, + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + } +} diff --git a/utils-i2c/src/index.ts b/utils-i2c/src/index.ts new file mode 100644 index 0000000..821b7ed --- /dev/null +++ b/utils-i2c/src/index.ts @@ -0,0 +1,40 @@ +import { I2C } from "i2c"; + +export function readInt16(bus: I2C, address: number, reg: number): number { + let value = bus.writeRead(address, reg, 2); + return value[0] | (value[1] << 8); +} + +export function readInt32(bus: I2C, address: number, reg: number): number { + let value = bus.writeRead(address, reg, 4); + return value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24); +} + +export function i2cScan(bus: I2C): number[] { + let found: number[] = []; + + for (let addr = 0x08; addr <= 0x77; addr++) { + try { + bus.readFrom(addr, 1); + found.push(addr); + } catch (e) { + // No device at this address + } + } + + return found; +} + +export function i2cScanPrint(bus: I2C) { + console.log("Scanning I2C bus for devices..."); + let found = i2cScan(bus); + if (found.length === 0) { + console.log("No I2C devices found."); + } else { + console.log("I2C devices found at addresses:"); + for (let addr of found) { + console.log(`- 0x${addr.toString(16).padStart(2, "0")}`); + } + console.log(`Total devices found: ${found.length}`); + } +} diff --git a/utils-i2c/tsconfig.json b/utils-i2c/tsconfig.json new file mode 100644 index 0000000..ab82ab7 --- /dev/null +++ b/utils-i2c/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true, + "types": ["jaculus"] + } +} diff --git a/utils/.gitignore b/utils/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/utils/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/utils/README.md b/utils/README.md new file mode 100644 index 0000000..05ad175 --- /dev/null +++ b/utils/README.md @@ -0,0 +1,104 @@ +# Jaculus Utilities Library + +Collection of practical helper functions for value normalization, range mapping, and bounds limiting. + +## Overview + +This package currently provides three numeric helpers: + +- `scale` for normalized values in range `[0, 1]` +- `map` for mapping from one range to another +- `clamp` for hard-limiting to min/max bounds + +## scale + +### What It Does + +`scale(value, min, max)` converts a value from range `[min, max]` to normalized `[0, 1]`, then limits the result to `[0, 1]`. + +### Formula + +``` +scaled = (value - min) / (max - min) +result = clamp(scaled, 0, 1) +``` + +### Edge Case + +If `min === max`, the function returns `0` to avoid division by zero. + +### Example + +```typescript +import * as utils from "utils"; + +console.log(utils.scale(512, 0, 1023)); // ~0.5 +console.log(utils.scale(1400, 0, 1023)); // 1 +console.log(utils.scale(-10, 0, 1023)); // 0 +``` + +## map + +### What It Does + +`map(value, inMin, inMax, outMin, outMax)` linearly converts a value from one range to another. + +### Formula + +``` +result = ((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin +``` + +### Edge Case + +If `inMin === inMax`, the function returns `outMin`. + +### Example + +```typescript +import * as utils from "utils"; + +// Map ADC value (0..1023) to PWM duty (0..255) +const duty = utils.map(512, 0, 1023, 0, 255); +console.log(duty); // ~127.6 +``` + +## clamp + +### What It Does + +`clamp(value, min, max)` limits a value so that it always stays inside `[min, max]`. + +### Example + +```typescript +import * as utils from "utils"; + +console.log(utils.clamp(1200, 0, 1023)); // 1023 +console.log(utils.clamp(-5, 0, 1023)); // 0 +console.log(utils.clamp(500, 0, 1023)); // 500 +``` + +## Quick Comparison + +- Use `scale` when you need normalized output in `[0, 1]`. +- Use `map` when you need to convert between arbitrary ranges. +- Use `clamp` when you need to enforce hard safety limits. + +## Typical Robotics Usage + +```typescript +import * as adc from "adc"; +import * as ledc from "ledc"; +import * as utils from "utils"; + +adc.configure(1); +ledc.configureTimer(0, 1000); +ledc.configureChannel(0, 46, 0, 1023); + +setInterval(() => { + const raw = adc.read(1); + const duty = utils.map(raw, 0, 1023, 0, 1023); + ledc.setDuty(0, utils.clamp(duty, 0, 1023)); +}, 20); +``` diff --git a/utils/blocks/translations/cs.lang.json b/utils/blocks/translations/cs.lang.json new file mode 100644 index 0000000..43971a1 --- /dev/null +++ b/utils/blocks/translations/cs.lang.json @@ -0,0 +1,10 @@ +{ + "UTILS_NAME": "Nástroje", + "UTILS_DESCRIPTION": "Knihovna nástrojů pro Jaculus s funkcemi pro mapování a škálování", + "UTILS_MAP_MESSAGE0": "převeď %[VALUE] z rozsahu [%FROM_LOW%, %FROM_HIGH%] do rozsahu [%TO_LOW%, %TO_HIGH%]", + "UTILS_MAP_TOOLTIP": "Převede číslo z jednoho rozsahu do druhého", + "UTILS_SCALE_MESSAGE0": "škáluj %[VALUE] z rozsahu [%FROM_LOW%, %FROM_HIGH%] do rozsahu [0, 1]", + "UTILS_SCALE_TOOLTIP": "Škáluje číslo do rozsahu 0-1 na základě zadaných minimálních a maximálních hodnot", + "UTILS_CLAMP_MESSAGE0": "omez %[VALUE] do rozsahu [%MIN%, %MAX%]", + "UTILS_CLAMP_TOOLTIP": "Omezí číslo tak, aby zůstalo mezi minimem a maximem" +} diff --git a/utils/blocks/translations/en.lang.json b/utils/blocks/translations/en.lang.json new file mode 100644 index 0000000..d8e186b --- /dev/null +++ b/utils/blocks/translations/en.lang.json @@ -0,0 +1,10 @@ +{ + "UTILS_NAME": "Utils", + "UTILS_DESCRIPTION": "Jaculus utilities library with mapping and scaling functions", + "UTILS_MAP_MESSAGE0": "map %[VALUE] from range [%FROM_LOW%, %FROM_HIGH%] to range [%TO_LOW%, %TO_HIGH%]", + "UTILS_MAP_TOOLTIP": "Maps a number from one range to another", + "UTILS_SCALE_MESSAGE0": "scale %[VALUE] from range [%FROM_LOW%, %FROM_HIGH%] to range [0, 1]", + "UTILS_SCALE_TOOLTIP": "Scales a number to a range of 0-1 based on the given minimum and maximum values", + "UTILS_CLAMP_MESSAGE0": "clamp %[VALUE] to range [%MIN%, %MAX%]", + "UTILS_CLAMP_TOOLTIP": "Clamps a number to stay between minimum and maximum" +} diff --git a/utils/blocks/utils.jacly.json b/utils/blocks/utils.jacly.json new file mode 100644 index 0000000..99a2268 --- /dev/null +++ b/utils/blocks/utils.jacly.json @@ -0,0 +1,163 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "category": "utils", + "name": "%T%", + "description": "%T%", + "colour": "#AC8FBC", + "icon": "Toolbox", + "priority": 140, + "import": ["import * as utils from \"utils\";"], + "contents": [ + { + "kind": "block", + "type": "utils_map", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "VALUE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 512 + } + } + }, + { + "type": "input_value", + "name": "FROM_LOW", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "FROM_HIGH", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1023 + } + } + }, + { + "type": "input_value", + "name": "TO_LOW", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "TO_HIGH", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 255 + } + } + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "utils.map($[VALUE], $[FROM_LOW], $[FROM_HIGH], $[TO_LOW], $[TO_HIGH])" + }, + { + "kind": "block", + "type": "utils_scale", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "VALUE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 512 + } + } + }, + { + "type": "input_value", + "name": "MIN", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "MAX", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1023 + } + } + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "utils.scale($[VALUE], $[MIN], $[MAX])" + }, + { + "kind": "block", + "type": "utils_clamp", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "VALUE", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 512 + } + } + }, + { + "type": "input_value", + "name": "MIN", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 0 + } + } + }, + { + "type": "input_value", + "name": "MAX", + "check": "Number", + "shadow": { + "type": "math_number", + "fields": { + "NUM": 1023 + } + } + } + ], + "output": "Number", + "tooltip": "%T%", + "code": "utils.clamp($[VALUE], $[MIN], $[MAX])" + } + ] +} diff --git a/utils/package.json b/utils/package.json new file mode 100644 index 0000000..b38b0e0 --- /dev/null +++ b/utils/package.json @@ -0,0 +1,25 @@ +{ + "name": "utils", + "version": "0.1.0", + "author": "Jakub Andrysek", + "license": "MIT", + "description": "Jaculus utilities library (scaling, clamping, mapping, etc.)", + "type": "module", + "main": "dist/js/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/utils/src/index.ts b/utils/src/index.ts new file mode 100644 index 0000000..95b2dc2 --- /dev/null +++ b/utils/src/index.ts @@ -0,0 +1,45 @@ +/** + * Scales a value to a range of 0-1 based on the given minimum and maximum values. + * @param value The value to scale. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @returns The scaled value between 0 and 1. + */ +export function scale(value: number, min: number, max: number): number { + if (min === max) return 0; + + const val = (value - min) / (max - min); + return Math.min(Math.max(val, 0), 1); +} + +/** + * Maps a value from one range to another. + * @param value The value to map. + * @param inMin The minimum value of the input range. + * @param inMax The maximum value of the input range. + * @param outMin The minimum value of the output range. + * @param outMax The maximum value of the output range. + * @returns The mapped value in the output range. + */ +export function map( + value: number, + inMin: number, + inMax: number, + outMin: number, + outMax: number, +): number { + if (inMin === inMax) return outMin; + + return ((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin; +} + +/** + * Clamps a value between a minimum and maximum. + * @param value The value to clamp. + * @param min The minimum value to clamp to. + * @param max The maximum value to clamp to. + * @returns The clamped value. + */ +export function clamp(value: number, min: number, max: number): number { + return Math.min(Math.max(value, min), max); +} diff --git a/utils/tsconfig.json b/utils/tsconfig.json new file mode 100644 index 0000000..3a3fbba --- /dev/null +++ b/utils/tsconfig.json @@ -0,0 +1,14 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true + } +} diff --git a/vl53l0x/.gitignore b/vl53l0x/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/vl53l0x/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/vl53l0x/README.md b/vl53l0x/README.md new file mode 100644 index 0000000..baca031 --- /dev/null +++ b/vl53l0x/README.md @@ -0,0 +1 @@ +# VL53L0X time-of-flight distance sensor library diff --git a/vl53l0x/blocks/translations/cs.lang.json b/vl53l0x/blocks/translations/cs.lang.json new file mode 100644 index 0000000..247a06c --- /dev/null +++ b/vl53l0x/blocks/translations/cs.lang.json @@ -0,0 +1,14 @@ +{ + "VL53L0X_NAME": "VL53L0X", + "VL53L0X_DESCRIPTION": "Knihovna pro senzor vzdálenosti VL53L0X (time-of-flight)", + "VL53L0X_CONSTRUCTOR_MESSAGE0": "vytvořit $[CONSTRUCTED_VAR_NAME] VL53L0X na I2C sběrnici $[I2C_INSTANCE]", + "VL53L0X_CONSTRUCTOR_TOOLTIP": "Vytvoří instanci senzoru VL53L0X na zadané I2C sběrnici", + "VL53L0X_READ_MEASUREMENT_MESSAGE0": "přečíst měření z $[SENSOR_INSTANCE]", + "VL53L0X_READ_MEASUREMENT_TOOLTIP": "Provede měření a vrátí výsledný objekt", + "VL53L0X_UNPACK_MEASUREMENT_MESSAGE0": "z $[MEASUREMENT] získat $[FIELD]", + "VL53L0X_UNPACK_MEASUREMENT_TOOLTIP": "Získat konkrétní hodnotu z výsledku měření VL53L0X", + "VL53L0X_UNPACK_MEASUREMENT_ARGS0_FIELD_DROPDOWN_DISTANCE": "vzdálenost (mm)", + "VL53L0X_UNPACK_MEASUREMENT_ARGS0_FIELD_DROPDOWN_SIGNAL_RATE": "úroveň signálu", + "VL53L0X_UNPACK_MEASUREMENT_ARGS0_FIELD_DROPDOWN_AMBIENT_RATE": "úroveň okolního světla", + "VL53L0X_UNPACK_MEASUREMENT_ARGS0_FIELD_DROPDOWN_EFFECTIVE_SPAD": "efektivní počet SPAD" +} diff --git a/vl53l0x/blocks/translations/en.lang.json b/vl53l0x/blocks/translations/en.lang.json new file mode 100644 index 0000000..5a3cdd5 --- /dev/null +++ b/vl53l0x/blocks/translations/en.lang.json @@ -0,0 +1,14 @@ +{ + "VL53L0X_NAME": "VL53L0X", + "VL53L0X_DESCRIPTION": "VL53L0X time-of-flight distance sensor library", + "VL53L0X_CONSTRUCTOR_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] VL53L0X on I2C bus $[I2C_INSTANCE]", + "VL53L0X_CONSTRUCTOR_TOOLTIP": "Create VL53L0X sensor instance on specified I2C bus", + "VL53L0X_READ_MEASUREMENT_MESSAGE0": "read measurement from $[SENSOR_INSTANCE]", + "VL53L0X_READ_MEASUREMENT_TOOLTIP": "Perform a measurement and return the result object", + "VL53L0X_UNPACK_MEASUREMENT_MESSAGE0": "from $[MEASUREMENT] get $[FIELD]", + "VL53L0X_UNPACK_MEASUREMENT_TOOLTIP": "Extract a specific value from a VL53L0X measurement result", + "VL53L0X_UNPACK_MEASUREMENT_ARGS0_FIELD_DROPDOWN_DISTANCE": "distance (mm)", + "VL53L0X_UNPACK_MEASUREMENT_ARGS0_FIELD_DROPDOWN_SIGNAL_RATE": "signal rate", + "VL53L0X_UNPACK_MEASUREMENT_ARGS0_FIELD_DROPDOWN_AMBIENT_RATE": "ambient rate", + "VL53L0X_UNPACK_MEASUREMENT_ARGS0_FIELD_DROPDOWN_EFFECTIVE_SPAD": "effective SPAD count" +} diff --git a/vl53l0x/blocks/vl53l0x.jacly.json b/vl53l0x/blocks/vl53l0x.jacly.json new file mode 100644 index 0000000..3223341 --- /dev/null +++ b/vl53l0x/blocks/vl53l0x.jacly.json @@ -0,0 +1,86 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "category": "vl53l0x", + "parentCategory": "peripherals", + "name": "%T%", + "description": "%T%", + "colour": "#AC8FBC", + "icon": "RulerDimensionLine", + "priority": 140, + "import": ["import { VL53L0X } from \"vl53l0x\";"], + "contents": [ + { + "kind": "block", + "type": "i2c_constructor" + }, + { + "kind": "block", + "type": "vl53l0x_constructor", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "vl53l0x_?" + }, + { + "type": "field_dropdown", + "name": "I2C_INSTANCE", + "instanceof": "i2c_inst" + } + ], + "constructs": "vl53l0x", + "tooltip": "%T%", + "code": "let $[CONSTRUCTED_VAR_NAME] = new VL53L0X($[I2C_INSTANCE]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "vl53l0x_read_measurement", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SENSOR_INSTANCE", + "instanceof": "vl53l0x" + } + ], + "hideInToolbox": true, + "tooltip": "%T%", + "code": "(await $[SENSOR_INSTANCE].read())", + "output": "VL53L0XMeasurement" + }, + { + "kind": "block", + "type": "vl53l0x_unpack_measurement", + "message0": "%T%", + "args0": [ + { + "type": "input_value", + "name": "MEASUREMENT", + "check": "VL53L0XMeasurement", + "block": { + "type": "vl53l0x_read_measurement", + "fields": { + "SENSOR_INSTANCE": "vl53l0x_?" + } + } + }, + { + "type": "field_dropdown", + "name": "FIELD", + "options": [ + ["%DISTANCE%", "distance"], + ["%SIGNAL_RATE%", "signalRate"], + ["%AMBIENT_RATE%", "ambientRate"], + ["%EFFECTIVE_SPAD%", "effectiveSpadRtnCount"] + ] + } + ], + "tooltip": "%T%", + "code": "$[MEASUREMENT].$[FIELD]", + "output": "Number" + } + ] +} diff --git a/vl53l0x/package.json b/vl53l0x/package.json new file mode 100644 index 0000000..dfb2dd4 --- /dev/null +++ b/vl53l0x/package.json @@ -0,0 +1,28 @@ +{ + "name": "vl53l0x", + "version": "0.1.0", + "author": "Jakub Andrysek", + "license": "MIT", + "description": "VL53L0X time-of-flight distance sensor library", + "type": "module", + "main": "dist/js/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "dependencies": { + "@types/jaculus": "0.1.0" + }, + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/vl53l0x/src/index.ts b/vl53l0x/src/index.ts new file mode 100644 index 0000000..06df7c2 --- /dev/null +++ b/vl53l0x/src/index.ts @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2017 Gordon Williams, Pur3 Ltd. See the file LICENSE for copying permission. + * modified from Espruino's VL53L0X module (https://www.espruino.com/VL53L0X) + */ + +import { I2C } from "i2c"; + +const C = { + REG_SYSRANGE_START: 0, + REG_RESULT_RANGE_STATUS: 0x0014, +}; + +export type Measurement = { + distance: number; + signalRate: number; + ambientRate: number; + effectiveSpadRtnCount: number; +}; + +export class VL53L0X { + private i2c: I2C; + private address: number; + private StopVariable: number = 0; + + constructor(i2c: I2C) { + this.i2c = i2c; + this.address = 0x52 >> 1; + this.init(); + } + + /** + * Set a new I2C address for the sensor. + * @note Config is not persistant through power cycle. + * @param newAddress The new I2C address to set. + */ + public setAddress(newAddress: number) { + this.w(0x8a, newAddress & 0x7f); + this.address = newAddress; + } + + /** initialise VL53L0X */ + private init() { + this.w(0x80, 0x01); + this.w(0xff, 0x01); + this.w(0x00, 0x00); + this.StopVariable = this.r(0x91, 1)[0]; + this.w(0x00, 0x01); + this.w(0xff, 0x00); + this.w(0x80, 0x00); + } + + private r(addr: number, n: number): Uint8Array { + return this.i2c.writeRead(this.address, addr, n); + } + private w(addr: number, d: number) { + this.i2c.writeTo(this.address, [addr, d]); + } + + /** + * Perform one measurement and return the result. + * Returns an object of the form: + * { + * distance , // distance in mm + * signalRate, // target reflectance + * ambientRate, // ambient light. + * effectiveSpadRtnCount // effective SPAD count for the return signal + * } + */ + async read(): Promise { + // start measurement + this.w(0x80, 0x01); + this.w(0xff, 0x01); + this.w(0x00, 0x00); + this.w(0x91, this.StopVariable); + this.w(0x00, 0x01); + this.w(0xff, 0x00); + this.w(0x80, 0x00); + // VL53L0X_DEVICEMODE_SINGLE_RANGING: + this.w(C.REG_SYSRANGE_START, 0x01); + // wait for it to finish + while (!(this.r(C.REG_RESULT_RANGE_STATUS, 1)[0] & 1)) { + await sleep(1); + } + // read and format data + var d = new DataView(this.r(0x14, 12).buffer); + var res = { + distance: d.getUint16(10), + signalRate: d.getUint16(6) / 128, + ambientRate: d.getUint16(8) / 128, + effectiveSpadRtnCount: d.getUint16(2) / 256, + }; + // TODO: use LinearityCorrectiveGain/etc + return res; + } +} diff --git a/vl53l0x/tsconfig.json b/vl53l0x/tsconfig.json new file mode 100644 index 0000000..ab82ab7 --- /dev/null +++ b/vl53l0x/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true, + "types": ["jaculus"] + } +} diff --git a/zscs2016c/.gitignore b/zscs2016c/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/zscs2016c/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/zscs2016c/README.md b/zscs2016c/README.md new file mode 100644 index 0000000..cf26f27 --- /dev/null +++ b/zscs2016c/README.md @@ -0,0 +1 @@ +# RGB sensor (zscs-2016c) diff --git a/zscs2016c/blocks/translations/cs.lang.json b/zscs2016c/blocks/translations/cs.lang.json new file mode 100644 index 0000000..3f8ee4b --- /dev/null +++ b/zscs2016c/blocks/translations/cs.lang.json @@ -0,0 +1,32 @@ +{ + "ZSCS2016C_NAME": "ZSCS2016C", + "ZSCS2016C_DESCRIPTION": "Knihovna pro barevný senzor ZSCS2016C", + "ZSCS2016C_CONSTRUCTOR_MESSAGE0": "vytvořit $[CONSTRUCTED_VAR_NAME] barevný senzor na $[I2C_INSTANCE] adresa $[ADDR_BIT]", + "ZSCS2016C_CONSTRUCTOR_TOOLTIP": "Vytvoří novou instanci barevného senzoru ZSCS2016C", + "ZSCS2016C_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_ON_BOARD": "na desce", + "ZSCS2016C_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_EXTERNAL": "externí", + "ZSCS2016C_READ_CHANNEL_MESSAGE0": "$[SENSOR_INSTANCE] přečíst kalibrovaný $[CHANNEL]", + "ZSCS2016C_READ_CHANNEL_TOOLTIP": "Přečte kalibrovanou hodnotu (0-1) vybraného barevného kanálu", + "ZSCS2016C_READ_CHANNEL_ARGS0_FIELD_DROPDOWN_RED": "červený kanál", + "ZSCS2016C_READ_CHANNEL_ARGS0_FIELD_DROPDOWN_GREEN": "zelený kanál", + "ZSCS2016C_READ_CHANNEL_ARGS0_FIELD_DROPDOWN_BLUE": "modrý kanál", + "ZSCS2016C_READ_CHANNEL_ARGS0_FIELD_DROPDOWN_IR": "IR kanál", + "ZSCS2016C_READ_CHANNEL_ARGS0_FIELD_DROPDOWN_CLEAR": "nefiltrovaný výstup", + + "ZSCS2016C_READ_RAW_CHANNEL_MESSAGE0": "$[SENSOR_INSTANCE] přečíst surový $[CHANNEL]", + "ZSCS2016C_READ_RAW_CHANNEL_TOOLTIP": "Přečte surovou hodnotu vybraného kanálu senzoru", + "ZSCS2016C_READ_RAW_CHANNEL_ARGS0_FIELD_DROPDOWN_RED": "červený kanál", + "ZSCS2016C_READ_RAW_CHANNEL_ARGS0_FIELD_DROPDOWN_GREEN": "zelený kanál", + "ZSCS2016C_READ_RAW_CHANNEL_ARGS0_FIELD_DROPDOWN_BLUE": "modrý kanál", + "ZSCS2016C_READ_RAW_CHANNEL_ARGS0_FIELD_DROPDOWN_IR": "infračervený kanál", + "ZSCS2016C_READ_RAW_CHANNEL_ARGS0_FIELD_DROPDOWN_CLEAR": "nefiltrovaný výstup", + + "ZSCS2016C_READ_RGB_MESSAGE0": "$[SENSOR_INSTANCE] přečíst kalibrované RGB", + "ZSCS2016C_READ_RGB_TOOLTIP": "Přečte kalibrované RGB hodnoty jako Rgb (0xRRGGBB) (vyžaduje provedení kalibrace nejdříve)", + "ZSCS2016C_RUN_CALIBRATION_MESSAGE0": "$[SENSOR_INSTANCE] spustit kalibraci", + "ZSCS2016C_RUN_CALIBRATION_TOOLTIP": "Spustí kalibrační proces (umístěte senzor na bílý, poté na černý povrch)", + "ZSCS2016C_SAVE_CALIBRATION_MESSAGE0": "$[SENSOR_INSTANCE] uložit kalibraci jako $[KEY]", + "ZSCS2016C_SAVE_CALIBRATION_TOOLTIP": "Uloží aktuální kalibrační data do trvalého úložiště", + "ZSCS2016C_LOAD_CALIBRATION_MESSAGE0": "$[SENSOR_INSTANCE] načíst kalibraci $[KEY]", + "ZSCS2016C_LOAD_CALIBRATION_TOOLTIP": "Načte kalibrační data z trvalého úložiště" +} diff --git a/zscs2016c/blocks/translations/en.lang.json b/zscs2016c/blocks/translations/en.lang.json new file mode 100644 index 0000000..040c084 --- /dev/null +++ b/zscs2016c/blocks/translations/en.lang.json @@ -0,0 +1,32 @@ +{ + "ZSCS2016C_NAME": "ZSCS2016C", + "ZSCS2016C_DESCRIPTION": "ZSCS2016C color sensor library", + "ZSCS2016C_CONSTRUCTOR_MESSAGE0": "create $[CONSTRUCTED_VAR_NAME] color sensor on $[I2C_INSTANCE] address $[ADDR_BIT]", + "ZSCS2016C_CONSTRUCTOR_TOOLTIP": "Create a new ZSCS2016C color sensor instance", + "ZSCS2016C_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_ON_BOARD": "on-board", + "ZSCS2016C_CONSTRUCTOR_ARGS0_FIELD_DROPDOWN_EXTERNAL": "external", + "ZSCS2016C_READ_CHANNEL_MESSAGE0": "$[SENSOR_INSTANCE] read calibrated $[CHANNEL]", + "ZSCS2016C_READ_CHANNEL_TOOLTIP": "Read calibrated value (0-1) of the selected color channel", + "ZSCS2016C_READ_CHANNEL_ARGS0_FIELD_DROPDOWN_RED": "red channel", + "ZSCS2016C_READ_CHANNEL_ARGS0_FIELD_DROPDOWN_GREEN": "green channel", + "ZSCS2016C_READ_CHANNEL_ARGS0_FIELD_DROPDOWN_BLUE": "blue channel", + "ZSCS2016C_READ_CHANNEL_ARGS0_FIELD_DROPDOWN_IR": "IR channel", + "ZSCS2016C_READ_CHANNEL_ARGS0_FIELD_DROPDOWN_CLEAR": "unfiltered color", + + "ZSCS2016C_READ_RAW_CHANNEL_MESSAGE0": "$[SENSOR_INSTANCE] read raw $[CHANNEL]", + "ZSCS2016C_READ_RAW_CHANNEL_TOOLTIP": "Read the raw value of the selected sensor channel", + "ZSCS2016C_READ_RAW_CHANNEL_ARGS0_FIELD_DROPDOWN_RED": "red channel", + "ZSCS2016C_READ_RAW_CHANNEL_ARGS0_FIELD_DROPDOWN_GREEN": "green channel", + "ZSCS2016C_READ_RAW_CHANNEL_ARGS0_FIELD_DROPDOWN_BLUE": "blue channel", + "ZSCS2016C_READ_RAW_CHANNEL_ARGS0_FIELD_DROPDOWN_IR": "IR channel", + "ZSCS2016C_READ_RAW_CHANNEL_ARGS0_FIELD_DROPDOWN_CLEAR": "unfiltered color", + + "ZSCS2016C_READ_RGB_MESSAGE0": "$[SENSOR_INSTANCE] read calibrated RGB", + "ZSCS2016C_READ_RGB_TOOLTIP": "Read calibrated RGB value (requires calibration to be performed first)", + "ZSCS2016C_RUN_CALIBRATION_MESSAGE0": "$[SENSOR_INSTANCE] run calibration", + "ZSCS2016C_RUN_CALIBRATION_TOOLTIP": "Run the calibration procedure (place sensor on white, then black surface)", + "ZSCS2016C_SAVE_CALIBRATION_MESSAGE0": "$[SENSOR_INSTANCE] save calibration as $[KEY]", + "ZSCS2016C_SAVE_CALIBRATION_TOOLTIP": "Save the current calibration data to persistent storage", + "ZSCS2016C_LOAD_CALIBRATION_MESSAGE0": "$[SENSOR_INSTANCE] load calibration $[KEY]", + "ZSCS2016C_LOAD_CALIBRATION_TOOLTIP": "Load calibration data from persistent storage" +} diff --git a/zscs2016c/blocks/zscs2016c.jacly.json b/zscs2016c/blocks/zscs2016c.jacly.json new file mode 100644 index 0000000..731282a --- /dev/null +++ b/zscs2016c/blocks/zscs2016c.jacly.json @@ -0,0 +1,180 @@ +{ + "$schema": "https://jacly.jaculus.org/schema.json", + "category": "zscs2016c", + "parentCategory": "peripherals", + "name": "%T%", + "description": "%T%", + "colour": "#AC8FBC", + "icon": "Pipette", + "priority": 140, + "import": ["import { ZSCS2016C } from \"zscs2016c\";"], + "contents": [ + { + "kind": "block", + "type": "zscs2016c_constructor", + "message0": "%T%", + "args0": [ + { + "type": "field_input", + "name": "CONSTRUCTED_VAR_NAME", + "text": "colorSensor_?" + }, + { + "type": "field_dropdown", + "name": "I2C_INSTANCE", + "instanceof": "i2c_inst" + }, + { + "type": "field_dropdown", + "name": "ADDR_BIT", + "options": [ + ["%ON_BOARD%", "true"], + ["%EXTERNAL%", "false"] + ] + } + ], + "constructs": "colorSensor", + "tooltip": "%T%", + "code": "$[CONSTRUCTED_VAR_NAME] = new ZSCS2016C($[I2C_INSTANCE], $[ADDR_BIT]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "zscs2016c_read_channel", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SENSOR_INSTANCE", + "instanceof": "colorSensor" + }, + { + "type": "field_dropdown", + "name": "CHANNEL", + "options": [ + ["%RED%", "Red"], + ["%GREEN%", "Green"], + ["%BLUE%", "Blue"], + ["%IR%", "IR"], + ["%CLEAR%", "Clear"] + ] + } + ], + "tooltip": "%T%", + "code": "$[SENSOR_INSTANCE].read$[CHANNEL]()", + "output": "Number" + }, + { + "kind": "block", + "type": "zscs2016c_read_raw_channel", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SENSOR_INSTANCE", + "instanceof": "colorSensor" + }, + { + "type": "field_dropdown", + "name": "CHANNEL", + "options": [ + ["%RED%", "Red"], + ["%GREEN%", "Green"], + ["%BLUE%", "Blue"], + ["%IR%", "IR"], + ["%CLEAR%", "Clear"] + ] + } + ], + "tooltip": "%T%", + "code": "$[SENSOR_INSTANCE].readRaw$[CHANNEL]()", + "output": "Number" + }, + { + "kind": "block", + "type": "zscs2016c_read_rgb", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SENSOR_INSTANCE", + "instanceof": "colorSensor" + } + ], + "tooltip": "%T%", + "code": "$[SENSOR_INSTANCE].readRgb()", + "output": "Color" + }, + { + "kind": "block", + "type": "zscs2016c_run_calibration", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SENSOR_INSTANCE", + "instanceof": "colorSensor" + } + ], + "tooltip": "%T%", + "code": "await $[SENSOR_INSTANCE].runCalibration();", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "zscs2016c_save_calibration", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SENSOR_INSTANCE", + "instanceof": "colorSensor" + }, + { + "type": "input_value", + "name": "KEY", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "default" + } + } + } + ], + "tooltip": "%T%", + "code": "$[SENSOR_INSTANCE].saveCalibration($[KEY]);", + "previousStatement": null, + "nextStatement": null + }, + { + "kind": "block", + "type": "zscs2016c_load_calibration", + "message0": "%T%", + "args0": [ + { + "type": "field_dropdown", + "name": "SENSOR_INSTANCE", + "instanceof": "colorSensor" + }, + { + "type": "input_value", + "name": "KEY", + "check": "String", + "shadow": { + "type": "text", + "fields": { + "TEXT": "default" + } + } + } + ], + "tooltip": "%T%", + "code": "$[SENSOR_INSTANCE].loadCalibration($[KEY]);", + "previousStatement": null, + "nextStatement": null + } + ] +} diff --git a/zscs2016c/package.json b/zscs2016c/package.json new file mode 100644 index 0000000..864b2c8 --- /dev/null +++ b/zscs2016c/package.json @@ -0,0 +1,31 @@ +{ + "name": "zscs2016c", + "version": "0.1.0", + "author": "Jakub Andrysek", + "license": "MIT", + "description": "ZSCS2016C color sensor library", + "type": "module", + "main": "dist/js/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "dependencies": { + "@types/jaculus": "0.1.0", + "utils": "0.1.0", + "utils-i2c": "0.1.0", + "colors": "0.1.0" + }, + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + } +} diff --git a/zscs2016c/src/index.ts b/zscs2016c/src/index.ts new file mode 100644 index 0000000..1c902ec --- /dev/null +++ b/zscs2016c/src/index.ts @@ -0,0 +1,297 @@ +import * as i2c from "i2c"; +import { open as kvOpen } from "keyvalue"; +import { scale, map } from "utils"; +import { rgb } from "colors"; +import { readInt16 } from "utils-i2c"; + +const ADDRESS_LOW = 0b1000_010; +const ADDRESS_HIGH = 0b1000_011; + +enum Reg { + Command = 0x80, + DeviceId = 0x82, + RevisionId = 0x83, + RedChan = 0xa0, + GreenChan = 0xa2, + BlueChan = 0xa4, + ClearChan = 0xa6, + IRChan = 0xa8, +} + +enum CmdMask { + PON = 0b1 << 0, // Power on + RGB_EN = 0b1 << 1, // RGB, clear, IR controller enable + W_EN = 0b1 << 2, // Wait state enable + // reserved + DARK_EN = 0b1 << 4, // Dark offset cancelation enable + // reserved + RGB_TIME = 0b111 << 8, // Optical integration time + W_TIME = 0b1 << 11, // Wait time + AGAIN = 0b11 << 12, // Analog gain + IRGAIN = 0b11 << 14, // IR gain +} + +function setMasked( + bus: i2c.I2C, + address: number, + reg: number, + quantity: number, + mask: number, + value: number, +) { + let data = bus.writeRead(address, reg, quantity); + for (let i = 0; i < quantity; i++) { + let maskByte = (mask >> (8 * i)) & 0xff; + let valueByte = (value >> (8 * i)) & 0xff; + data[i] = (data[i] & ~maskByte) | (valueByte & maskByte); + } + + bus.writeTo(address, [reg, ...data]); +} + +export type Calibration = { + mins: number[]; + maxs: number[]; +}; + +export class ZSCS2016C { + readonly bus: i2c.I2C; + private cal: Calibration = { + mins: Array(5).fill(0), + maxs: Array(5).fill(1), + }; + private addr: number; + + /** + * Creates a new instance of the ZSCS2016C color sensor. + * @param bus The I2C bus used for communication + * @param addrBit If true, uses address 0x43, otherwise 0x42 + */ + constructor(bus: i2c.I2C, addrBit: boolean) { + this.bus = bus; + this.addr = addrBit ? ADDRESS_HIGH : ADDRESS_LOW; + this.init(); + } + + /** + * Resets the sensor to its default state. + */ + reset() { + setMasked(this.bus, this.addr, Reg.Command, 2, 0xffff, 0); + } + + /** + * Initializes the sensor + */ + private init() { + setMasked( + this.bus, + this.addr, + Reg.Command, + 2, + 0xffff, + CmdMask.PON | CmdMask.RGB_EN, + ); + } + + /** + * Reads value of the red channel and applies calibration. + * @returns Scaled value of the red channel (0-1) + */ + readRed(): number { + let value = this.readRawRed(); + return scale(value, this.cal.mins[0], this.cal.maxs[0]); + } + + /** + * Reads value of the green channel and applies calibration. + * @returns Scaled value of the green channel (0-1) + */ + readGreen(): number { + let value = this.readRawGreen(); + return scale(value, this.cal.mins[1], this.cal.maxs[1]); + } + + /** + * Reads value of the blue channel and applies calibration. + * @returns Scaled value of the blue channel (0-1) + */ + readBlue(): number { + let value = this.readRawBlue(); + return scale(value, this.cal.mins[2], this.cal.maxs[2]); + } + + /** + * Reads value of the IR channel and applies calibration. + * @returns Scaled value of the IR channel (0-1) + */ + readIR(): number { + let value = this.readRawIR(); + return scale(value, this.cal.mins[3], this.cal.maxs[3]); + } + + /** + * Reads value of the clear channel (unfiltered color) and applies calibration. + * @returns Scaled value of the clear channel (0-1) + */ + readClear(): number { + let value = this.readRawClear(); + return scale(value, this.cal.mins[4], this.cal.maxs[4]); + } + + /** + * Reads values of all RGB channels and applies calibration. + * @note This method is more efficient and accurate than reading all channels individually. + * @returns uint8 Rgb color (format 0xRRGGBB) + */ + readRgb(): Rgb { + let value = this.readRawRgb(); + return rgb( + map(value[0], this.cal.mins[0], this.cal.maxs[0], 0, 255), + map(value[1], this.cal.mins[1], this.cal.maxs[1], 0, 255), + map(value[2], this.cal.mins[2], this.cal.maxs[2], 0, 255), + ); + } + + /** + * Reads raw value of the red channel. + * @returns Raw value of the red channel + */ + readRawRed(): number { + return readInt16(this.bus, this.addr, Reg.RedChan); + } + + /** + * Reads raw value of the green channel. + * @returns Raw value of the green channel + */ + readRawGreen(): number { + return readInt16(this.bus, this.addr, Reg.GreenChan); + } + + /** + * Reads raw value of the blue channel. + * @returns Raw value of the blue channel + */ + readRawBlue(): number { + return readInt16(this.bus, this.addr, Reg.BlueChan); + } + + /** + * Reads raw value of the IR channel. + * @returns Raw value of the IR channel + */ + readRawIR(): number { + return readInt16(this.bus, this.addr, Reg.IRChan); + } + + /** + * Reads raw value of the clear channel (unfiltered color). + * @returns Raw value of the clear channel + */ + readRawClear(): number { + return readInt16(this.bus, this.addr, Reg.ClearChan); + } + + /** + * Reads raw values of all RGB channels. + * @note This method is more efficient and accurate than reading all channels individually. + * @returns Array of raw RGB values [R, G, B] + */ + readRawRgb(): [number, number, number] { + let value = this.bus.writeRead(this.addr, Reg.RedChan, 6); + return [ + value[0] | (value[1] << 8), + value[2] | (value[3] << 8), + value[4] | (value[5] << 8), + ]; + } + + /** + * Runs a calibration procedure for the sensor. During the calibration, the sensor + * should be placed on a white and a black surface. The resulting calibration + * depends on the distance from the surface and lighting conditions. + */ + async runCalibration(): Promise { + let mins = Array(5).fill(Infinity); + let maxs = Array(5).fill(0); + let funs = [ + "readRawRed", + "readRawGreen", + "readRawBlue", + "readRawIR", + "readRawClear", + ] as const; + + console.log("Place on white..."); + await sleep(3000); + console.log("measuring..."); + for (let i = 0; i < 30; i++) { + for (let i = 0; i < funs.length; i++) { + let value = this[funs[i]](); + maxs[i] = Math.max(maxs[i], value); + } + await sleep(10); + } + + console.log("Place on black..."); + await sleep(3000); + console.log("measuring..."); + for (let i = 0; i < 30; i++) { + for (let i = 0; i < funs.length; i++) { + let value = this[funs[i]](); + mins[i] = Math.min(mins[i], value); + } + await sleep(10); + } + console.log("Calibration done"); + + this.cal = { + mins: mins, + maxs: maxs, + }; + } + + /** + * Returns the current calibration data. + * @throws Error if calibration is not set + * @returns The current calibration data + */ + getCalibration(): Calibration { + if (!this.cal) { + throw new Error("Calibration not set"); + } + return this.cal; + } + + /** + * Sets the calibration data. + * @param cal The calibration data to set + */ + setCalibration(cal: Calibration) { + this.cal = cal; + } + + /** + * Saves the current calibration data to persistent storage. + * @param id Identifier for the calibration data + */ + saveCalibration(id: string) { + const kv = kvOpen("ZSCS2016C_calib"); + kv.set(id, JSON.stringify(this.cal)); + kv.commit(); + } + + /** + * Loads calibration data from persistent storage. + * @param id Identifier for the calibration data + */ + loadCalibration(id: string) { + const kv = kvOpen("ZSCS2016C_calib"); + const data = kv.getString(id); + if (!data) { + throw new Error(`Calibration with id ${id} not found`); + } + this.cal = JSON.parse(data); + } +} diff --git a/zscs2016c/tsconfig.json b/zscs2016c/tsconfig.json new file mode 100644 index 0000000..ab82ab7 --- /dev/null +++ b/zscs2016c/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true, + "types": ["jaculus"] + } +} From c0aae0587a4da6aede067a7748b639722b49d1d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1chym=20Zadra=C5=BEil?= Date: Thu, 9 Jul 2026 02:51:46 +0200 Subject: [PATCH 2/2] Add RbHub75 library --- rbhub75/.gitignore | 2 + rbhub75/package.json | 28 ++++++++++ rbhub75/src/index.ts | 125 ++++++++++++++++++++++++++++++++++++++++++ rbhub75/tsconfig.json | 15 +++++ 4 files changed, 170 insertions(+) create mode 100644 rbhub75/.gitignore create mode 100644 rbhub75/package.json create mode 100644 rbhub75/src/index.ts create mode 100644 rbhub75/tsconfig.json diff --git a/rbhub75/.gitignore b/rbhub75/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/rbhub75/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/rbhub75/package.json b/rbhub75/package.json new file mode 100644 index 0000000..5971a94 --- /dev/null +++ b/rbhub75/package.json @@ -0,0 +1,28 @@ +{ + "name": "rbhub75", + "version": "0.1.0", + "description": "Hub75 matrix control", + "dependencies": { + "@types/jaculus": "0.1.0" + }, + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "blocks/**" + ], + "type": "module", + "main": "dist/js/index.js", + "scripts": { + "install": "jac lib-install", + "build-no-check": "jac lib-build --no-check", + "build": "jac lib-build", + "clean": "rm -rf dist" + }, + "types": "dist/types/index.d.ts", + "jaculus": { + "packageFormat": 1, + "blocks": "blocks" + }, + "author": "Jáchym Zadražil", + "license": "MIT" +} \ No newline at end of file diff --git a/rbhub75/src/index.ts b/rbhub75/src/index.ts new file mode 100644 index 0000000..d9d79f5 --- /dev/null +++ b/rbhub75/src/index.ts @@ -0,0 +1,125 @@ +import { SPI2 } from "spi"; + +interface SPIConfig { + pin_ck: number, + pin_cs: number, + pin_d0: number, + pin_d1: number, + pin_d2: number, + pin_d3: number, + baud: number, +} + +export interface DisplayConfig { + width?: number, + height?: number, + buffer?: ArrayBuffer, + spi?: SPIConfig +} + +// TODO: implement other formats? (RGB_888 is currently hard-coded) +/** + * The `Display` class holds a framebuffer and facilitates + * communication with the Hub75 controller. + */ +export class Display { + private _width: number; + private _height: number; + private frame: ArrayBuffer; + private view: Uint8Array; + private sync: ArrayBuffer; + private modeset: ArrayBuffer; + private spi_pin_cs: number; + + static DEFAULT_CONFIG: Required = { + width: 64, + height: 64, + buffer: new ArrayBuffer(64 * 64 * 3), + spi: { + pin_ck: 3, + pin_cs: 1, + pin_d0: 38, + pin_d1: 39, + pin_d2: 41, + pin_d3: 45, + baud: 2_000_000, + } + }; + + constructor(config?: DisplayConfig) { + const resolved = { ...Display.DEFAULT_CONFIG, ...config }; + if (resolved.buffer.byteLength != resolved.width * resolved.height * 3) + throw new TypeError(); + + this._width = resolved.width; + this._height = resolved.height; + this.frame = resolved.buffer; + this.view = new Uint8Array(this.frame); + this.spi_pin_cs = resolved.spi.pin_cs; + + this.setupSPI(resolved.spi); + this.sync = this.createSyncBuffer(); + this.modeset = this.createModesetBuffer(); + } + + draw() { + SPI2.transfer(this.sync, this.spi_pin_cs, 0, true); + SPI2.transfer(this.modeset, this.spi_pin_cs, 0, true); + SPI2.transfer(this.frame, this.spi_pin_cs, 0, true); + } + + /** + * Modifies the color of a single pixel at (x, y). + * This action only affects the framebuffer. + * To submit the changes, use `Display.draw`. + */ + setPixel(x: number, y: number, color: Rgb) { + const i = (y + this.height * x); + this.setPixelByIndex(i, color); + } + + fill(color: Rgb) { + for (let i = 0; i < this.view.length / 3; i++) + this.setPixelByIndex(i, color); + } + + get width(): number { return this._width; } + get height(): number { return this._height; } + + private setupSPI(spi: SPIConfig) { + SPI2.setup({ + sck: spi.pin_ck, + data0: spi.pin_d0, + data1: spi.pin_d1, + data2: spi.pin_d2, + data3: spi.pin_d3, + baud: spi.baud, + mode: 0, + order: "lsb" + }); + } + + private setPixelByIndex(i: number, color: Rgb) { + this.view[3 * i + 0] = (color & 0xff0000) >> 16; + this.view[3 * i + 1] = (color & 0x00ff00) >> 8; + this.view[3 * i + 2] = (color & 0x0000ff) >> 0; + } + + private createSyncBuffer(): ArrayBuffer { + return new Uint16Array([ + 0xac92, 0x3bca, 0x41bf, 0x393d, + 0xa74a, 0xae01, 0x155d, 0xfb70, + 0xf681, 0x2f6d, 0x4931, 0x0fa3, + 0x77bf, 0xd756, 0x26f9, 0x4eb6, + ]).buffer; + } + + private createModesetBuffer(): ArrayBuffer { + if (this.width >= 2**16) + throw new RangeError(); + + return new Uint16Array([ + 0x00fb, 0xff09, this.width, 0x0000 + ]).buffer; + } +} diff --git a/rbhub75/tsconfig.json b/rbhub75/tsconfig.json new file mode 100644 index 0000000..ab82ab7 --- /dev/null +++ b/rbhub75/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "lib": ["es2023"], + "moduleResolution": "nodenext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/js", + "rootDir": "src", + "strict": true, + "types": ["jaculus"] + } +}