├── main.ts
├── microbit.ts
├── fwdedu
├── .prettierrc
├── main.ts
├── mkc.json
├── .github
│ └── workflows
│ │ └── makecode.yml
├── tsconfig.json
├── pxt.json
├── jacdac.ts
└── jacbot.ts
├── botsim
├── src
│ ├── react-app-env.d.ts
│ ├── maps
│ │ ├── index.ts
│ │ ├── specs.ts
│ │ └── testMap.ts
│ ├── App.css
│ ├── constants.ts
│ ├── index.tsx
│ ├── App.tsx
│ ├── index.css
│ ├── bots
│ │ ├── index.ts
│ │ ├── yahboomtinybit.ts
│ │ ├── dfrobotmaqueen.ts
│ │ ├── dfrobotmaqueenplusv2.ts
│ │ ├── elecfreakscutebot.ts
│ │ ├── testBot.ts
│ │ ├── elecfreakscutebotpro.ts
│ │ └── specs.ts
│ ├── util.ts
│ ├── types
│ │ ├── aabb.ts
│ │ └── line.ts
│ ├── sim
│ │ ├── bot
│ │ │ ├── ballast.ts
│ │ │ ├── led.ts
│ │ │ ├── chassis.ts
│ │ │ ├── wheel.ts
│ │ │ ├── lineSensor.ts
│ │ │ └── index.ts
│ │ └── entity.ts
│ ├── ui
│ │ └── SimContainer.tsx
│ └── external
│ │ └── protocol.ts
├── .env
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── placeholder.png
│ ├── bots
│ │ ├── fwdeducakit
│ │ │ └── chassis.png
│ │ ├── tslmotionkit2
│ │ │ └── chassis.png
│ │ ├── dfrobotmaqueen
│ │ │ └── chassis.png
│ │ ├── knotechcallibot
│ │ │ └── chassis.png
│ │ ├── elecfreakscutebot
│ │ │ └── chassis.png
│ │ ├── dfrobotmaqueenplusv2
│ │ │ └── chassis.png
│ │ └── elecfreakscutebotpro
│ │ │ └── chassis.png
│ ├── manifest.json
│ └── index.html
├── .prettierrc
├── tsconfig.paths.json
├── .gitignore
├── tsconfig.json
├── package.json
├── README.md
├── config-overrides.js
└── scripts
│ └── img2hull.js
├── icon.png
├── Gemfile
├── mkc.json
├── .prettierrc
├── assets
├── docs
│ ├── sim.gif
│ └── sim.mp4
└── images
│ ├── fwdedu.jpg
│ ├── robots.jpg
│ ├── cutebot.jpeg
│ ├── maqueen.jpeg
│ ├── minilfr.png
│ ├── nanobit.webp
│ ├── tabbybot.png
│ ├── tinybit.jpeg
│ ├── cutebotpro.jpeg
│ ├── inksmithk8.webp
│ ├── motionkitv2.png
│ ├── robotbit.webp
│ ├── robots-sim.jpg
│ └── dfrobotmaqueenplusv2.jpg
├── mkc-calliopemini.json
├── package.json
├── mkc-inksmithk8.json
├── remotes
├── maininksmithk8.ts
├── maindfrobotmaqueen.ts
├── maintslmotionkit2.ts
├── mainyahboomtinybit.ts
├── mainkittenbotminilfr.ts
├── mainknotechcalibot.ts
├── maindfrobotmaqueenplusv2.ts
├── mainkitronikmotordriverrccar.ts
├── mainkeystudiominismartrobot.ts
├── mainelecfreakscutebot.ts
├── mainkittenbotrobotbit.ts
├── mainkittenbottabbybot.ts
├── mainelecfreakscutebotpro.ts
└── mainkittenbotnanobit.ts
├── mkc-dfrobotmaqueen.json
├── mkc-tslmotionkit2.json
├── mkc-yahboomtinybit.json
├── mkc-elecfreakscutebot.json
├── mkc-kittenbotminilfr.json
├── mkc-kittenbotnanobit.json
├── mkc-kittenbotrobotbit.json
├── mkc-kittenbottabbybot.json
├── mkc-knotechcallibot.json
├── mkc-dfrobotmaqueenplusv2.json
├── mkc-elecfreakscutebotpro.json
├── mkc-keystudiominismartrobot.json
├── mkc-kitronikmotordriverrccar.json
├── _config.yml
├── .vscode
├── extensions.json
├── launch.json
└── settings.json
├── .gitignore
├── protocol
├── README.md
├── pxt.json
└── protocol.ts
├── constants.ts
├── tsconfig.json
├── i2c.ts
├── SUPPORT.md
├── test.ts
├── .github
└── workflows
│ ├── makecode.yml
│ ├── makecode-release.yml
│ └── botsim.yml
├── drivers
├── rccar.ts
├── ws2812bleds.ts
├── drivers.ts
├── sim.ts
├── hbridgemotor.ts
├── servoarm.ts
├── sr04sonar.ts
├── kalmanfilter1d.ts
└── pinlinedetectors.ts
├── CODE_OF_CONDUCT.md
├── storage.ts
├── messages.ts
├── storage.cpp
├── .devcontainer
└── devcontainer.json
├── tutorials
├── light-and-sound.md
├── line-follower.md
└── getting-started.md
├── robots
├── sim.ts
├── dfrobotmaqueen.ts
├── dfrobotmaqueenplusv2.ts
├── kitronikmotordriverrccar.ts
├── elecfreakscutebot.ts
├── inksmithk8.ts
├── tslmotionkit2.ts
├── kittenbotnanobit.ts
├── kittenbottabbybot.ts
├── robot.ts
├── knotechcallibot.ts
├── keystudiominismartrobot.ts
├── kittenbotrobotbit.ts
├── yahboomtinybit.ts
├── elecfreakscutebotpro.ts
└── kittenbotminilfr.ts
├── LICENSE
├── _locales
└── de
│ └── microbit-robot-strings.json
├── calibration.ts
├── sim.ts
├── pxt.json
├── tester.ts
├── mkhex.sh
├── SECURITY.md
└── radio.ts
/main.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/microbit.ts:
--------------------------------------------------------------------------------
1 | music.setVolume(156)
2 |
--------------------------------------------------------------------------------
/fwdedu/.prettierrc:
--------------------------------------------------------------------------------
1 | {"arrowParens":"avoid","semi":false,"tabWidth":4}
--------------------------------------------------------------------------------
/botsim/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/icon.png
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | gem 'github-pages', group: :jekyll_plugins
--------------------------------------------------------------------------------
/mkc.json:
--------------------------------------------------------------------------------
1 | {
2 | "targetWebsite": "https://makecode.microbit.org/beta"
3 | }
4 |
--------------------------------------------------------------------------------
/botsim/.env:
--------------------------------------------------------------------------------
1 | DISABLE_ESLINT_PLUGIN=true
2 | GENERATE_SOURCEMAP=false
3 | BROWSER=none
4 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | { "arrowParens": "avoid", "semi": false, "trailingComma": "es5", "tabWidth": 4 }
2 |
--------------------------------------------------------------------------------
/assets/docs/sim.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/assets/docs/sim.gif
--------------------------------------------------------------------------------
/assets/docs/sim.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/assets/docs/sim.mp4
--------------------------------------------------------------------------------
/mkc-calliopemini.json:
--------------------------------------------------------------------------------
1 | {
2 | "targetWebsite": "https://makecode.calliope.cc/v6.0.21"
3 | }
4 |
--------------------------------------------------------------------------------
/botsim/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/assets/images/fwdedu.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/assets/images/fwdedu.jpg
--------------------------------------------------------------------------------
/assets/images/robots.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/assets/images/robots.jpg
--------------------------------------------------------------------------------
/assets/images/cutebot.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/assets/images/cutebot.jpeg
--------------------------------------------------------------------------------
/assets/images/maqueen.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/assets/images/maqueen.jpeg
--------------------------------------------------------------------------------
/assets/images/minilfr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/assets/images/minilfr.png
--------------------------------------------------------------------------------
/assets/images/nanobit.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/assets/images/nanobit.webp
--------------------------------------------------------------------------------
/assets/images/tabbybot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/assets/images/tabbybot.png
--------------------------------------------------------------------------------
/assets/images/tinybit.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/assets/images/tinybit.jpeg
--------------------------------------------------------------------------------
/botsim/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/botsim/public/favicon.ico
--------------------------------------------------------------------------------
/assets/images/cutebotpro.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/assets/images/cutebotpro.jpeg
--------------------------------------------------------------------------------
/assets/images/inksmithk8.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/assets/images/inksmithk8.webp
--------------------------------------------------------------------------------
/assets/images/motionkitv2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/assets/images/motionkitv2.png
--------------------------------------------------------------------------------
/assets/images/robotbit.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/assets/images/robotbit.webp
--------------------------------------------------------------------------------
/assets/images/robots-sim.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/assets/images/robots-sim.jpg
--------------------------------------------------------------------------------
/botsim/public/placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/botsim/public/placeholder.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "microbit-robot",
3 | "dependencies": {
4 | "makecode": "^1.2.0"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/fwdedu/main.ts:
--------------------------------------------------------------------------------
1 | robot.forwardEducationJacbot.start()
2 | robot.startCompactRadio()
3 | robot.startCalibrationButtons()
4 |
--------------------------------------------------------------------------------
/mkc-inksmithk8.json:
--------------------------------------------------------------------------------
1 | {
2 | "overrides": {
3 | "testFiles": ["remotes/maininksmithk8.ts"]
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/remotes/maininksmithk8.ts:
--------------------------------------------------------------------------------
1 | robot.inksmithK8.start()
2 | robot.startCompactRadio()
3 | robot.startCalibrationButtons()
4 |
--------------------------------------------------------------------------------
/mkc-dfrobotmaqueen.json:
--------------------------------------------------------------------------------
1 | {
2 | "overrides": {
3 | "testFiles": ["remotes/maindfrobotmaqueen.ts"]
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/mkc-tslmotionkit2.json:
--------------------------------------------------------------------------------
1 | {
2 | "overrides": {
3 | "testFiles": ["remotes/maintslmotionkit2.ts"]
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/mkc-yahboomtinybit.json:
--------------------------------------------------------------------------------
1 | {
2 | "overrides": {
3 | "testFiles": ["remotes/mainyahboomtinybit.ts"]
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/remotes/maindfrobotmaqueen.ts:
--------------------------------------------------------------------------------
1 | robot.dfRobotMaqueen.start()
2 | robot.startCompactRadio()
3 | robot.startCalibrationButtons()
4 |
--------------------------------------------------------------------------------
/remotes/maintslmotionkit2.ts:
--------------------------------------------------------------------------------
1 | robot.tslmotionkit2.start()
2 | robot.startCompactRadio()
3 | robot.startCalibrationButtons()
4 |
--------------------------------------------------------------------------------
/remotes/mainyahboomtinybit.ts:
--------------------------------------------------------------------------------
1 | robot.yahboomTinyBit.start()
2 | robot.startCompactRadio()
3 | robot.startCalibrationButtons()
4 |
--------------------------------------------------------------------------------
/mkc-elecfreakscutebot.json:
--------------------------------------------------------------------------------
1 | {
2 | "overrides": {
3 | "testFiles": ["remotes/mainelecfreakscutebot.ts"]
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/mkc-kittenbotminilfr.json:
--------------------------------------------------------------------------------
1 | {
2 | "overrides": {
3 | "testFiles": ["remotes/mainkittenbotminilfr.ts"]
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/mkc-kittenbotnanobit.json:
--------------------------------------------------------------------------------
1 | {
2 | "overrides": {
3 | "testFiles": ["remotes/mainkittenbotnanobit.ts"]
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/mkc-kittenbotrobotbit.json:
--------------------------------------------------------------------------------
1 | {
2 | "overrides": {
3 | "testFiles": ["remotes/mainkittenbotrobotbit.ts"]
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/mkc-kittenbottabbybot.json:
--------------------------------------------------------------------------------
1 | {
2 | "overrides": {
3 | "testFiles": ["remotes/mainkittenbottabbybot.ts"]
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/mkc-knotechcallibot.json:
--------------------------------------------------------------------------------
1 | {
2 | "overrides": {
3 | "testFiles": ["remotes/mainknotechcallibot.ts"]
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/remotes/mainkittenbotminilfr.ts:
--------------------------------------------------------------------------------
1 | robot.kittenbotMiniLFR.start()
2 | robot.startCompactRadio()
3 | robot.startCalibrationButtons()
4 |
--------------------------------------------------------------------------------
/remotes/mainknotechcalibot.ts:
--------------------------------------------------------------------------------
1 | robot.knotechcallibot.start()
2 | robot.startCompactRadio()
3 | robot.startCalibrationButtons()
4 |
--------------------------------------------------------------------------------
/assets/images/dfrobotmaqueenplusv2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/assets/images/dfrobotmaqueenplusv2.jpg
--------------------------------------------------------------------------------
/botsim/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 4,
4 | "semi": false,
5 | "singleQuote": false
6 | }
7 |
--------------------------------------------------------------------------------
/botsim/src/maps/index.ts:
--------------------------------------------------------------------------------
1 | import * as TestMap from "./testMap"
2 |
3 | export const MAPS = {
4 | [TestMap.name]: TestMap.create,
5 | }
6 |
--------------------------------------------------------------------------------
/mkc-dfrobotmaqueenplusv2.json:
--------------------------------------------------------------------------------
1 | {
2 | "overrides": {
3 | "testFiles": ["remotes/maindfrobotmaqueenplusv2.ts"]
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/mkc-elecfreakscutebotpro.json:
--------------------------------------------------------------------------------
1 | {
2 | "overrides": {
3 | "testFiles": ["remotes/mainelecfreakscutebotpro.ts"]
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/remotes/maindfrobotmaqueenplusv2.ts:
--------------------------------------------------------------------------------
1 | robot.dfRobotMaqueenPlusV2.start()
2 | robot.startCompactRadio()
3 | robot.startCalibrationButtons()
4 |
--------------------------------------------------------------------------------
/botsim/public/bots/fwdeducakit/chassis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/botsim/public/bots/fwdeducakit/chassis.png
--------------------------------------------------------------------------------
/botsim/public/bots/tslmotionkit2/chassis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/botsim/public/bots/tslmotionkit2/chassis.png
--------------------------------------------------------------------------------
/mkc-keystudiominismartrobot.json:
--------------------------------------------------------------------------------
1 | {
2 | "overrides": {
3 | "testFiles": ["remotes/mainkeystudiominismartrobot.ts"]
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/mkc-kitronikmotordriverrccar.json:
--------------------------------------------------------------------------------
1 | {
2 | "overrides": {
3 | "testFiles": ["remotes/mainkitronikmotordriverrccar.ts"]
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/remotes/mainkitronikmotordriverrccar.ts:
--------------------------------------------------------------------------------
1 | robot.kitronikMotorDriverRCCar.start()
2 | robot.startCompactRadio()
3 | robot.startCalibrationButtons()
4 |
--------------------------------------------------------------------------------
/botsim/public/bots/dfrobotmaqueen/chassis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/botsim/public/bots/dfrobotmaqueen/chassis.png
--------------------------------------------------------------------------------
/botsim/public/bots/knotechcallibot/chassis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/botsim/public/bots/knotechcallibot/chassis.png
--------------------------------------------------------------------------------
/fwdedu/mkc.json:
--------------------------------------------------------------------------------
1 | {
2 | "targetWebsite": "https://makecode.microbit.org/beta",
3 | "links": {
4 | "microbit-robot": "../"
5 | }
6 | }
--------------------------------------------------------------------------------
/botsim/public/bots/elecfreakscutebot/chassis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/botsim/public/bots/elecfreakscutebot/chassis.png
--------------------------------------------------------------------------------
/botsim/public/bots/dfrobotmaqueenplusv2/chassis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/botsim/public/bots/dfrobotmaqueenplusv2/chassis.png
--------------------------------------------------------------------------------
/botsim/public/bots/elecfreakscutebotpro/chassis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/microbit-robot/HEAD/botsim/public/bots/elecfreakscutebotpro/chassis.png
--------------------------------------------------------------------------------
/remotes/mainkeystudiominismartrobot.ts:
--------------------------------------------------------------------------------
1 | robot.keyStudioMiniSmartRobot.start()
2 | robot.startCompactRadio()
3 | //robot.startCalibrationButtons()// out of space
4 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | makecode:
2 | target: microbit
3 | platform: microbit
4 | home_url: https://makecode.microbit.org/
5 | include:
6 | - assets
7 | - README.md
8 |
--------------------------------------------------------------------------------
/botsim/tsconfig.paths.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "paths": {
4 | "microbit-robot/*": ["../*"]
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/remotes/mainelecfreakscutebot.ts:
--------------------------------------------------------------------------------
1 | robot.elecfreaksCuteBot.start()
2 | robot.startCompactRadio()
3 | robot.startCalibrationButtons()
4 | pins.analogSetPitchVolume(156)
5 |
--------------------------------------------------------------------------------
/remotes/mainkittenbotrobotbit.ts:
--------------------------------------------------------------------------------
1 | robot.kittenbotRobotbit.start()
2 | robot.startCompactRadio()
3 | robot.startCalibrationButtons()
4 | pins.analogSetPitchVolume(156)
5 |
--------------------------------------------------------------------------------
/remotes/mainkittenbottabbybot.ts:
--------------------------------------------------------------------------------
1 | robot.kittenbotTabbyBot.start()
2 | robot.startCompactRadio()
3 | robot.startCalibrationButtons()
4 | pins.analogSetPitchVolume(156)
5 |
--------------------------------------------------------------------------------
/remotes/mainelecfreakscutebotpro.ts:
--------------------------------------------------------------------------------
1 | robot.elecfreaksCuteBotPro.start()
2 | robot.startCompactRadio()
3 | robot.startCalibrationButtons()
4 | pins.analogSetPitchVolume(156)
5 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "ms-edu.pxt-vscode-web",
4 | "esbenp.prettier-vscode",
5 | "genaiscript.genaiscript-vscode"
6 | ]
7 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | built
2 | node_modules
3 | yotta_modules
4 | yotta_targets
5 | pxt_modules
6 | .pxt
7 | _site
8 | *.db
9 | *.tgz
10 | .header.json
11 | .simstate.json
12 | microcode
13 | .DS_Store
14 |
--------------------------------------------------------------------------------
/protocol/README.md:
--------------------------------------------------------------------------------
1 | # Micro:bit Robot Protocol
2 |
3 | Remote control protocol for microbit robots.
4 |
5 | This project only contains the constants defined for the protocol. It should not be imported along with the main library.
--------------------------------------------------------------------------------
/remotes/mainkittenbotnanobit.ts:
--------------------------------------------------------------------------------
1 | robot.kittenbotNanobit.start()
2 | robot.startCompactRadio()
3 | // there is no screen on the nanobit, set the radio group to 1
4 | robot.RobotDriver.instance().setRadioGroup(1)
5 | robot.startCalibrationButtons()
6 | pins.analogSetPitchVolume(168)
7 |
--------------------------------------------------------------------------------
/constants.ts:
--------------------------------------------------------------------------------
1 | namespace robot.configuration {
2 | export const MAX_DEFAULT_GROUPS = 10
3 | export const MAX_GROUPS = 25
4 | export const SCROLL_SPEED = 50
5 | export const ROBOT_EVENT_ID = 7325
6 | export const MAX_SONAR_DISTANCE = 40
7 | export const LINE_RESEND_RESET_COUNT = 50
8 | }
--------------------------------------------------------------------------------
/fwdedu/.github/workflows/makecode.yml:
--------------------------------------------------------------------------------
1 | name: MakeCode Build
2 | on:
3 | push:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 | with:
12 | submodules: recursive
13 | - run: npx makecode
14 |
--------------------------------------------------------------------------------
/botsim/src/App.css:
--------------------------------------------------------------------------------
1 | .app {
2 | width: 100%;
3 | padding: 0;
4 | margin: 0;
5 | }
6 |
7 | .sim-container {
8 | border-top-left-radius: 6% 7%;
9 | border-top-right-radius: 6% 7%;
10 | border-bottom-left-radius: 7% 6%;
11 | border-bottom-right-radius: 7% 6%;
12 | overflow: hidden;
13 | }
14 |
--------------------------------------------------------------------------------
/botsim/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const MAP_ASPECT_RATIO = 1.22
2 |
3 | export const RENDER_SCALE = 12
4 |
5 | export const MICROBIT_COLORS = {
6 | YELLOW: "rgb(255, 212, 58)", // FFD43A
7 | GREEN: "rgb(58, 255, 179)", // 3AFFB3
8 | RED: "rgb(255, 58, 84)", // FF3A54
9 | BLUE: "rgb(58, 220, 255)", // 3ADCFF
10 | }
11 |
--------------------------------------------------------------------------------
/botsim/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom/client"
3 | import "./index.css"
4 | import { App } from "./App"
5 |
6 | const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
7 | root.render(
8 |
9 |
10 |
11 | )
12 |
--------------------------------------------------------------------------------
/fwdedu/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES5",
4 | "noImplicitAny": true,
5 | "outDir": "built",
6 | "rootDir": "."
7 | },
8 | "include": [
9 | "**/*.ts"
10 | ],
11 | "exclude": [
12 | "built/**",
13 | "pxt_modules/**/*test.ts"
14 | ]
15 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES5",
4 | "noImplicitAny": true,
5 | "outDir": "built",
6 | "rootDir": "."
7 | },
8 | "include": [
9 | "**/*.ts"
10 | ],
11 | "exclude": [
12 | "built/**",
13 | "pxt_modules/**/*test.ts",
14 | "botsim/**"
15 | ]
16 | }
--------------------------------------------------------------------------------
/botsim/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react"
2 | import "./App.css"
3 | import { SimContainer } from "./ui/SimContainer"
4 | import * as MakeCodeService from "./services/makecodeService"
5 |
6 | export const App: React.FC = () => {
7 | useEffect(() => {
8 | MakeCodeService.init()
9 | }, [])
10 | return
{}
11 | }
12 |
--------------------------------------------------------------------------------
/botsim/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Micro:bit Robot Simulator",
3 | "name": "Micro:bit Robot Simulator",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "128x128",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/i2c.ts:
--------------------------------------------------------------------------------
1 | namespace robot.robots {
2 | export function i2cReadRegU8(addr: number, reg: number) {
3 | const req = pins.createBuffer(1)
4 | req[0] = reg
5 | pins.i2cWriteBuffer(addr, req)
6 | return i2cReadU8(addr)
7 | }
8 |
9 | export function i2cReadU8(addr: number) {
10 | const resp = pins.i2cReadBuffer(addr, 1)
11 | return resp[0]
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/SUPPORT.md:
--------------------------------------------------------------------------------
1 | # Support
2 |
3 | ## How to file issues and get help
4 |
5 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing
6 | issues before filing new issues to avoid duplicates. For new issues, file your bug or
7 | feature request as a new Issue.
8 |
9 | ## Microsoft Support Policy
10 |
11 | Support for this microbit-robot is limited to the resources listed above.
12 |
--------------------------------------------------------------------------------
/test.ts:
--------------------------------------------------------------------------------
1 | robot.elecfreaksCuteBot.start()
2 | //robot.yahboomTinyBit.start(
3 | //robot.elecfreaksCuteBotPro.start()
4 | //robot.keyStudioMiniSmartRobot.start()
5 | //robot.setMotorDrift(6)
6 | //robot.dfRobotMaqueen.start()
7 | //robot.dfRobotMaqueenPlusV2.start()
8 | //robot.kittenbotMiniLFR.start()
9 | //robot.inksmithK8.start()
10 | //robot.startCompactRadio()
11 | //robot.startCalibrationButtons()
12 | robot.test.startTestMode()
13 |
--------------------------------------------------------------------------------
/botsim/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | public/assets
8 |
9 | # testing
10 | /coverage
11 |
12 | # production
13 | /build
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/.github/workflows/makecode.yml:
--------------------------------------------------------------------------------
1 | name: MakeCode Build
2 | on:
3 | push:
4 | paths-ignore:
5 | - "botsim/**"
6 | workflow_dispatch:
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | actions: read
13 | contents: write
14 | steps:
15 | - uses: actions/checkout@v4
16 | with:
17 | submodules: recursive
18 | - run: npm install -g makecode
19 | - run: sh mkhex.sh
20 |
--------------------------------------------------------------------------------
/drivers/rccar.ts:
--------------------------------------------------------------------------------
1 | namespace robot.drivers {
2 | /**
3 | * Converts tank speed commands to [speed,direction] throggle
4 | * @param left
5 | * @param right
6 | * @returns
7 | */
8 | export function tankToRCMotors(left: number, right: number): number[] {
9 | const speed = clampSpeed((left + right) / 2)
10 | const dir = speed === 0 ? 0 : clampSpeed(((left - right) / speed) * 100)
11 | return [speed, dir]
12 | }
13 | }
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Microsoft Open Source Code of Conduct
2 |
3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
4 |
5 | Resources:
6 |
7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
10 |
--------------------------------------------------------------------------------
/storage.ts:
--------------------------------------------------------------------------------
1 | namespace robot.configuration {
2 | //% shim=robot::writeCalibration
3 | export function writeCalibration(
4 | radioGroup: number,
5 | drift: number
6 | ): void {
7 | console.log("radio group:" + radioGroup)
8 | console.log("run drift: " + drift)
9 | }
10 |
11 | //% shim=robot::readCalibration
12 | export function readCalibration(field: number): number {
13 | // read run drift
14 | return 0
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/protocol/pxt.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "microbit-robot-protocol",
3 | "version": "2.7.4",
4 | "dependencies": {
5 | "core": "*",
6 | "radio": "*"
7 | },
8 | "files": [
9 | "README.md",
10 | "protocol.ts"
11 | ],
12 | "targetVersions": {
13 | "target": "6.0.19",
14 | "targetId": "microbit"
15 | },
16 | "supportedTargets": [
17 | "microbit",
18 | "calliopemini"
19 | ],
20 | "preferredEditor": "tsprj"
21 | }
22 |
--------------------------------------------------------------------------------
/botsim/src/maps/specs.ts:
--------------------------------------------------------------------------------
1 | import { EntitySpec } from "../sim/specs"
2 | import { Vec2Like } from "../types/vec2"
3 |
4 | /// Spawn
5 |
6 | export type SpawnSpec = {
7 | pos: Vec2Like
8 | angle: number
9 | }
10 |
11 | /// Map
12 |
13 | export type MapSpec = {
14 | name: string
15 | width: number // cm
16 | aspectRatio: number // width / height
17 | color: string // background color
18 | spawns: SpawnSpec[] // robot spawn locations
19 | entities: EntitySpec[] // obstacles, paths, etc.
20 | }
21 |
--------------------------------------------------------------------------------
/.github/workflows/makecode-release.yml:
--------------------------------------------------------------------------------
1 | name: MakeCode Release
2 | on:
3 | push:
4 | tags:
5 | - v**
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 | permissions:
10 | actions: read
11 | contents: write
12 | steps:
13 | - uses: actions/checkout@v4
14 | with:
15 | submodules: recursive
16 | - run: npm install -g makecode
17 | - run: sh mkhex.sh
18 | - name: upload modified assets
19 | uses: stefanzweifel/git-auto-commit-action@v4
20 | with:
21 | file_pattern: "assets/*"
22 | branch: main
23 |
--------------------------------------------------------------------------------
/botsim/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.paths.json",
3 | "compilerOptions": {
4 | "target": "ES6",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "esModuleInterop": true,
13 | "allowSyntheticDefaultImports": true,
14 | "strict": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "noFallthroughCasesInSwitch": true,
17 | "module": "esnext",
18 | "moduleResolution": "node",
19 | "resolveJsonModule": true,
20 | "isolatedModules": true,
21 | "noEmit": true,
22 | "jsx": "react-jsx"
23 | },
24 | "include": [
25 | "src"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/botsim/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | height: 100%;
3 | width: 100%;
4 | overflow: hidden;
5 | margin: 0;
6 | padding: 0;
7 |
8 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
9 | "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
10 | "Helvetica Neue", sans-serif;
11 | -webkit-font-smoothing: antialiased;
12 | -moz-osx-font-smoothing: grayscale;
13 | background-color: transparent;
14 | }
15 |
16 | #root {
17 | display: flex;
18 | flex-direction: column;
19 | justify-content: center;
20 | height: 100%;
21 | width: 100%;
22 | }
23 |
24 | code {
25 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
26 | monospace;
27 | }
28 |
--------------------------------------------------------------------------------
/drivers/ws2812bleds.ts:
--------------------------------------------------------------------------------
1 | namespace robot.drivers {
2 | export class WS2812bLEDStrip implements LEDStrip {
3 | private ledsBuffer: Buffer
4 |
5 | constructor(
6 | public readonly pin: DigitalPin,
7 | public readonly count: number
8 | ) {}
9 |
10 | start() {
11 | this.ledsBuffer = Buffer.create(this.count * 3)
12 | }
13 |
14 | setColor(red: number, green: number, blue: number) {
15 | const b = this.ledsBuffer
16 | for (let i = 0; i + 2 < b.length; i += 3) {
17 | b[i] = green
18 | b[i + 1] = red
19 | b[i + 2] = blue
20 | }
21 | ws2812b.sendBuffer(this.ledsBuffer, this.pin)
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/botsim/src/bots/index.ts:
--------------------------------------------------------------------------------
1 | import testBot from "./testBot"
2 | import elecfreakscutebotBot from "./elecfreakscutebot"
3 | import elecfreakscutebotProBot from "./elecfreakscutebotpro"
4 | import dfrobotmaqueenBot from "./dfrobotmaqueen"
5 | import dfrobotmaqueenPlusV2Bot from "./dfrobotmaqueenplusv2"
6 | import yahboomtinybitBot from "./yahboomtinybit"
7 |
8 | export const BOTS = {
9 | [elecfreakscutebotBot.productId]: elecfreakscutebotBot,
10 | [elecfreakscutebotProBot.productId]: elecfreakscutebotProBot,
11 | [dfrobotmaqueenBot.productId]: dfrobotmaqueenBot,
12 | [dfrobotmaqueenPlusV2Bot.productId]: dfrobotmaqueenPlusV2Bot,
13 | [yahboomtinybitBot.productId]: yahboomtinybitBot,
14 | [testBot.productId]: testBot,
15 | }
16 |
17 | export const DEFAULT_BOT = testBot.productId
18 |
--------------------------------------------------------------------------------
/drivers/drivers.ts:
--------------------------------------------------------------------------------
1 | namespace robot.drivers {
2 | /**
3 | * A LED strip
4 | */
5 | export interface LEDStrip {
6 | start(): void
7 | setColor(red: number, green: number, blue: number): void
8 | }
9 |
10 | export interface Sonar {
11 | start(): void
12 | distance(maxCmDistance: number): number
13 | }
14 |
15 | export interface LineDetectors {
16 | start(): void
17 | /**
18 | * Updates the state vector with the current line detector state.
19 | * @param state a number of size 5, indexed by LineDetectors, with 0 for white and 1 for black
20 | */
21 | lineState(state: number[]): void
22 | }
23 |
24 | export interface Arm {
25 | start(): void
26 | open(aperture: number): void
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "launch",
10 | "name": "img2hull.js",
11 | "skipFiles": [
12 | "/**"
13 | ],
14 | "program": "${workspaceFolder}/botsim/scripts/img2hull.js",
15 | "args": [
16 | "-i",
17 | "${workspaceFolder}/botsim/public/bots/elecfreakscutebot/chassis.png",
18 | "-s",
19 | "8.6",
20 | ]
21 | }
22 | ]
23 | }
--------------------------------------------------------------------------------
/botsim/src/util.ts:
--------------------------------------------------------------------------------
1 | import { customAlphabet } from "nanoid"
2 | const nanoid = customAlphabet("1234567890abcdefghijklmnopqrstuvwxyz", 10)
3 |
4 | export function nextId(): string {
5 | return nanoid()
6 | }
7 |
8 | export function toRadians(degrees: number) {
9 | return (degrees * Math.PI) / 180
10 | }
11 |
12 | export function toDegrees(radians: number) {
13 | return (radians * 180) / Math.PI
14 | }
15 |
16 | export function randomAngle() {
17 | return Math.random() * 2 * Math.PI
18 | }
19 |
20 | export function randomAngleDeg() {
21 | return Math.random() * 360
22 | }
23 |
24 | export function clamp(value: number, min: number, max: number) {
25 | return Math.max(min, Math.min(max, value))
26 | }
27 |
28 | export function pickRandom(arr: T[]): T {
29 | return arr[Math.floor(Math.random() * arr.length)]
30 | }
31 |
--------------------------------------------------------------------------------
/botsim/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 | Micro:bit Robot Simulator
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnType": true,
3 | "files.autoSave": "afterDelay",
4 | "files.watcherExclude": {
5 | "**/.git/objects/**": true,
6 | "**/built/**": true,
7 | "**/node_modules/**": true,
8 | "**/yotta_modules/**": true,
9 | "**/yotta_targets": true,
10 | "**/pxt_modules/**": true,
11 | "**/.pxt/**": true
12 | },
13 | "files.associations": {
14 | "*.blocks": "html",
15 | "*.jres": "json"
16 | },
17 | "search.exclude": {
18 | "**/built": true,
19 | "**/node_modules": true,
20 | "**/yotta_modules": true,
21 | "**/yotta_targets": true,
22 | "**/pxt_modules": true,
23 | "**/.pxt": true
24 | },
25 | "files.exclude": {
26 | "**/pxt_modules": true,
27 | "**/.pxt": true
28 | }
29 | }
--------------------------------------------------------------------------------
/messages.ts:
--------------------------------------------------------------------------------
1 | namespace robot.messages {
2 | export enum RobotEvents {
3 | None = 0,
4 | Any = ~0,
5 | LineAny = 1 << 0,
6 | LineLeftRight = 1 << 1,
7 | LineLeftMiddleRight = 1 << 2,
8 | LineOuterLeftLeftRightOuterRight = 1 << 3,
9 | ObstacleDistance = 1 << 4,
10 | }
11 |
12 | let events: RobotEvents = RobotEvents.None
13 |
14 | export function raiseEvent(event: RobotEvents, code: robots.RobotCompactCommand) {
15 | if (events & event)
16 | control.raiseEvent(configuration.ROBOT_EVENT_ID, code & 0xffff)
17 | }
18 | export function onEvent(
19 | event: RobotEvents,
20 | code: robots.RobotCompactCommand,
21 | handler: () => void
22 | ) {
23 | events |= event
24 | control.onEvent(configuration.ROBOT_EVENT_ID, code & 0xffff, handler)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/storage.cpp:
--------------------------------------------------------------------------------
1 | #include "pxt.h"
2 |
3 | #define CALIBRATION_KEY "robot"
4 |
5 | namespace robot {
6 | struct Calibration {
7 | int radioGroup;
8 | int drift;
9 | };
10 |
11 | //%
12 | void writeCalibration(int radioGroup, int drift) {
13 | Calibration cal;
14 | cal.radioGroup = radioGroup;
15 | cal.drift = drift;
16 | uBit.storage.put(CALIBRATION_KEY, (uint8_t *)&cal, sizeof(Calibration));
17 | }
18 |
19 | //%
20 | int readCalibration(int field) {
21 | KeyValuePair* kv = uBit.storage.get(CALIBRATION_KEY);
22 | int value = 0;
23 | if (NULL != kv) {
24 | Calibration cal;
25 | memcpy(&cal, kv->value, sizeof(Calibration));
26 | if (field == 0)
27 | value = cal.radioGroup;
28 | else if (field == 1)
29 | value = cal.drift;
30 | }
31 | return value;
32 | }
33 | }
--------------------------------------------------------------------------------
/drivers/sim.ts:
--------------------------------------------------------------------------------
1 | namespace robot.drivers {
2 | /**
3 | * Simulator line detectors; used to marshal simmessages into the robot driver
4 | */
5 | export class SimLineDetectors implements LineDetectors {
6 | current: number[] = [-1, -1, -1, -1, -1]
7 | constructor() {}
8 |
9 | start(): void {}
10 |
11 | lineState(state: number[]): void {
12 | for (let i = 0; i < this.current.length; ++i)
13 | state[i] = this.current[i]
14 | }
15 | }
16 |
17 | /**
18 | * Simulator sonar; used to marshal simmessages into the robot driver
19 | */
20 | export class SimSonar implements Sonar {
21 | current: number
22 | constructor() {
23 | this.current = -1
24 | }
25 | start(): void {}
26 | distance(maxCmDistance: number): number {
27 | return Math.min(this.current, maxCmDistance)
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/fwdedu/pxt.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Forward Education JacBot Robot Car",
3 | "version": "2.7.4",
4 | "files": [
5 | "main.ts",
6 | "jacbot.ts",
7 | "jacdac.ts"
8 | ],
9 | "supportedTargets": [
10 | "microbit"
11 | ],
12 | "dependencies": {
13 | "core": "*",
14 | "radio": "*",
15 | "microphone": "*",
16 | "microbit-robot": "github:microsoft/microbit-robot#v2.5.47",
17 | "jacdac": "github:microsoft/pxt-jacdac#v1.9.23",
18 | "fwd-edu-common": "github:climate-action-kits/pxt-fwd-edu/fwd-common",
19 | "fwd-edu-breakout": "github:climate-action-kits/pxt-fwd-edu/fwd-breakout",
20 | "fwd-edu-led": "github:climate-action-kits/pxt-fwd-edu/fwd-led",
21 | "fwd-edu-line": "github:climate-action-kits/pxt-fwd-edu/fwd-line",
22 | "fwd-edu-sonar": "github:climate-action-kits/pxt-fwd-edu/fwd-sonar"
23 | },
24 | "testDependencies": {}
25 | }
26 |
--------------------------------------------------------------------------------
/botsim/src/types/aabb.ts:
--------------------------------------------------------------------------------
1 | import { Vec2, Vec2Like } from "./vec2"
2 |
3 | export type AABBLike = {
4 | min: Vec2Like
5 | max: Vec2Like
6 | }
7 |
8 | export class AABB {
9 | static like(min: Vec2Like, max: Vec2Like): AABBLike {
10 | return { min, max }
11 | }
12 | static from(verts: Vec2Like[]): AABBLike {
13 | if (verts.length === 0) return AABB.like(Vec2.zero(), Vec2.zero())
14 | const min = { ...verts[0] }
15 | const max = { ...verts[0] }
16 | for (const v of verts) {
17 | min.x = Math.min(min.x, v.x)
18 | min.y = Math.min(min.y, v.y)
19 | max.x = Math.max(max.x, v.x)
20 | max.y = Math.max(max.y, v.y)
21 | }
22 | return { min, max }
23 | }
24 | static width(aabb: AABBLike): number {
25 | return aabb.max.x - aabb.min.x
26 | }
27 | static height(aabb: AABBLike): number {
28 | return aabb.max.y - aabb.min.y
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/fwdedu/jacdac.ts:
--------------------------------------------------------------------------------
1 | namespace robot.drivers {
2 |
3 | export class JacdacLed implements LEDStrip {
4 | start() {
5 | fwdSensors.ledRing.setBrightness(5)
6 | }
7 |
8 | setColor(red: number, green: number, blue: number) {
9 | fwdSensors.ledRing.setAll(red << 16 | green << 8 | blue)
10 | }
11 | }
12 |
13 | export class JacdacSonar implements Sonar {
14 | start() { }
15 |
16 | distance(maxCmDistance: number): number {
17 | return fwdSensors.sonar1.distance() * 100
18 | }
19 | }
20 |
21 | export class JacdacLines implements LineDetectors {
22 | start() { }
23 |
24 | lineState(state: number[]) {
25 | state[RobotLineDetector.Right] = fwdSensors.line3.brightness() ? 0 : 400
26 | state[RobotLineDetector.Middle] = fwdSensors.line2.brightness() ? 0 : 400
27 | state[RobotLineDetector.Left] = fwdSensors.line1.brightness() ? 0 : 400
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2 | // README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
3 | {
4 | "name": "Node.js & TypeScript",
5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6 | "image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye",
7 |
8 | // Features to add to the dev container. More info: https://containers.dev/features.
9 | // "features": {},
10 |
11 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
12 | // "forwardPorts": [],
13 |
14 | // Use 'postCreateCommand' to run commands after the container is created.
15 | "postCreateCommand": "npm install -g makecode"
16 |
17 | // Configure tool-specific properties.
18 | // "customizations": {},
19 |
20 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
21 | // "remoteUser": "root"
22 | }
23 |
--------------------------------------------------------------------------------
/botsim/src/sim/bot/ballast.ts:
--------------------------------------------------------------------------------
1 | import { BotSpec } from "../../bots/specs"
2 | import {
3 | EntityShapeSpec,
4 | defaultBoxShape,
5 | defaultColorBrush,
6 | defaultDynamicPhysics,
7 | } from "../specs"
8 |
9 | export function makeBallastSpec(botSpec: BotSpec): EntityShapeSpec | undefined {
10 | if (!botSpec.ballast) return undefined
11 | return {
12 | ...defaultBoxShape(),
13 | label: "ballast",
14 | offset: botSpec.ballast.pos,
15 | angle: 0,
16 | size: botSpec.ballast.size,
17 | brush: {
18 | ...defaultColorBrush(),
19 | fillColor: "transparent",
20 | borderColor: "black",
21 | borderWidth: 0,
22 | },
23 | physics: {
24 | ...defaultDynamicPhysics(),
25 | sensor: true,
26 | // density = mass / area
27 | density:
28 | botSpec.ballast.mass /
29 | (botSpec.ballast.size.x * botSpec.ballast.size.y),
30 | friction: 0,
31 | restitution: 0,
32 | },
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tutorials/light-and-sound.md:
--------------------------------------------------------------------------------
1 | # Lights and Sounds
2 |
3 | ## Light And Sounds @showdialog
4 |
5 | Controls the headlights and buzzer to make your robot more communicative.
6 |
7 | ## Choose the robot type @showhint
8 |
9 | Drag the `||robot:robot ... start||` at the start of `||basic:on start||` and choose the robot type
10 | in the dropdown. After restarting, you should also see the robot simulator.
11 |
12 | ```blocks
13 | // @highlight
14 | robot.elecfreaksCuteBot.start()
15 | ```
16 |
17 | ## Light
18 |
19 | Drag a `||robot:robot set color||` to set the color of LEDs on the robot.
20 | If the robot does not have an RGB color, the intensity of the color will be used.
21 |
22 | ```blocks
23 | robot.elecfreaksCuteBot.start()
24 | // @highlight
25 | robot.setColor(0xff0000)
26 | ```
27 |
28 | ## Sound
29 |
30 | Drag a `||robot:robot play tone||` to play a tone of the robot.
31 | Most robot have a buzzer or the micro:bit V2 buzzer will be used.
32 |
33 | ```blocks
34 | robot.elecfreaksCuteBot.start()
35 | robot.setColor(0xff0000)
36 | // @highlight
37 | robot.playTone(262, music.beat(BeatFraction.Whole))
38 | ```
39 |
--------------------------------------------------------------------------------
/robots/sim.ts:
--------------------------------------------------------------------------------
1 | namespace robot.robots {
2 | /**
3 | * A robot simulator that prevents using any kind of hardware services
4 | */
5 | export class SimRobot extends Robot {
6 | constructor(p: Robot) {
7 | super(p.productId)
8 |
9 | this.lineDetectors = new drivers.SimLineDetectors()
10 | this.sonar = new drivers.SimSonar()
11 |
12 | // copy parameters to presever characteristices
13 | // TODO: validate this make sense
14 | this.stopThreshold = p.stopThreshold
15 | this.targetSpeedThreshold = p.targetSpeedThreshold
16 | this.speedTransitionAlpha = p.speedTransitionAlpha
17 | this.speedBrakeTransitionAlpha = p.speedBrakeTransitionAlpha
18 | this.targetTurnRatioThreshold = p.targetTurnRatioThreshold
19 | this.turnRatioTransitionAlpha = p.turnRatioTransitionAlpha
20 | this.sonarMinReading = p.sonarMinReading
21 | this.lineLostThreshold = p.lineLostThreshold
22 | this.lineHighThreshold = p.lineHighThreshold
23 | this.commands = p.commands
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/drivers/hbridgemotor.ts:
--------------------------------------------------------------------------------
1 | namespace robot.drivers {
2 | export class AnalogPinHBridgeMotor {
3 | private readonly pin0: DigitalPin
4 | private readonly pin1: DigitalPin
5 | private lastValue: number = undefined
6 | constructor(pin0: DigitalPin, pin1: DigitalPin) {
7 | this.pin0 = pin0
8 | this.pin1 = pin1
9 | }
10 |
11 | run(speed: number): void {
12 | const value = Math.floor(Math.map(Math.abs(speed), 0, 100, 0, 1023))
13 | if (value === this.lastValue) return
14 |
15 | this.lastValue = value
16 | if (value === 0) {
17 | pins.digitalWritePin(this.pin0, 0);
18 | pins.digitalWritePin(this.pin1, 0);
19 | } else if (speed > 0) {
20 | pins.digitalWritePin(this.pin1, 0);
21 | pins.analogWritePin(this.pin0, value);
22 | /*Write the low side digitally, to allow the 3rd PWM to be used if required elsewhere*/
23 | } else {
24 | pins.digitalWritePin(this.pin0, 0);
25 | pins.analogWritePin(this.pin1, value);
26 | }
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/drivers/servoarm.ts:
--------------------------------------------------------------------------------
1 | namespace robot.drivers {
2 | export class ServoArm implements Arm {
3 | pulseUs?: number
4 | constructor(
5 | public minAngle: number,
6 | public maxAngle: number,
7 | public readonly pin: AnalogPin
8 | ) { }
9 | start() {
10 | if (this.pulseUs) pins.servoSetPulse(this.pin, this.pulseUs)
11 | }
12 | open(aperture: number): void {
13 | const angle = Math.round(
14 | Math.map(aperture, 0, 100, this.minAngle, this.maxAngle)
15 | )
16 | pins.servoWritePin(this.pin, angle)
17 | }
18 | }
19 |
20 | // fixed angle mappping to 3D printed arm
21 | // servo degree 0: close, 90: close
22 | export class FixedServoArm implements drivers.Arm {
23 | constructor(
24 | public minAngle: number,
25 | public maxAngle: number,
26 | public readonly pin: AnalogPin) { }
27 | start() { }
28 | open(aperture: number) {
29 | if (aperture > 50) {
30 | pins.servoWritePin(this.pin, this.minAngle)
31 | } else {
32 | pins.servoWritePin(this.pin, this.maxAngle)
33 | }
34 | }
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/.github/workflows/botsim.yml:
--------------------------------------------------------------------------------
1 | name: Botsim build
2 | on:
3 | push:
4 | paths:
5 | - "botsim/**"
6 | - "assets/**"
7 | tags:
8 | - v**
9 |
10 | workflow_dispatch:
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 | strategy:
16 | matrix:
17 | node-version: [18.x]
18 | permissions:
19 | actions: read
20 | contents: write
21 | steps:
22 | - uses: actions/checkout@v4
23 | - name: Use Node.js ${{ matrix.node-version }}
24 | uses: actions/setup-node@v4
25 | with:
26 | node-version: ${{ matrix.node-version }}
27 | - run: npm ci
28 | working-directory: botsim
29 | - run: npm run assets
30 | working-directory: botsim
31 | - run: npm run build
32 | working-directory: botsim
33 | - name: github pages
34 | uses: peaceiris/actions-gh-pages@v3
35 | if: ${{ github.ref == 'refs/heads/main' }}
36 | with:
37 | github_token: ${{ secrets.GITHUB_TOKEN }}
38 | publish_dir: ./botsim/build
39 | force_orphan: true
40 |
--------------------------------------------------------------------------------
/botsim/src/bots/yahboomtinybit.ts:
--------------------------------------------------------------------------------
1 | import { BotSpec, toWheels } from "./specs"
2 |
3 | const spec: BotSpec = {
4 | name: "Yahboom Tiny:bit",
5 | productId: 0x345f8369,
6 | mass: 1,
7 | weight: 179,
8 | silkColor: "#000000",
9 | chassis: {
10 | shape: "circle",
11 | radius: 10 / 2,
12 | },
13 | wheels: toWheels({
14 | separation: 7.2,
15 | diameter: 4.2,
16 | width: 1.7,
17 | y: 1.9,
18 | }),
19 | rangeSensor: {
20 | beamAngle: 25, // degrees
21 | maxRange: 40, // cm
22 | pos: { x: 0, y: -2.5 },
23 | },
24 | // Recognized line sensor names: "outer-left", "left", "middle", "right", "outer-right"
25 | lineSensors: [
26 | {
27 | name: "left",
28 | // offset to center of sensor from chassis center
29 | pos: { x: -2.8 / 3, y: -2.3 },
30 | },
31 | {
32 | name: "right",
33 | // offset to center of sensor from chassis center
34 | pos: { x: 2.8 / 2, y: -2.3 },
35 | },
36 | ],
37 | leds: [
38 | {
39 | name: "general", // Generalized/non-specific LED
40 | pos: { x: 0, y: 0 },
41 | radius: 0, // The "general" LED takes its shape and size from the chassis
42 | },
43 | ],
44 | }
45 |
46 | export default spec
47 |
--------------------------------------------------------------------------------
/tutorials/line-follower.md:
--------------------------------------------------------------------------------
1 | # Line follower
2 |
3 | ## Line follow @showdialog
4 |
5 | In this tutorial you will learn how to use the line sensors to follow a line
6 | on their left side. We will use the left and right sensors typically located near the metal ball under the robot
7 | (some robots have more).
8 |
9 | ## Choose the robot type @showhint
10 |
11 | Drag the `||robot:robot ... start||` at the start of `||basic:on start||` and choose the robot type
12 | in the dropdown. After restarting, you should also see the robot simulator.
13 |
14 | ```blocks
15 | // @highlight
16 | robot.elecfreaksCuteBot.start()
17 | ```
18 |
19 | ## Detect when the robot is over the Line
20 |
21 | Add an event block to run code when both sensors detect a line.
22 |
23 | ```blocks
24 | robot.onLineLeftRightDetected(true, true, function () {
25 | robot.motorSteer(50, 100)
26 | })
27 | ```
28 |
29 | ## Turn right slightly.
30 |
31 | Add a `||robot:motor run||` block to run right when both lines are detected.
32 |
33 | ```blocks
34 | robot.onLineLeftRightDetected(true, true, function () {
35 | // @highlight
36 | robot.motorSteer(50, 100)
37 | })
38 | ```
39 |
40 | ## Turn left when loosing the line
41 |
42 | Add blocks to turn left when the line is lost, when the right line sensor stops detecting the line.
43 |
44 | ```blocks
45 | robot.onLineLeftRightDetected(false, false, function () {
46 | robot.motorSteer(-50, 100)
47 | })
48 | ```
49 |
50 | ## Improve your code! @showdialog
51 |
52 | Try to improve your code to make your robot follow better the line. Good luck!
53 |
--------------------------------------------------------------------------------
/drivers/sr04sonar.ts:
--------------------------------------------------------------------------------
1 | namespace robot.drivers {
2 | export class SR04Sonar implements Sonar {
3 | /**
4 | * Microseconds to keep the trigger pin low. Default is 4.
5 | */
6 | pulseLowUs?: number
7 | /**
8 | * Microseconds to keep the trigger pin high. Default is 10.
9 | */
10 | pulseHighUs?: number
11 | /**
12 | * Microseconds per cm. Defaults to 58.
13 | */
14 | usPerCm?: number
15 | constructor(
16 | public readonly echo: DigitalPin,
17 | public readonly trig: DigitalPin
18 | ) {}
19 |
20 | start() {}
21 |
22 | distance(maxCmDistance: number): number {
23 | const trig = this.trig
24 | const echo = this.echo
25 | const lowUs = this.pulseLowUs || 4
26 | const highUs = this.pulseHighUs || 10
27 | const usToCm = this.usPerCm || 58
28 |
29 | // send pulse
30 | pins.setPull(trig, PinPullMode.PullNone)
31 | pins.digitalWritePin(trig, 0)
32 | control.waitMicros(lowUs)
33 | pins.digitalWritePin(trig, 1)
34 | control.waitMicros(highUs)
35 | pins.digitalWritePin(trig, 0)
36 |
37 | // read pulse
38 | const d = pins.pulseIn(
39 | echo,
40 | PulseValue.High,
41 | maxCmDistance * usToCm
42 | )
43 | if (d <= 0) return maxCmDistance
44 | return d / usToCm
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/fwdedu/jacbot.ts:
--------------------------------------------------------------------------------
1 | namespace robot {
2 |
3 | class ForwardEducationJacbot extends robots.Robot {
4 | constructor() {
5 | super(0xdeadbeef) // need a reak ID here
6 | this.leds = new drivers.JacdacLed()
7 | this.sonar = new drivers.JacdacSonar()
8 | this.lineDetectors = new drivers.JacdacLines()
9 | this.commands[robot.robots.RobotCompactCommand.MotorTurnLeft] = {
10 | turnRatio: -75,
11 | speed: 40,
12 | }
13 |
14 | this.commands[robot.robots.RobotCompactCommand.MotorTurnRight] = {
15 | turnRatio: 75,
16 | speed: 40,
17 | }
18 | }
19 |
20 | motorRun(lspeed: number, rspeed: number) {
21 | if (lspeed == 0)
22 | fwdMotors.leftServo.setEnabled(false)
23 | else
24 | fwdMotors.leftServo.fwdSetSpeed(lspeed)
25 | if (rspeed == 0)
26 | fwdMotors.rightServo.setEnabled(false)
27 | else
28 | fwdMotors.rightServo.fwdSetSpeed(-rspeed) // silliness with servos
29 | }
30 |
31 | headlightsSetColor(red: number, green: number, blue: number) {
32 | this.leds.setColor(red, green, blue)
33 | }
34 | }
35 |
36 | /**
37 | * Jacdac robot from Forward Education's Climate Action Kit
38 | */
39 | //% fixedInstance whenUsed block="forward education jacbot" weight=100
40 | export const forwardEducationJacbot = new RobotDriver(
41 | new ForwardEducationJacbot()
42 | )
43 | }
44 |
--------------------------------------------------------------------------------
/botsim/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@microbit-robot/botsim",
3 | "version": "0.1.0",
4 | "private": true,
5 | "homepage": "https://microsoft.github.io/microbit-robot/",
6 | "dependencies": {
7 | "@pixi-essentials/gradients": "^1.0.1",
8 | "@types/node": "^16.18.61",
9 | "@types/react": "^18.2.37",
10 | "@types/react-dom": "^18.2.15",
11 | "earcut": "^2.2.4",
12 | "microbit-robot": "file:../",
13 | "nanoid": "^5.0.3",
14 | "pixi.js": "^7.3.2",
15 | "planck-js": "^0.3.31",
16 | "react": "^18.2.0",
17 | "react-dom": "^18.2.0",
18 | "react-scripts": "5.0.1",
19 | "typescript": "^4.9.5",
20 | "yaml": "^2.3.4"
21 | },
22 | "scripts": {
23 | "start": "react-app-rewired start",
24 | "assets": "cp -r ../assets ./public/assets",
25 | "build": "react-app-rewired build",
26 | "prettier": "prettier --write ./src"
27 | },
28 | "eslintConfig": {
29 | "extends": [
30 | "react-app"
31 | ]
32 | },
33 | "browserslist": {
34 | "production": [
35 | ">0.2%",
36 | "not dead",
37 | "not op_mini all"
38 | ],
39 | "development": [
40 | "last 1 chrome version",
41 | "last 1 firefox version",
42 | "last 1 safari version"
43 | ]
44 | },
45 | "devDependencies": {
46 | "@types/earcut": "^2.1.4",
47 | "commander": "^11.1.0",
48 | "get-pixels": "^3.3.3",
49 | "html-webpack-plugin": "^5.5.3",
50 | "node-polyfill-webpack-plugin": "^2.0.1",
51 | "prettier": "^3.1.0",
52 | "react-app-alias-ex": "^2.1.0",
53 | "react-app-rewired": "^2.2.1",
54 | "simplify-js": "^1.2.4"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/_locales/de/microbit-robot-strings.json:
--------------------------------------------------------------------------------
1 | {
2 | "{id:group}Robot": "Roboter",
3 | "{id:group}Motors": "Motoren",
4 | "{id:group}Accessories": "Zubehör",
5 | "{id:group}Lines": "Linien",
6 | "{id:group}Obstacles": "Hindernisse",
7 | "{id:group}Configuration": "Einstellungen",
8 | "robot.motorSteer|block": "Robotermotoren steuern $turnRatio mit $speed \\% || für $duration ms",
9 | "robot.motorTank|block": "Robotermotoren drehen $left \\% $right \\% || für $duration ms",
10 | "RobotAssist.LineFollowing|block": "Linienfolger",
11 | "RobotAssist.Speed|block": "Geschwindigkeit",
12 | "RobotAssist.Display|block": "Anzeige",
13 | "robot.motorStop|block": "Robotermotor stoppen",
14 | "robot.armOpen|block": "Roboterarm $index öffnen $opening \\%",
15 | "robot.setColor|block": "setze Roboterfarbe $rgb",
16 | "robot.obstacleDistance|block": "Abstand zu Hindernis (cm)",
17 | "robot.onObstacleDistanceChanged|block": "wenn Abstand zu Hindernis geändert",
18 | "robot.detectLine|block": "Roboter erkennt Linie $detector",
19 | "robot.onLineDetected|block": "wenn Roboter Liniensensoränderung erkennt",
20 | "robot.onLineLeftRightDetected|block": "Roboter auf Linie $left $right",
21 | "robot.onLineLeftMiddleRightDetected|block": "Roboter auf Linie $left $middle $right",
22 | "robot.onLineOuterLeftLeftOuterRightDetected|block": "Roboter auf Linie $outerLeft $left $right $outerRight",
23 | "robot.setMotorDrift|block": "Motordrift ändern auf %drift",
24 | "robot.setAssist|block": "setze Asistenz $assist $enabled",
25 | "robot.setRadioGroup|block": "setze Funkgruppe auf $group",
26 | "robot.playTone|block": "spiele Ton $frequency für $duration",
27 | "robot.calibrate|block": "Roboter (mit den Knöpfen) kalibrieren"
28 | }
29 |
--------------------------------------------------------------------------------
/drivers/kalmanfilter1d.ts:
--------------------------------------------------------------------------------
1 | namespace robot.drivers {
2 | /**
3 | * Kalman Filter 1D.
4 | * Ported from https://github.com/wouterbulten/kalmanjs
5 | */
6 | export class KalmanFilter1D {
7 | /**
8 | * Last filtered measurement
9 | */
10 | x: number = undefined
11 | /**
12 | * Measurement vector
13 | */
14 | C: number = 1
15 | private cov: number = undefined
16 | /**
17 | * Control vector
18 | */
19 | B: number = 0
20 | /**
21 | * Measurement noise
22 | */
23 | Q: number = 0.1
24 | /**
25 | * Process noise
26 | */
27 | R: number = 0.01
28 | /**
29 | * State vector
30 | */
31 | A: number = 1
32 |
33 | constructor() {}
34 |
35 | /**
36 | * Filter a new value
37 | * @param {Number} z measurement
38 | * @param {Number} u control
39 | * @return {Number}
40 | */
41 | filter(z: number/*, u: number*/) {
42 | const A = this.A
43 | const C = this.C
44 | const Q = this.Q
45 | if (isNaN(this.x)) {
46 | this.x = (1 / C) * z
47 | this.cov = (1 / C) * Q * (1 / C)
48 | } else {
49 | // Compute prediction
50 | const predX = A * this.x /*+ this.B * u*/
51 | const predCov = A * this.cov * A + this.R
52 |
53 | // Kalman gain
54 | const K = predCov * C * (1 / (C * predCov * C + Q))
55 |
56 | // Correction
57 | this.x = predX + K * (z - C * predX)
58 | this.cov = predCov - K * C * predCov
59 | }
60 |
61 | return this.x
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/botsim/README.md:
--------------------------------------------------------------------------------
1 | # BOTSIM - A simulator for the microbit-robot MakeCode extension
2 |
3 | ## Local testing and development
4 |
5 | ### First-time setup
6 | ```
7 | cd botsim
8 | npm install
9 | ```
10 |
11 | ### Start the dev server
12 | ```
13 | npm start
14 | ```
15 |
16 | ## Adding your robot to the simulator
17 |
18 | For reference, see existing bot specifications in `src/bots`
19 |
20 | ### Adding a chassis image to your bot
21 |
22 | If your bot chassis specifies a texture, it will be overlaid on the bot.
23 |
24 | 1. Take a top-down picture of the bot. Make sure it's got a micro:bit on board!
25 | - Tip: Use light-box lighting conditions. Shadows should be subtle or not present.
26 | - Tip: Place the bot on a uniformly colored, matte surface that will be easy to edit out later.
27 | - Tip: Take the picture from a few feet above the bot to minimize perspective effects.
28 | 2. Crop the image, leaving a some margin between the bot and the edge of the picture.
29 | 3. Rotate the image so that the bot is facing upward.
30 | 4. Clip out the wheels and anything else that is clearly not part of the chassis.
31 | 5. Delete all background pixels around the bot.
32 | 6. Crop the image further, leaving a few margin pixels.
33 | 7. Optional: Tweak contrast, brightness, etc.
34 | 8. Optional: Add a dark outline.
35 | 9. Resize the image, making the smaller dimension 256px, preserving the image ratio.
36 | 11. Save the image to "/botsim/public/bots/{your bot name}/chassis.png"
37 | 12. Generate polygon vertices from the chassis image using `scripts/img2hull.js`
38 | - For the script's "scale" parameter, pass the width of the bot in centimeters.
39 | 13. Copy the resulting verts to your bot spec.
40 | - Set chassis type to "polygon".
41 | - Copy the generated vertices to the chassis' "verts" field.
42 | 14. Reference the texture in your bot's chassis spec
43 | - Set the chassis' "texture" field to the texture's relative path.
44 |
--------------------------------------------------------------------------------
/botsim/src/bots/dfrobotmaqueen.ts:
--------------------------------------------------------------------------------
1 | import { BotSpec, toWheels } from "./specs"
2 |
3 | const spec: BotSpec = {
4 | name: "DFRobot Maqueen",
5 | productId: 0x325e1e40,
6 | mass: 1,
7 | weight: 126,
8 | silkColor: "#000000",
9 | chassis: {
10 | shape: "polygon",
11 | texture: "bots/dfrobotmaqueen/chassis.png",
12 | verts: [
13 | { x: -3.9867187499999996, y: -1.5899999999999999 },
14 | { x: -2.1199218749999997, y: -3.9 },
15 | { x: 2.309765625, y: -3.96 },
16 | { x: 3.955078125, y: -1.4399999999999997 },
17 | { x: 3.0691406249999997, y: 3.12 },
18 | { x: 2.4363281249999997, y: 3.8699999999999997 },
19 | { x: -2.53125, y: 3.8699999999999997 },
20 | { x: -3.0691406249999997, y: 3.0599999999999996 },
21 | { x: -3.9867187499999996, y: -0.8099999999999999 },
22 | ],
23 | },
24 | wheels: toWheels({
25 | separation: 6.2,
26 | diameter: 4.2,
27 | width: 1,
28 | y: 1.9,
29 | }),
30 | rangeSensor: {
31 | beamAngle: 25, // degrees
32 | maxRange: 40, // cm
33 | pos: { x: 0, y: -1.5 },
34 | },
35 | // Recognized line sensor names: "outer-left", "left", "middle", "right", "outer-right"
36 | lineSensors: [
37 | {
38 | name: "left",
39 | // offset to center of sensor from chassis center
40 | pos: { x: -0.6, y: -1.6 },
41 | },
42 | {
43 | name: "right",
44 | // offset to center of sensor from chassis center
45 | pos: { x: 0.6, y: -1.6 },
46 | },
47 | ],
48 | leds: [
49 | {
50 | name: "general", // Generalized/non-specific LED
51 | pos: { x: 0, y: 0 },
52 | radius: 0, // The "general" LED takes its shape and size from the chassis
53 | },
54 | ],
55 | }
56 |
57 | export default spec
58 |
--------------------------------------------------------------------------------
/botsim/src/sim/entity.ts:
--------------------------------------------------------------------------------
1 | import { RenderObject } from "./renderer"
2 | import { PhysicsObject } from "./physics"
3 | import { Vec2Like } from "../types/vec2"
4 | import { Simulation } from "."
5 | import { EntitySpec } from "./specs"
6 |
7 | /**
8 | * An Entity is a container for a RenderObject and a PhysicsObject. These two
9 | * objects represent the visual and physical aspects of the entity,
10 | * respectively. The Entity class is responsible for keeping these two objects
11 | * in sync with each other.
12 | */
13 | export class Entity {
14 | private _renderObj!: RenderObject
15 | private _physicsObj!: PhysicsObject
16 | public label: string | undefined
17 |
18 | public get sim(): Simulation {
19 | return this._sim
20 | }
21 | public get renderObj() {
22 | return this._renderObj
23 | }
24 | public get physicsObj() {
25 | return this._physicsObj
26 | }
27 | public get pos(): Vec2Like {
28 | return this.physicsObj.pos
29 | }
30 | public set pos(pos: Vec2Like) {
31 | this.physicsObj.pos = pos
32 | this.renderObj.sync()
33 | }
34 | public get angle(): number {
35 | return this.physicsObj.angle
36 | }
37 | public set angle(angle: number) {
38 | this.physicsObj.angle = angle
39 | this.renderObj.sync()
40 | }
41 |
42 | constructor(
43 | private _sim: Simulation,
44 | spec: EntitySpec
45 | ) {
46 | this.label = spec.label
47 | }
48 |
49 | public init(renderObj: RenderObject, physicsObj: PhysicsObject) {
50 | this._renderObj = renderObj
51 | this._physicsObj = physicsObj
52 | this._renderObj.sync()
53 | }
54 |
55 | public destroy() {
56 | this.renderObj.destroy()
57 | this.physicsObj.destroy()
58 | }
59 |
60 | public update(dtSecs: number) {
61 | this.renderObj.update(dtSecs)
62 | this.physicsObj.update(dtSecs)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/botsim/config-overrides.js:
--------------------------------------------------------------------------------
1 | const { aliasWebpack } = require("react-app-alias-ex");
2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
3 | const HtmlWebpackPlugin = require("html-webpack-plugin");
4 |
5 | module.exports = function (config, env) {
6 | const isEnvProduction = env === "production";
7 | const aliasFn = aliasWebpack({});
8 | config = {
9 | ...config,
10 | ...aliasFn(config),
11 | resolve: {
12 | ...config.resolve,
13 | fallback: {
14 | ...config.resolve.fallback,
15 | fs: false,
16 | path: false,
17 | console: false,
18 | },
19 | },
20 | plugins: [
21 | ...config.plugins.filter((p) => !(p instanceof HtmlWebpackPlugin)),
22 | //new NodePolyfillPlugin(),
23 | new HtmlWebpackPlugin(
24 | Object.assign(
25 | {},
26 | {
27 | inject: true,
28 | template: "./public/index.html",
29 | },
30 | isEnvProduction
31 | ? {
32 | minify: {
33 | removeComments: false,
34 | collapseWhitespace: false,
35 | removeRedundantAttributes: true,
36 | useShortDoctype: true,
37 | removeEmptyAttributes: true,
38 | removeStyleLinkTypeAttributes: true,
39 | keepClosingSlash: true,
40 | minifyJS: true,
41 | minifyCSS: true,
42 | minifyURLs: true,
43 | },
44 | }
45 | : undefined
46 | )
47 | ),
48 | ],
49 | };
50 | return config;
51 | };
52 |
--------------------------------------------------------------------------------
/botsim/src/bots/dfrobotmaqueenplusv2.ts:
--------------------------------------------------------------------------------
1 | import { BotSpec, toWheels } from "./specs"
2 |
3 | const spec: BotSpec = {
4 | name: "DFRobot Maqueen Plus V2",
5 | productId: 0x3375036b,
6 | mass: 1,
7 | weight: 208,
8 | silkColor: "#000000",
9 | chassis: {
10 | shape: "polygon",
11 | texture: "bots/dfrobotmaqueenplusv2/chassis.png",
12 | verts: [
13 | { x: -5, y: 0.5185185185185185 },
14 | { x: -4.6484375, y: -1.5555555555555556 },
15 | { x: -2.0703125, y: -4.851851851851852 },
16 | { x: 1.484375, y: -4.962962962962963 },
17 | { x: 4.8046875, y: -0.8888888888888888 },
18 | { x: 4.8828125, y: 3.8518518518518516 },
19 | { x: 3.125, y: 4.851851851851852 },
20 | { x: -2.96875, y: 4.888888888888889 },
21 | { x: -4.84375, y: 3.962962962962963 },
22 | { x: -5, y: 0.7407407407407407 },
23 | ],
24 | },
25 | wheels: toWheels({
26 | separation: 8.2,
27 | diameter: 4.3,
28 | width: 0.9,
29 | y: 2.3,
30 | }),
31 | rangeSensor: {
32 | beamAngle: 25, // degrees
33 | maxRange: 40, // cm
34 | pos: { x: 0, y: -4.7 },
35 | },
36 | // Recognized line sensor names: "outer-left", "left", "middle", "right", "outer-right"
37 | lineSensors: [
38 | {
39 | name: "left",
40 | // offset to center of sensor from chassis center
41 | pos: { x: -0.9, y: -3.0 },
42 | },
43 | {
44 | name: "middle",
45 | // offset to center of sensor from chassis center
46 | pos: { x: 0, y: -3.0 },
47 | },
48 | {
49 | name: "right",
50 | // offset to center of sensor from chassis center
51 | pos: { x: 0.9, y: -3.0 },
52 | },
53 | ],
54 | leds: [
55 | {
56 | name: "general", // Generalized/non-specific LED
57 | pos: { x: 0, y: 0 },
58 | radius: 0, // The "general" LED takes its shape and size from the chassis
59 | },
60 | ],
61 | }
62 |
63 | export default spec
64 |
--------------------------------------------------------------------------------
/robots/dfrobotmaqueen.ts:
--------------------------------------------------------------------------------
1 | namespace robot {
2 | // https://github.com/DFRobot/pxt-maqueen/blob/master/maqueen.ts#L20
3 | const I2C_ADRESS = 0x10
4 | // https://github.com/DFRobot/pxt-maqueen/blob/master/maqueen.ts#L46
5 | const M1_INDEX = 0
6 | const M2_INDEX = 0x02
7 | // https://github.com/DFRobot/pxt-maqueen/blob/master/maqueen.ts#L62
8 | const FORWARD = 0
9 | const BACKWARD = 1
10 |
11 | // https://github.com/DFRobot/pxt-maqueen/blob/master/maqueen.ts
12 | class DFRobotMaqueenRobot extends robots.Robot {
13 | constructor() {
14 | super(0x325e1e40)
15 | this.lineDetectors = new drivers.DigitalPinLineDetectors(
16 | DigitalPin.P13,
17 | DigitalPin.P14,
18 | false
19 | )
20 | this.leds = new drivers.WS2812bLEDStrip(DigitalPin.P15, 4)
21 | this.sonar = new drivers.SR04Sonar(DigitalPin.P2, DigitalPin.P1)
22 | }
23 |
24 | private run(index: number, speed: number): void {
25 | // https://github.com/DFRobot/pxt-maqueen/blob/master/maqueen.ts#L46
26 | const buf = pins.createBuffer(3)
27 | const direction = speed > 0 ? FORWARD : BACKWARD
28 | const s = Math.round(Math.map(Math.abs(speed), 0, 100, 0, 255))
29 | buf[0] = index
30 | buf[1] = direction
31 | buf[2] = s
32 | pins.i2cWriteBuffer(I2C_ADRESS, buf)
33 | }
34 |
35 | motorRun(left: number, right: number): void {
36 | this.run(M1_INDEX, left)
37 | this.run(M2_INDEX, right)
38 | }
39 |
40 | headlightsSetColor(red: number, green: number, blue: number): void {
41 | // monochrome leds
42 | const on = red > 0xf || green > 0xf || blue > 0xf ? 1 : 0
43 | pins.digitalWritePin(DigitalPin.P8, on)
44 | pins.digitalWritePin(DigitalPin.P12, on)
45 | }
46 | }
47 |
48 | /**
49 | * DFRobot Maqueen
50 | */
51 | //% fixedInstance block="dfrobot maqueen" whenUsed weight=80
52 | export const dfRobotMaqueen = new RobotDriver(new DFRobotMaqueenRobot())
53 | }
54 |
--------------------------------------------------------------------------------
/robots/dfrobotmaqueenplusv2.ts:
--------------------------------------------------------------------------------
1 | namespace robot {
2 | const I2CADDR = 0x10
3 | const LEFT_LED_REGISTER = 0x0b
4 | const RIGHT_LED_REGISTER = 0x0c
5 | const LEFT_MOTOR_REGISTER = 0x00
6 | const RIGHT_MOTOR_REGISTER = 0x02
7 | const LINE_STATE_REGISTER = 0x1d
8 | const FORWARD = 0
9 | const BACKWARD = 1
10 | function run(index: number, speed: number): void {
11 | const buf = pins.createBuffer(3)
12 | const direction = speed > 0 ? FORWARD : BACKWARD
13 | const s = Math.round(Math.map(Math.abs(speed), 0, 100, 0, 255))
14 | buf[0] = index
15 | buf[1] = direction
16 | buf[2] = s
17 | pins.i2cWriteBuffer(I2CADDR, buf)
18 | }
19 |
20 | // https://github.com/DFRobot/pxt-DFRobot_MaqueenPlus_v20/blob/master/maqueenPlusV2.ts
21 | class DFRobotMaqueenPlusV2Robot extends robots.Robot {
22 | constructor() {
23 | super(0x3375036b)
24 | this.leds = new drivers.WS2812bLEDStrip(DigitalPin.P15, 4)
25 | this.sonar = new drivers.SR04Sonar(DigitalPin.P14, DigitalPin.P13)
26 | }
27 |
28 | motorRun(left: number, right: number): void {
29 | run(LEFT_MOTOR_REGISTER, left)
30 | run(RIGHT_MOTOR_REGISTER, right)
31 | }
32 |
33 | headlightsSetColor(red: number, green: number, blue: number): void {
34 | const on = red > 0xf || green > 0xf || blue > 0xf ? 1 : 0
35 | const buf = pins.createBuffer(3)
36 | buf[0] = LEFT_LED_REGISTER
37 | buf[1] = on
38 | buf[2] = on
39 | pins.i2cWriteBuffer(I2CADDR, buf)
40 | }
41 |
42 | lineState() {
43 | const data = robots.i2cReadRegU8(I2CADDR, LINE_STATE_REGISTER)
44 | const left = (data & 0x08) == 0x08 ? 1 : 0
45 | const right = (data & 0x02) == 0x02 ? 1 : 0
46 | return (left << 0) | (right << 1)
47 | }
48 | }
49 |
50 | /**
51 | * DFRobot Maqueen
52 | */
53 | //% fixedInstance block="dfrobot maqueen plus v2" whenUsed weight=80
54 | export const dfRobotMaqueenPlusV2 = new RobotDriver(
55 | new DFRobotMaqueenPlusV2Robot()
56 | )
57 | }
58 |
--------------------------------------------------------------------------------
/calibration.ts:
--------------------------------------------------------------------------------
1 | namespace robot {
2 | /**
3 | * Registers button A, B, A+B to change the radio group and drift.
4 | */
5 | export function startCalibrationButtons(calibrate?: boolean) {
6 | const d = RobotDriver.instance()
7 | input.onButtonPressed(Button.A, () =>
8 | control.inBackground(() => {
9 | if (d.configDrift !== undefined) {
10 | d.playTone(440, 500)
11 | if (d.configDrift) d.setRunDrift(d.runDrift - 1)
12 | else d.setRadioGroup(d.radioGroup - 1)
13 | }
14 | showConfigurationState(d)
15 | })
16 | )
17 | input.onButtonPressed(Button.B, () =>
18 | control.inBackground(() => {
19 | if (d.configDrift !== undefined) {
20 | d.playTone(640, 500)
21 | if (d.configDrift) d.setRunDrift(d.runDrift + 1)
22 | else d.setRadioGroup(d.radioGroup + 1)
23 | }
24 | showConfigurationState(d)
25 | })
26 | )
27 | input.onButtonPressed(Button.AB, () =>
28 | control.inBackground(() => {
29 | d.playTone(840, 500)
30 | d.configDrift = !d.configDrift
31 | showConfigurationState(d, true)
32 | })
33 | )
34 |
35 | if (calibrate)
36 | showConfigurationState(d, true)
37 | }
38 |
39 | function showConfigurationState(d: RobotDriver, showTitle?: boolean) {
40 | d.showConfiguration++
41 | try {
42 | led.stopAnimation()
43 | if (d.configDrift === undefined) {
44 | basic.showString(
45 | `RADIO ${d.radioGroup} DRIFT ${d.runDrift}`,
46 | configuration.SCROLL_SPEED
47 | )
48 | } else {
49 | const title = d.configDrift ? "DRIFT" : "RADIO"
50 | const value = d.configDrift ? d.runDrift : d.radioGroup
51 | basic.showString(title + " " + value, configuration.SCROLL_SPEED)
52 | }
53 | } finally {
54 | d.showConfiguration--
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/botsim/src/sim/bot/led.ts:
--------------------------------------------------------------------------------
1 | import { Bot } from "."
2 | import { BotSpec, LEDSpec } from "../../bots/specs"
3 | import { Vec2 } from "../../types/vec2"
4 | import {
5 | EntityShapeSpec,
6 | defaultCircleShape,
7 | defaultColorBrush,
8 | defaultEntityShape,
9 | defaultShapePhysics,
10 | } from "../specs"
11 |
12 | export class LED {
13 | public static makeShapeSpec(
14 | bot: BotSpec,
15 | ledSpec: LEDSpec
16 | ): EntityShapeSpec {
17 | if (ledSpec.name === "general") {
18 | let radius = 0
19 | if (bot.chassis.shape === "circle") {
20 | radius = bot.chassis.radius * 2
21 | } else if (bot.chassis.shape === "box") {
22 | radius = 1.5 * Vec2.len(bot.chassis.size)
23 | }
24 | const shapeSpec: EntityShapeSpec = {
25 | ...defaultEntityShape(),
26 | ...defaultCircleShape(),
27 | radius,
28 | physics: {
29 | ...defaultShapePhysics(),
30 | sensor: true, // Don't collide with anything
31 | },
32 | brush: {
33 | ...defaultColorBrush(),
34 | fillColor: "00FF0044",
35 | borderColor: "transparent",
36 | borderWidth: 0,
37 | zIndex: 1,
38 | },
39 | }
40 | return shapeSpec
41 | } else {
42 | // Not fully implemented yet
43 | const shapeSpec: EntityShapeSpec = {
44 | ...defaultEntityShape(),
45 | ...defaultCircleShape(),
46 | offset: ledSpec.pos,
47 | radius: ledSpec.radius,
48 | }
49 | return shapeSpec
50 | }
51 | }
52 |
53 | private color: number = 0
54 |
55 | constructor(
56 | private bot: Bot,
57 | private spec: LEDSpec
58 | ) {}
59 |
60 | public destroy() {}
61 |
62 | public update(dtSecs: number) {}
63 |
64 | public setColor(color: number) {
65 | if (color === this.color) return
66 | this.color = color
67 | this.rebuildBrush()
68 | }
69 |
70 | private rebuildBrush() {}
71 | }
72 |
--------------------------------------------------------------------------------
/sim.ts:
--------------------------------------------------------------------------------
1 | namespace robot.robots {
2 | export let sensorsUsed = Sensors.None
3 |
4 | /**
5 | * Sends the robot state to the simulator
6 | */
7 | //% shim=TD_NOOP
8 | export function sendSim() {
9 | const r = robot.RobotDriver.instance()
10 | const msg = {
11 | type: "state",
12 | id: r.id,
13 | deviceId: control.deviceSerialNumber(),
14 | motorSpeed: r.currentSpeed,
15 | motorTurnRatio: r.currentTurnRatio,
16 | motorLeft: r.currentThrottle[0],
17 | motorRight: r.currentThrottle[1],
18 | color: r.currentColor,
19 | assists: r.assists,
20 | sensors: sensorsUsed
21 | }
22 | if (r.robot.productId) msg.productId = r.robot.productId
23 | control.simmessages.send("robot", Buffer.fromUTF8(JSON.stringify(msg)), false)
24 | }
25 |
26 | function handleRobotMessage(b: Buffer) {
27 | const s = b.toString()
28 | const msg = JSON.parse(s)
29 | if (msg && msg.type === "sensors") {
30 | const r = robot.RobotDriver.instance()
31 | const sensors = msg
32 | if (sensors.deviceId != control.deviceSerialNumber() ||
33 | sensors.id !== r.id) return
34 |
35 | const rob = r.robot
36 | if (Array.isArray(sensors.lineDetectors)) {
37 | const lines = rob.lineDetectors as drivers.SimLineDetectors
38 | const old = lines.current
39 | lines.current = sensors.lineDetectors
40 | }
41 | if (!isNaN(sensors.obstacleDistance)) {
42 | const sonar = rob.sonar as drivers.SimSonar
43 | sonar.current = sensors.obstacleDistance
44 | }
45 | }
46 | }
47 |
48 | /**
49 | * Register simulator line sensor and sonar
50 | */
51 | //% shim=TD_NOOP
52 | export function registerSim() {
53 | const r = robot.RobotDriver.instance()
54 | // replace pysical robot with simulator robot, before starting
55 | r.robot = new robots.SimRobot(r.robot)
56 | control.simmessages.onReceived("robot", handleRobotMessage)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/robots/kitronikmotordriverrccar.ts:
--------------------------------------------------------------------------------
1 | namespace robot {
2 | const MOTOR_SPEED = 100
3 |
4 | // https://github.com/KitronikLtd/pxt-kitronik-motor-driver
5 | class KitronikMotorDriverRCCar extends robots.Robot {
6 | readonly throttle: drivers.AnalogPinHBridgeMotor
7 | readonly direction: drivers.AnalogPinHBridgeMotor
8 | constructor() {
9 | super(0x33498160)
10 | this.maxLineSpeed = MOTOR_SPEED
11 | this.speedTransitionAlpha = 0
12 | this.speedBrakeTransitionAlpha = 0
13 | this.throttle = new drivers.AnalogPinHBridgeMotor(
14 | DigitalPin.P12,
15 | DigitalPin.P8
16 | )
17 | this.direction = new drivers.AnalogPinHBridgeMotor(
18 | DigitalPin.P0,
19 | DigitalPin.P16
20 | )
21 | this.commands[robot.robots.RobotCompactCommand.MotorRunForward] = {
22 | speed: MOTOR_SPEED,
23 | }
24 | this.commands[robot.robots.RobotCompactCommand.MotorTurnLeft] = {
25 | turnRatio: -60,
26 | speed: MOTOR_SPEED,
27 | }
28 | this.commands[robot.robots.RobotCompactCommand.MotorTurnRight] = {
29 | turnRatio: 60,
30 | speed: MOTOR_SPEED,
31 | }
32 | this.commands[robot.robots.RobotCompactCommand.MotorSpinLeft] = {
33 | turnRatio: -100,
34 | speed: MOTOR_SPEED,
35 | }
36 | this.commands[robot.robots.RobotCompactCommand.MotorSpinRight] = {
37 | turnRatio: 100,
38 | speed: MOTOR_SPEED,
39 | }
40 | }
41 |
42 | motorRun(left: number, right: number): void {
43 | // need to convert this back to angle, throttle
44 | const [speed, dir] = drivers.tankToRCMotors(left, right)
45 | this.throttle.run(speed)
46 | this.direction.run(dir)
47 | }
48 | }
49 |
50 | /**
51 | * Kitronik Motor Driver RC Car
52 | */
53 | //% fixedInstance whenUsed block="Kitronik Motor Driver RC Car"
54 | export const kitronikMotorDriverRCCar = new RobotDriver(
55 | new KitronikMotorDriverRCCar()
56 | )
57 | }
58 |
--------------------------------------------------------------------------------
/pxt.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "microbit-robot",
3 | "version": "2.7.4",
4 | "dependencies": {
5 | "core": "*",
6 | "radio": "*",
7 | "ws2812b": "github:microsoft/pxt-ws2812b#v0.1.2"
8 | },
9 | "files": [
10 | "README.md",
11 | "protocol/protocol.ts",
12 | "i2c.ts",
13 | "microbit.ts",
14 | "messages.ts",
15 | "blocks.ts",
16 | "robotdriver.ts",
17 | "storage.cpp",
18 | "storage.ts",
19 | "radio.ts",
20 | "calibration.ts",
21 | "tester.ts",
22 | "sim.ts",
23 | "drivers/drivers.ts",
24 | "drivers/hbridgemotor.ts",
25 | "drivers/ws2812bleds.ts",
26 | "drivers/sr04sonar.ts",
27 | "drivers/pinlinedetectors.ts",
28 | "drivers/servoarm.ts",
29 | "drivers/kalmanfilter1d.ts",
30 | "drivers/sim.ts",
31 | "drivers/rccar.ts",
32 | "robots/robot.ts",
33 | "robots/keystudiominismartrobot.ts",
34 | "robots/yahboomtinybit.ts",
35 | "robots/elecfreakscutebot.ts",
36 | "robots/elecfreakscutebotpro.ts",
37 | "robots/dfrobotmaqueen.ts",
38 | "robots/dfrobotmaqueenplusv2.ts",
39 | "robots/kittenbotminilfr.ts",
40 | "robots/kittenbotrobotbit.ts",
41 | "robots/kittenbotnanobit.ts",
42 | "robots/kittenbottabbybot.ts",
43 | "robots/kitronikmotordriverrccar.ts",
44 | "robots/inksmithk8.ts",
45 | "robots/sim.ts",
46 | "constants.ts",
47 | "robots/knotechcallibot.ts",
48 | "robots/tslmotionkit2.ts",
49 | "_locales/de/microbit-robot-strings.json",
50 | "tutorials/getting-started.md",
51 | "tutorials/light-and-sound.md",
52 | "tutorials/line-follower.md"
53 | ],
54 | "testFiles": [
55 | "test.ts"
56 | ],
57 | "fileDependencies": {
58 | "kittenbotnanobit.ts": "target:microbit",
59 | "kittenbotrobotbit.ts": "target:microbit",
60 | "kittenbottabbybot.ts": "target:microbit",
61 | "yahboomtinybit.ts": "target:microbit",
62 | "microbit.ts": "target:microbit"
63 | },
64 | "targetVersions": {
65 | "target": "6.0.26",
66 | "targetId": "microbit"
67 | },
68 | "supportedTargets": [
69 | "microbit",
70 | "calliopemini"
71 | ],
72 | "preferredEditor": "tsprj"
73 | }
74 |
--------------------------------------------------------------------------------
/botsim/src/bots/elecfreakscutebot.ts:
--------------------------------------------------------------------------------
1 | import { BotSpec } from "./specs"
2 |
3 | const spec: BotSpec = {
4 | name: "Elecfreaks Cutebot",
5 | productId: 0x3818d146,
6 | mass: 1,
7 | silkColor: "#0000F0",
8 | chassis: {
9 | shape: "polygon",
10 | texture: "bots/elecfreakscutebot/chassis.png",
11 | verts: [
12 | { x: -4.282868525896414, y: -0.57109375 },
13 | { x: -4.145816733067729, y: -1.2765625 },
14 | { x: -3.9402390438247012, y: -1.8140625 },
15 | { x: -3.6661354581673304, y: -2.31796875 },
16 | { x: -3.049402390438247, y: -3.19140625 },
17 | { x: -1.952988047808765, y: -4.19921875 },
18 | { x: -0.650996015936255, y: -4.3 },
19 | { x: 0.6852589641434262, y: -4.3 },
20 | { x: 2.055776892430279, y: -4.13203125 },
21 | { x: 2.9808764940239043, y: -3.2585937499999997 },
22 | { x: 3.6661354581673304, y: -2.38515625 },
23 | { x: 4.043027888446215, y: -1.64609375 },
24 | { x: 4.282868525896414, y: -0.8062499999999999 },
25 | { x: 4.282868525896414, y: 4.26640625 },
26 | { x: -4.282868525896414, y: 4.26640625 },
27 | ],
28 | },
29 | wheels: [
30 | {
31 | name: "left",
32 | maxSpeed: 100,
33 | pos: { x: -3.8, y: 1.5 },
34 | width: 1.3,
35 | radius: 3.6 / 2,
36 | dashTime: 0.5,
37 | },
38 | {
39 | name: "right",
40 | maxSpeed: 100,
41 | pos: { x: 3.8, y: 1.5 },
42 | width: 1.3,
43 | radius: 3.6 / 2,
44 | dashTime: 0.5,
45 | },
46 | ],
47 | rangeSensor: {
48 | beamAngle: 25,
49 | maxRange: 40,
50 | pos: { x: 0, y: -4.2 },
51 | },
52 | lineSensors: [
53 | {
54 | name: "left",
55 | pos: { x: -0.58, y: -2.74 },
56 | },
57 | {
58 | name: "right",
59 | pos: { x: 0.58, y: -2.74 },
60 | },
61 | ],
62 | leds: [
63 | {
64 | name: "general",
65 | pos: { x: 0, y: 0 },
66 | radius: 0,
67 | },
68 | ],
69 | ballast: {
70 | pos: { x: 0, y: 1.3 },
71 | size: { x: 3.8, y: 1.6 },
72 | mass: 10,
73 | },
74 | }
75 |
76 | export default spec
77 |
--------------------------------------------------------------------------------
/drivers/pinlinedetectors.ts:
--------------------------------------------------------------------------------
1 | namespace robot.drivers {
2 | export class DigitalPinLineDetectors implements LineDetectors {
3 | readonly pins: DigitalPin[] = []
4 | /**
5 | * Left line detector
6 | */
7 | constructor(
8 | left: DigitalPin,
9 | right: DigitalPin,
10 | public lineHigh = false
11 | ) {
12 | this.pins = []
13 | if (left) this.pins[RobotLineDetector.Left] = left
14 | if (right) this.pins[RobotLineDetector.Right] = right
15 | }
16 |
17 | start() {
18 | for (let i = 0; i < this.pins.length; ++i) {
19 | const pin = this.pins[i]
20 | if (pin) pins.setPull(pin, PinPullMode.PullNone)
21 | }
22 | }
23 |
24 | lineState(state: number[]) {
25 | for (let i = 0; i < this.pins.length; ++i) {
26 | const pin = this.pins[i]
27 | if (pin) {
28 | const v =
29 | pins.digitalReadPin(pin) > 0 === this.lineHigh
30 | ? 1023
31 | : 0
32 | state[i] = v
33 | }
34 | }
35 | }
36 | }
37 |
38 | export class AnalogPinLineDetectors implements LineDetectors {
39 | readonly pins: AnalogPin[] = []
40 | /**
41 | * Left line detector
42 | */
43 | constructor(
44 | left: AnalogPin,
45 | right: AnalogPin,
46 | public lineHigh = false
47 | ) {
48 | this.pins = []
49 | if (left) this.pins[RobotLineDetector.Left] = left
50 | if (right) this.pins[RobotLineDetector.Right] = right
51 | }
52 |
53 | start() {
54 | for (let i = 0; i < this.pins.length; ++i) {
55 | const pin = this.pins[i]
56 | if (pin) pins.setPull(pin as number, PinPullMode.PullNone)
57 | }
58 | }
59 |
60 | lineState(state: number[]) {
61 | for (let i = 0; i < this.pins.length; ++i) {
62 | const pin = this.pins[i]
63 | if (pin) {
64 | let v = pins.analogReadPin(pin)
65 | if (this.lineHigh) v = 1023 - v
66 | state[i] = v
67 | }
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/robots/elecfreakscutebot.ts:
--------------------------------------------------------------------------------
1 | namespace robot {
2 | const STM8_ADDRESSS = 0x10
3 |
4 | //https://github.com/elecfreaks/pxt-cutebot/blob/master/cutebot.ts
5 | class ElecfreaksCutebotRobot extends robots.Robot {
6 | constructor() {
7 | super(0x3818d146)
8 | this.leds = new drivers.WS2812bLEDStrip(DigitalPin.P15, 2)
9 | this.sonar = new drivers.SR04Sonar(DigitalPin.P12, DigitalPin.P8)
10 | this.lineDetectors = new drivers.DigitalPinLineDetectors(
11 | DigitalPin.P13,
12 | DigitalPin.P14,
13 | false
14 | )
15 | this.arms = [new drivers.ServoArm(45, 135, AnalogPin.P1), new drivers.ServoArm(45, 135, AnalogPin.P2)]
16 | this.maxLineSpeed = 28
17 | }
18 |
19 | motorRun(lspeed: number, rspeed: number) {
20 | const buf = pins.createBuffer(4)
21 | if (lspeed > 0) {
22 | buf[0] = 0x01 //左右轮 0x01左轮 0x02右轮
23 | buf[1] = 0x02 //正反转0x02前进 0x01后退
24 | buf[2] = lspeed //速度
25 | buf[3] = 0 //补位
26 | } else {
27 | buf[0] = 0x01
28 | buf[1] = 0x01
29 | buf[2] = -lspeed
30 | buf[3] = 0 //补位
31 | }
32 | pins.i2cWriteBuffer(STM8_ADDRESSS, buf) //写入左轮
33 | if (rspeed > 0) {
34 | buf[0] = 0x02
35 | buf[1] = 0x02
36 | buf[2] = rspeed
37 | buf[3] = 0 //补位
38 | } else {
39 | buf[0] = 0x02
40 | buf[1] = 0x01
41 | buf[2] = -rspeed
42 | buf[3] = 0 //补位
43 | }
44 | pins.i2cWriteBuffer(STM8_ADDRESSS, buf) //写入左轮
45 | }
46 |
47 | headlightsSetColor(red: number, green: number, blue: number) {
48 | const buf = pins.createBuffer(4)
49 | buf[0] = 0x04
50 | buf[1] = red
51 | buf[2] = green
52 | buf[3] = blue
53 | pins.i2cWriteBuffer(STM8_ADDRESSS, buf)
54 | buf[0] = 0x08
55 | pins.i2cWriteBuffer(STM8_ADDRESSS, buf)
56 | }
57 | }
58 |
59 | /**
60 | * Cute:bot from Elecfreaks
61 | */
62 | //% fixedInstance whenUsed block="elecfreaks cutebot" weight=100
63 | export const elecfreaksCuteBot = new RobotDriver(
64 | new ElecfreaksCutebotRobot()
65 | )
66 | }
67 |
--------------------------------------------------------------------------------
/botsim/src/ui/SimContainer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { useState, useEffect, useRef } from "react"
3 | import { Simulation } from "../sim"
4 | import { Vec2 } from "../types/vec2"
5 |
6 | type Props = {}
7 |
8 | export const SimContainer: React.FC = ({}) => {
9 | const [simContainer, setSimContainer] = useState()
10 |
11 | // Get the simulator instance and run it as long as we're mounted
12 | useEffect(() => {
13 | return () => {
14 | Simulation.instance.clear()
15 | Simulation.instance.stop()
16 | }
17 | }, [])
18 |
19 | // Add the simulator to the div once we have the ref.
20 | // Ensure container has no existing children, because React.
21 | useEffect(() => {
22 | if (simContainer && !simContainer.firstChild) {
23 | simContainer.append(Simulation.instance.renderer.handle as any)
24 | }
25 | }, [simContainer])
26 |
27 | // Hook mouse events
28 | useEffect(() => {
29 | if (simContainer) {
30 | const handleMouseDown = (e: MouseEvent) => {
31 | if (e.button === 0) {
32 | const p = Vec2.like(e.offsetX, e.offsetY)
33 | Simulation.instance.mouseDown(p)
34 | }
35 | }
36 | const handleMouseMove = (e: MouseEvent) => {
37 | const p = Vec2.like(e.offsetX, e.offsetY)
38 | Simulation.instance.mouseMove(p)
39 | }
40 | const handleMouseUp = (e: MouseEvent) => {
41 | if (e.button === 0) {
42 | const p = Vec2.like(e.offsetX, e.offsetY)
43 | Simulation.instance.mouseUp(p)
44 | }
45 | }
46 | simContainer.addEventListener("mousedown", handleMouseDown)
47 | simContainer.addEventListener("mousemove", handleMouseMove)
48 | simContainer.addEventListener("mouseup", handleMouseUp)
49 | return () => {
50 | simContainer.removeEventListener("mousedown", handleMouseDown)
51 | simContainer.removeEventListener("mousemove", handleMouseMove)
52 | simContainer.removeEventListener("mouseup", handleMouseUp)
53 | }
54 | }
55 | }, [simContainer])
56 |
57 | const handleDivRef = (ref: HTMLDivElement) => {
58 | setSimContainer(ref)
59 | }
60 |
61 | return
62 | }
63 |
--------------------------------------------------------------------------------
/robots/inksmithk8.ts:
--------------------------------------------------------------------------------
1 | namespace robot {
2 | // https://github.com/k8robotics/pxt-k8
3 | const IR_SENSOR_LEFT = DigitalPin.P0 //AnalogPin.P0
4 | const IR_SENSOR_MIDDLE = DigitalPin.P1 //AnalogPin.P1
5 | const SPEAKER = AnalogPin.P1
6 | const IR_SENSOR_RIGHT = DigitalPin.P2 // AnalogPin.P2
7 | const SERVO_2 = AnalogPin.P8
8 | const SONAR = DigitalPin.P8
9 | const SERVO_1 = AnalogPin.P12
10 | const M2_PWR: number = DigitalPin.P13
11 | const M2_DIR: number = DigitalPin.P14
12 | const M1_PWR: number = DigitalPin.P15
13 | const M1_DIR: number = DigitalPin.P16
14 |
15 | const enum Motor {
16 | LEFT = 0,
17 | RIGHT = 1,
18 | }
19 |
20 | const enum MotorDirection {
21 | FORWARD = 0,
22 | REVERSE = 1,
23 | }
24 |
25 | class InksmithK8Robot extends robots.Robot {
26 | constructor() {
27 | super(0x3b603322)
28 | this.lineDetectors = new drivers.DigitalPinLineDetectors(
29 | IR_SENSOR_LEFT,
30 | IR_SENSOR_RIGHT,
31 | true
32 | )
33 | this.arms = [new drivers.ServoArm(-85, 85, SERVO_1), new drivers.ServoArm(-85, 85, SERVO_2)]
34 | const sonar = new drivers.SR04Sonar(SONAR, SONAR)
35 | this.sonar = sonar
36 | }
37 |
38 | start() {
39 | pins.analogSetPeriod(M1_PWR, 1024)
40 | pins.analogSetPeriod(M2_PWR, 1024)
41 | }
42 |
43 | motorRun(left: number, right: number): void {
44 | const l = Math.clamp(
45 | 0,
46 | 1023,
47 | Math.round((Math.abs(left) / 100) * 1023)
48 | )
49 | const r = Math.clamp(
50 | 0,
51 | 1023,
52 | Math.round((Math.abs(right) / 100) * 1023)
53 | )
54 |
55 | pins.digitalWritePin(
56 | M1_DIR,
57 | left >= 0 ? MotorDirection.FORWARD : MotorDirection.REVERSE
58 | )
59 | pins.analogWritePin(M1_PWR, l)
60 |
61 | pins.digitalWritePin(
62 | M2_DIR,
63 | right >= 0 ? MotorDirection.FORWARD : MotorDirection.REVERSE
64 | )
65 | pins.analogWritePin(M2_PWR, r)
66 | }
67 | }
68 |
69 | /**
70 | * Inksmith K8
71 | */
72 | //% fixedInstance block="inksmith k8" whenUsed
73 | export const inksmithK8 = new RobotDriver(new InksmithK8Robot())
74 | }
75 |
--------------------------------------------------------------------------------
/botsim/src/bots/testBot.ts:
--------------------------------------------------------------------------------
1 | import { BotSpec } from "./specs"
2 |
3 | const spec: BotSpec = {
4 | name: "Test Bot",
5 | productId: 0,
6 | mass: 1,
7 | chassis: {
8 | shape: "circle",
9 | radius: 8.6 / 2, // cm
10 | },
11 | // Recognized wheel names: "left", "right"
12 | wheels: [
13 | {
14 | name: "left",
15 | maxSpeed: 100,
16 | // offset to center of wheel from chassis center
17 | pos: { x: -3.8, y: 1.5 },
18 | // wheel width
19 | width: 1.3,
20 | // wheel radius
21 | radius: 3.6 / 2,
22 | dashTime: 0.5,
23 | },
24 | {
25 | name: "right",
26 | maxSpeed: 100,
27 | // offset to center of wheel from chassis center
28 | pos: { x: 3.8, y: 1.5 },
29 | // wheel width
30 | width: 1.3,
31 | // wheel radius
32 | radius: 3.6 / 2,
33 | dashTime: 0.5,
34 | },
35 | ],
36 | rangeSensor: {
37 | beamAngle: 25, // degrees
38 | maxRange: 40, // cm
39 | pos: { x: 0, y: -4.2 },
40 | },
41 | // Recognized line sensor names: "outer-left", "left", "middle", "right", "outer-right"
42 | lineSensors: [
43 | {
44 | name: "left",
45 | // offset to center of sensor from chassis center
46 | pos: { x: -0.58, y: -2.74 },
47 | },
48 | {
49 | name: "right",
50 | // offset to center of sensor from chassis center
51 | pos: { x: 0.58, y: -2.74 },
52 | },
53 | ],
54 | leds: [
55 | /*
56 | {
57 | name: "left",
58 | pos: { x: 4.5, y: 1.5 },
59 | brush: {
60 | ...defaultColorBrush(),
61 | },
62 | },
63 | {
64 | name: "right",
65 | pos: { x: 4.5, y: -1.5 },
66 | brush: {
67 | ...defaultColorBrush(),
68 | },
69 | },
70 | */
71 | {
72 | name: "general", // Generalized/non-specific LED
73 | pos: { x: 0, y: 0 },
74 | radius: 0, // The "general" LED takes its shape and size from the chassis
75 | },
76 | ],
77 | // Ballast can be used to adjust the center of mass of the bot.
78 | // Here it represents a battery located between the wheels.
79 | ballast: {
80 | pos: { x: 0, y: 1.3 },
81 | size: { x: 3.8, y: 1.6 },
82 | mass: 10,
83 | },
84 | }
85 |
86 | export default spec
87 |
--------------------------------------------------------------------------------
/tester.ts:
--------------------------------------------------------------------------------
1 | namespace robot.test {
2 | /**
3 | * Starts a testing mode to be used when building a robot
4 | */
5 | export function startTestMode() {
6 | setAssist(RobotAssist.LineFollowing, false)
7 | onLineDetected(function () {
8 | playTone(600, 50)
9 | })
10 | onLineLeftRightDetected(true, true, () => {
11 | playTone(640, 50)
12 | led.toggle(0, 2)
13 | })
14 | onLineLeftMiddleRightDetected(true, true, true, () => {
15 | playTone(680, 50)
16 | })
17 | onLineOuterLeftLeftOuterRightDetected(true, true, true, true, () => {
18 | playTone(720, 50)
19 | })
20 | onObstacleDistanceChanged(function () {
21 | playTone(768, 50)
22 | })
23 |
24 | input.onButtonPressed(Button.A, () => {
25 | const d = 1000
26 | playTone(440, 200)
27 | setColor(0xff0000)
28 | motorTank(-100, 100)
29 | pause(d)
30 | playTone(440, 200)
31 | setColor(0xff0000)
32 | motorTank(100, -100)
33 | pause(d)
34 | playTone(840, 200)
35 | setColor(0x000000)
36 | motorStop()
37 | })
38 |
39 | input.onButtonPressed(Button.B, () => {
40 | const d = 600
41 |
42 | motorTank(50, 50, d)
43 | motorTank(0, 0, d)
44 | motorTank(-50, -50, d)
45 | motorStop()
46 |
47 | motorSteer(0, 100, d)
48 | motorSteer(0, 0, d)
49 | motorSteer(0, -100, d)
50 | motorStop()
51 | })
52 |
53 | input.onButtonPressed(Button.AB, () => {
54 | setAssist(RobotAssist.LineFollowing, true)
55 | let last = 0
56 | onLineLeftRightDetected(true, true, () => {
57 | console.log(`x x`)
58 | last = 0
59 | motorSteer(0, 100)
60 | })
61 | onLineLeftRightDetected(false, false, () => {
62 | console.log(`o o`)
63 | if (last < 0) motorSteer(-200, 100)
64 | else motorSteer(200, 100)
65 | })
66 | onLineLeftRightDetected(true, false, () => {
67 | console.log(`x o`)
68 | last = -1
69 | motorSteer(-100, 100)
70 | })
71 | onLineLeftRightDetected(false, true, () => {
72 | console.log(`o x`)
73 | last = 1
74 | motorSteer(100, 100)
75 | })
76 | })
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/tutorials/getting-started.md:
--------------------------------------------------------------------------------
1 | # Micro:bit Robot Getting Started
2 |
3 | ## Getting started @showdialog
4 |
5 | In this tutorial, you will learn to configure your robot, use the simulator, move forward and do turns.
6 | Let's go!
7 |
8 | ## Choose the robot type @showhint
9 |
10 | Drag the `||robot:robot ... start||` at the start of `||basic:on start||` and choose the robot type
11 | in the dropdown. After restarting, you should also see the robot simulator.
12 |
13 | ```blocks
14 | // @highlight
15 | robot.elecfreaksCuteBot.start()
16 | ```
17 |
18 | ## Simulator Pro Tips! @showdialog
19 |
20 | The robot simulator is there to help you create cool robot programs.
21 | - If you are on a small screen, click on the **full screen icon** to see it.
22 | - Drag the robot around using the mouse.
23 | - The simulator is not perfect! There will be differences of behavior between the simulator
24 | and your physical robot based on the motors, gearing, weight, balance, and other imperfections
25 | of the real world!
26 |
27 | ## Move forward
28 |
29 | Drag a `||robot:robot motor run||` block into on start.
30 | It will instruct the robot to go full throttle on both motors.
31 |
32 | You will see the robot moving forward in the robot simulator.
33 |
34 | ```blocks
35 | robot.elecfreaksCuteBot.start()
36 | // @highlight
37 | robot.motorSteer(0, 100)
38 | ```
39 |
40 | ## Stop
41 |
42 | Use the `||robot:robot stop||` block to stop all motors after 1 second.
43 |
44 | ```blocks
45 | robot.elecfreaksCuteBot.start()
46 | robot.motorSteer(0, 100)
47 | basic.pause(1000)
48 | // @highlight
49 | robot.motorStop()
50 | ```
51 |
52 | ## Turn Right
53 |
54 | Add another `||robot:robot motor run with steering 100||`. The steering controls the distributing
55 | of forces between the two motors. Positive steering will favor the right motor
56 | where the left motor stops spinning at 100, and starts going negative beyond 100.
57 |
58 | ```blocks
59 | robot.elecfreaksCuteBot.start()
60 | robot.motorSteer(0, 100)
61 | basic.pause(1000)
62 | robot.motorStop()
63 | basic.pause(1000)
64 | // @highlight
65 | robot.motorSteer(100, 100)
66 | ```
67 |
68 | ## Spin Right
69 |
70 | Add another `||robot:robot motor run with steering 200||`. The left motor will spin backward
71 | and the right motor will spin forward.
72 |
73 | ```blocks
74 | robot.elecfreaksCuteBot.start()
75 | robot.motorSteer(0, 100)
76 | basic.pause(1000)
77 | robot.motorStop()
78 | basic.pause(1000)
79 | robot.motorSteer(100, 100)
80 | basic.pause(1000)
81 | // @highlight
82 | robot.motorSteer(200, 100)
83 | ```
84 |
85 | ## Make it dance @showdialog
86 |
87 | That's it. You can try to add any sequence of `||robot:motor run||` blocs to create fun
88 | dance robot movements.
89 |
--------------------------------------------------------------------------------
/mkhex.sh:
--------------------------------------------------------------------------------
1 | set -e
2 |
3 | makecode -f size
4 | makecode -c mkc-keystudiominismartrobot.json -f size
5 | cp ./built/mbdal-binary.hex ./assets/keystudio-minismartrobot-for-microbit-v1.hex
6 | cp ./built/mbcodal-binary.hex ./assets/keystudio-minismartrobot-for-microbit-v2.hex
7 | makecode -c mkc-kittenbotrobotbit.json -f size
8 | cp ./built/mbdal-binary.hex ./assets/kittenbot-robotbit-for-microbit-v1.hex
9 | cp ./built/mbcodal-binary.hex ./assets/kittenbot-robotbit-for-microbit-v2.hex
10 | makecode -c mkc-elecfreakscutebot.json -f size
11 | cp ./built/mbdal-binary.hex ./assets/elecfreaks-cutebot-for-microbit-v1.hex
12 | cp ./built/mbcodal-binary.hex ./assets/elecfreaks-cutebot-for-microbit-v2.hex
13 | makecode -c mkc-elecfreakscutebotpro.json -f size
14 | cp ./built/mbdal-binary.hex ./assets/elecfreaks-cutebotpro-for-microbit-v1.hex
15 | cp ./built/mbcodal-binary.hex ./assets/elecfreaks-cutebotpro-for-microbit-v2.hex
16 | makecode -c mkc-yahboomtinybit.json -f size
17 | cp ./built/mbdal-binary.hex ./assets/yahboom-tinybit-for-microbit-v1.hex
18 | cp ./built/mbcodal-binary.hex ./assets/yahboom-tinybit-for-microbit-v2.hex
19 | makecode -c mkc-dfrobotmaqueen.json -f size
20 | cp ./built/mbdal-binary.hex ./assets/dfrobot-maqueen-for-microbit-v1.hex
21 | cp ./built/mbcodal-binary.hex ./assets/dfrobot-maqueen-for-microbit-v2.hex
22 | makecode -c mkc-dfrobotmaqueenplusv2.json -f size
23 | cp ./built/mbdal-binary.hex ./assets/dfrobot-maqueen-plus-for-microbit-v1.hex
24 | cp ./built/mbcodal-binary.hex ./assets/dfrobot-maqueen-plus-for-microbit-v2.hex
25 | makecode -c mkc-kittenbotminilfr.json -f size
26 | cp ./built/mbdal-binary.hex ./assets/kittenbot-minilfr-for-microbit-v1.hex
27 | cp ./built/mbcodal-binary.hex ./assets/kittenbot-minilfr-for-microbit-v2.hex
28 | makecode -c mkc-kittenbottabbybot.json -f size
29 | cp ./built/mbdal-binary.hex ./assets/kittenbot-tabbybot-for-microbit-v1.hex
30 | cp ./built/mbcodal-binary.hex ./assets/kittenbot-tabbybot-for-microbit-v2.hex
31 | makecode -c mkc-kittenbotnanobit.json -f size
32 | cp ./built/mbdal-binary.hex ./assets/kittenbot-nanobit-for-microbit-v1.hex
33 | cp ./built/mbcodal-binary.hex ./assets/kittenbot-nanobit-for-microbit-v2.hex
34 | makecode -c mkc-kitronikmotordriverrccar.json -f size
35 | cp ./built/mbdal-binary.hex ./assets/kitronik-motor-driver-rc-car-for-microbit-v1.hex
36 | cp ./built/mbcodal-binary.hex ./assets/kitronik-motor-driver-rc-car-for-microbit-v2.hex
37 | makecode -c mkc-inksmithk8.json -f size
38 | cp ./built/mbdal-binary.hex ./assets/inksmith-k8-for-microbit-v1.hex
39 | cp ./built/mbcodal-binary.hex ./assets/inksmith-k8-for-microbit-v2.hex
40 | cd fwdedu
41 | makecode -f size
42 | cd ..
43 | cp ./fwdedu/built/mbcodal-binary.hex ./assets/fwdedu-for-microbit-v2.hex
44 |
45 | cd protocol
46 | makecode
47 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd).
40 |
41 |
42 |
--------------------------------------------------------------------------------
/robots/tslmotionkit2.ts:
--------------------------------------------------------------------------------
1 | namespace robot {
2 | const I2C_ADRESS = 0x10
3 | const M1_INDEX = 0
4 | const M2_INDEX = 0x02
5 | const FORWARD = 0
6 | const BACKWARD = 1
7 | const PatrolLeft = 0
8 | const PatrolRight = 1
9 | const LINE_STATE_REGISTER = 0x1d
10 | const enum I2Cservos {
11 | S0 = 0x14,
12 | S1 = 0x15
13 | }
14 | let StatePuffer = 0
15 |
16 | function run(index: number, speed: number): void {
17 | const buf = pins.createBuffer(3)
18 | const direction = speed > 0 ? FORWARD : BACKWARD
19 | const s = Math.round(Math.map(Math.abs(speed), 0, 100, 0, 255))
20 | buf[0] = index
21 | buf[1] = direction
22 | buf[2] = s
23 | pins.i2cWriteBuffer(I2C_ADRESS, buf)
24 | }
25 |
26 | function readData(reg: number, len: number): Buffer {
27 | pins.i2cWriteNumber(I2C_ADRESS, reg, NumberFormat.UInt8BE);
28 | return pins.i2cReadBuffer(I2C_ADRESS, len, false);
29 | }
30 |
31 | function writeData(buf: number[]): void {
32 | pins.i2cWriteBuffer(I2C_ADRESS, pins.createBufferFromArray(buf));
33 | }
34 |
35 | class I2CLineDetector implements drivers.LineDetectors {
36 | start(): void { }
37 | lineState(state: number[]): void {
38 | const v = this.readPatrol()
39 | StatePuffer = (v & 0x01) == 0x01 ? 1023 : 0;
40 | state[RobotLineDetector.Left] = (StatePuffer << 0)
41 | StatePuffer = (v & 0x02) == 0x02 ? 1023 : 0;
42 | state[RobotLineDetector.Right] = (StatePuffer << 1)
43 | }
44 |
45 | private readPatrol() {
46 | return robots.i2cReadRegU8(I2C_ADRESS, LINE_STATE_REGISTER)
47 | }
48 | }
49 |
50 | class I2CSonar implements drivers.Sonar {
51 | start(): void { }
52 | distance(maxCmDistance: number): number {
53 | let integer = readData(0x28, 2);
54 | let distance = integer[0] << 8 | integer[1];
55 | return (distance > 399 || distance < 1) ? -1 : distance;
56 | }
57 | }
58 |
59 | class PwmArm implements drivers.Arm {
60 | constructor(public readonly servo: I2Cservos) { }
61 | start() { }
62 | open(aperture: number) {
63 | writeData([this.servo, aperture])
64 |
65 | }
66 | }
67 |
68 | class TSLMotionkitRobot extends robots.Robot {
69 | constructor() {
70 | super(0x325e1e40)
71 | this.lineDetectors = new I2CLineDetector()
72 | this.sonar = new I2CSonar()
73 | this.arms = [new PwmArm(I2Cservos.S0), new PwmArm(I2Cservos.S1)]
74 | }
75 |
76 | motorRun(left: number, right: number): void {
77 | run(M1_INDEX, left)
78 | run(M2_INDEX, right)
79 | }
80 |
81 | headlightsSetColor(r: number, g: number, b: number) {
82 | writeData([0x18, r]);
83 | writeData([0x19, g]);
84 | writeData([0x1A, b]);
85 | }
86 | }
87 |
88 | /**
89 | * TinySuperLab MotionKit V2
90 | */
91 | //% fixedInstance block="tinysuperlab motionkit v2" whenUsed weight=80
92 | export const tslMotionkit2 = new RobotDriver(new TSLMotionkitRobot())
93 | }
94 |
--------------------------------------------------------------------------------
/botsim/src/bots/elecfreakscutebotpro.ts:
--------------------------------------------------------------------------------
1 | import { convexHull } from "../sim/util"
2 | import { BotSpec, toWheels } from "./specs"
3 |
4 | const spec: BotSpec = {
5 | name: "Elecfreaks Cutebot PRO",
6 | productId: 0x31e95c0a,
7 | mass: 1,
8 | weight: 199,
9 | silkColor: "#0000F0",
10 | chassis: {
11 | shape: "polygon",
12 | texture: "bots/elecfreakscutebotpro/chassis.png",
13 | // The vertices below are an approximation of the hull shape, but this
14 | // is a concave polygon. The collidable geometry for this shape will be
15 | // a convex polygon wrapping these points.
16 | verts: convexHull([
17 | { x: 0.0, y: -6.5 },
18 | { x: 1.0, y: -6.4 },
19 | { x: 2.0, y: -6.3 },
20 | { x: 3.0, y: -6.1 },
21 | { x: 3.5, y: -5.75 },
22 | { x: 4.0, y: -5.0 },
23 | { x: 4.3, y: -4.0 },
24 | { x: 5.0, y: -2.66 },
25 | { x: 5.75, y: -1.33 },
26 | { x: 5.8, y: -1.0 },
27 | { x: 5.8, y: -0.4 },
28 | { x: 5.5, y: 0.0 },
29 | { x: 3.8, y: 0.0 },
30 | { x: 3.5, y: 0.4 },
31 | { x: 3.5, y: 5.6 },
32 | { x: 3.6, y: 5.8 },
33 | { x: 3.6, y: 6.1 },
34 | { x: 3.0, y: 6.45 },
35 | { x: 2.0, y: 6.8 },
36 | { x: 1.5, y: 6.9 },
37 | { x: 1.0, y: 6.7 },
38 | { x: 0.5, y: 6.6 },
39 | { x: 0.0, y: 6.6 },
40 | { x: -0.5, y: 6.6 },
41 | { x: -1.0, y: 6.7 },
42 | { x: -1.5, y: 6.9 },
43 | { x: -2.0, y: 6.8 },
44 | { x: -3.0, y: 6.45 },
45 | { x: -3.6, y: 6.1 },
46 | { x: -3.6, y: 5.8 },
47 | { x: -3.5, y: 5.6 },
48 | { x: -3.5, y: 0.4 },
49 | { x: -3.8, y: 0.0 },
50 | { x: -5.5, y: 0.0 },
51 | { x: -5.8, y: -0.4 },
52 | { x: -5.8, y: -1.0 },
53 | { x: -5.75, y: -1.33 },
54 | { x: -5.0, y: -2.66 },
55 | { x: -4.3, y: -4.0 },
56 | { x: -4.0, y: -5.0 },
57 | { x: -3.5, y: -5.75 },
58 | { x: -3.0, y: -6.1 },
59 | { x: -2.0, y: -6.3 },
60 | { x: -1.0, y: -6.4 },
61 | ]),
62 | },
63 | wheels: toWheels({
64 | separation: 7.4,
65 | diameter: 5.0,
66 | width: 1.8,
67 | y: 2.9,
68 | }),
69 | rangeSensor: {
70 | beamAngle: 25,
71 | maxRange: 40,
72 | pos: { x: 0, y: -5 },
73 | },
74 | lineSensors: [
75 | {
76 | name: "outer-left",
77 | pos: { x: -2.9, y: -2.4 },
78 | },
79 | {
80 | name: "left",
81 | pos: { x: -0.58, y: -2.4 },
82 | },
83 | {
84 | name: "right",
85 | pos: { x: 0.58, y: -2.4 },
86 | },
87 | {
88 | name: "outer-right",
89 | pos: { x: 2.9, y: -2.4 },
90 | },
91 | ],
92 | leds: [
93 | {
94 | name: "general",
95 | pos: { x: 0, y: 0 },
96 | radius: 0,
97 | },
98 | ],
99 | }
100 |
101 | export default spec
102 |
--------------------------------------------------------------------------------
/robots/kittenbotnanobit.ts:
--------------------------------------------------------------------------------
1 | namespace robot {
2 | // https://github.com/KittenBot/pxt-nanobit.git
3 |
4 | // TODO: there is no 5x5 led matrix on nanobit, possible add oled I2C display support?
5 |
6 | const PCA_9624: number = 0x0
7 |
8 | function pca9624Init() {
9 | let i2cBuffer = pins.createBuffer(2)
10 | let buf = pins.createBuffer(2)
11 | buf[0] = 0x0
12 | buf[1] = 0x01
13 | pins.i2cWriteBuffer(PCA_9624, buf)
14 | basic.pause(200)
15 |
16 | const val = robots.i2cReadRegU8(PCA_9624, 0x0)
17 |
18 | buf = pins.createBuffer(3)
19 | buf[0] = 0x0c | 0x80
20 | buf[1] = 0xaa
21 | buf[2] = 0xaa
22 | pins.i2cWriteBuffer(PCA_9624, buf)
23 | }
24 |
25 | function motorSet4(m1: number, m2: number, m3: number, m4: number) {
26 | let i2cBuffer = pins.createBuffer(9)
27 | i2cBuffer[0] = 0x82
28 | if (m1 > 0) {
29 | i2cBuffer[1] = 0x0
30 | i2cBuffer[2] = m1
31 | } else {
32 | i2cBuffer[1] = -m1
33 | i2cBuffer[2] = 0x0
34 | }
35 |
36 | if (m2 > 0) {
37 | i2cBuffer[3] = 0x0
38 | i2cBuffer[4] = m2
39 | } else {
40 | i2cBuffer[3] = -m2
41 | i2cBuffer[4] = 0x0
42 | }
43 |
44 | if (m3 > 0) {
45 | i2cBuffer[5] = 0x0
46 | i2cBuffer[6] = m3
47 | } else {
48 | i2cBuffer[5] = -m3
49 | i2cBuffer[6] = 0x0
50 | }
51 |
52 | if (m4 > 0) {
53 | i2cBuffer[7] = 0x0
54 | i2cBuffer[8] = m4
55 | } else {
56 | i2cBuffer[7] = -m4
57 | i2cBuffer[8] = 0x0
58 | }
59 |
60 | pins.i2cWriteBuffer(PCA_9624, i2cBuffer)
61 | }
62 |
63 | class KittenbotNanobitOminiRobot extends robots.Robot {
64 | constructor() {
65 | super(0x34e5f4f2)
66 | this.leds = new drivers.WS2812bLEDStrip(DigitalPin.P16, 2)
67 | this.arms = [new drivers.FixedServoArm(0, 90, AnalogPin.P1)]
68 | }
69 |
70 | start() {
71 | pca9624Init()
72 | motorSet4(0, 0, 0, 0)
73 | }
74 |
75 | motorRun(left: number, right: number): void {
76 | left *= 2.55
77 | right *= 2.55
78 | // synchronize motor on both side
79 | // the order of m1~m4
80 | // m4 m3
81 | // --
82 | // m1 m2
83 | if (left == -right) {
84 | const speed = Math.abs(left)
85 | // map spin command to horizontal move
86 | if (left < 0) {
87 | // horizontal move left
88 | motorSet4(-speed, -speed, speed, speed)
89 | } else {
90 | // horizontal move right
91 | motorSet4(speed, speed, -speed, -speed)
92 | }
93 | } else {
94 | motorSet4(-left, right, right, -left)
95 | }
96 | }
97 | }
98 |
99 | /**
100 | * Kittenbot Nanobit
101 | */
102 | //% fixedInstance block="kittenbot nanobit" whenUsed weight=80
103 | export const kittenbotNanobit = new RobotDriver(
104 | new KittenbotNanobitOminiRobot()
105 | )
106 | }
107 |
--------------------------------------------------------------------------------
/robots/kittenbottabbybot.ts:
--------------------------------------------------------------------------------
1 | namespace robot {
2 | // https://github.com/KittenBot/pxt-tabbyrobot
3 | const TABBY_ADDR = 0x16
4 | const REG_MOTOR = 0x02
5 | const REG_SERVO1 = 0x03
6 | const REG_SERVO2 = 0x04
7 | const REG_HEADLIGHT = 0x05
8 | const REG_BATTERY = 0x06
9 |
10 | enum Servos {
11 | S1 = 0x3,
12 | S2 = 0x4
13 | }
14 |
15 | class I2CArm implements drivers.Arm {
16 | constructor(public readonly servo: Servos) { }
17 | start() { }
18 | open(aperture: number) {
19 | if (aperture > 5) {
20 | aperture = 0
21 | } else {
22 | aperture = 90
23 | }
24 | let buf4 = pins.createBuffer(3)
25 | buf4[0] = this.servo
26 | let minPulse = 600
27 | let maxPulse = 2400
28 | let v_us = (aperture * (maxPulse - minPulse) / 180 + minPulse)
29 | buf4[1] = v_us & 0xff
30 | buf4[2] = v_us >> 8
31 | pins.i2cWriteBuffer(TABBY_ADDR, buf4)
32 | }
33 | }
34 |
35 |
36 | class KittenBotTabbyBot extends robots.Robot {
37 | constructor() {
38 | super(0)
39 | this.leds = new drivers.WS2812bLEDStrip(DigitalPin.P16, 2)
40 | this.sonar = new drivers.SR04Sonar(DigitalPin.P8, DigitalPin.P8)
41 | this.lineDetectors = new drivers.AnalogPinLineDetectors(AnalogPin.P2, AnalogPin.P1, true)
42 | this.lineHighThreshold = 600
43 | this.maxLineSpeed = 100
44 | this.commands[robot.robots.RobotCompactCommand.MotorRunForward] =
45 | {
46 | speed: 100,
47 | }
48 | this.commands[robot.robots.RobotCompactCommand.MotorTurnLeft] =
49 | {
50 | turnRatio: -65,
51 | speed: 100,
52 | }
53 | this.commands[robot.robots.RobotCompactCommand.MotorTurnRight] =
54 | {
55 | turnRatio: 65,
56 | speed: 100,
57 | }
58 | // this.lineDetectors = new drivers.DigitalPinLineDetectors(
59 | // DigitalPin.P2,
60 | // DigitalPin.P1,
61 | // false
62 | // )
63 | this.arms = [new I2CArm(Servos.S1), new I2CArm(Servos.S2)]
64 | this.reset()
65 |
66 | // this.headlight(50,50)
67 | }
68 |
69 | reset() {
70 | let buf = pins.createBuffer(1)
71 | buf[0] = 0x01
72 | pins.i2cWriteBuffer(TABBY_ADDR, buf)
73 | }
74 |
75 | motorRun(left: number, right: number): void {
76 | left = (0 - left)
77 | right = (0 - right)
78 | let buf = pins.createBuffer(5)
79 | // REG, M1A, M1B, M2A, M2B
80 | buf[0] = REG_MOTOR
81 | if (left >= 0) {
82 | buf[1] = left
83 | buf[2] = 0
84 | } else {
85 | buf[1] = 0
86 | buf[2] = -left
87 | }
88 |
89 | if (right >= 0) {
90 | buf[3] = right
91 | buf[4] = 0
92 | } else {
93 | buf[3] = 0
94 | buf[4] = -right
95 | }
96 | pins.i2cWriteBuffer(TABBY_ADDR, buf)
97 |
98 | }
99 |
100 | headlight(left: number, right: number): void {
101 | let buf = pins.createBuffer(3)
102 | buf[0] = REG_HEADLIGHT
103 | buf[1] = left
104 | buf[2] = right
105 | pins.i2cWriteBuffer(TABBY_ADDR, buf)
106 |
107 | }
108 |
109 |
110 |
111 | }
112 |
113 | /**
114 | * Kittenbot TabbyBot
115 | */
116 | //% fixedInstance whenUsed block="kittenbot tabbybot" whenUsed weight=80
117 | export const kittenbotTabbyBot = new RobotDriver(
118 | new KittenBotTabbyBot()
119 | )
120 | }
121 |
122 |
--------------------------------------------------------------------------------
/botsim/src/sim/bot/chassis.ts:
--------------------------------------------------------------------------------
1 | import { BotSpec, ChassisSpec } from "../../bots/specs"
2 | import {
3 | BoxShapeSpec,
4 | BrushSpec,
5 | CircleShapeSpec,
6 | EntityShapeSpec,
7 | PolygonShapeSpec,
8 | defaultBoxShape,
9 | defaultCircleShape,
10 | defaultColorBrush,
11 | defaultTextureBrush,
12 | defaultEntityShape,
13 | defaultPolygonShape,
14 | defaultShapePhysics,
15 | } from "../specs"
16 | import { Bot } from "."
17 | import {
18 | hslToRgb,
19 | numberToRgb,
20 | rgbToFloatArray,
21 | rgbToHsl,
22 | rgbToString,
23 | } from "../util"
24 | import { createGraphics } from "../renderer"
25 | import { clamp } from "../../util"
26 | import * as Pixi from "pixi.js"
27 |
28 | export class Chassis {
29 | public static makeShapeSpec(botSpec: BotSpec): EntityShapeSpec {
30 | const chassisSpec = botSpec.chassis
31 | const chassisColorBrush: BrushSpec = {
32 | ...defaultColorBrush(),
33 | fillColor: "#11B5E4" + "C0",
34 | borderColor: "#555555",
35 | zIndex: 5,
36 | }
37 | const chassisTextureBrush: BrushSpec = {
38 | ...defaultTextureBrush(),
39 | texture: chassisSpec.texture!,
40 | color: "#FFFFFF",
41 | alpha: 0.75,
42 | zIndex: 5,
43 | }
44 |
45 | let chassisShape: CircleShapeSpec | BoxShapeSpec | PolygonShapeSpec
46 | switch (chassisSpec.shape) {
47 | case "circle":
48 | chassisShape = {
49 | ...defaultCircleShape(),
50 | radius: chassisSpec.radius,
51 | }
52 | break
53 | case "box":
54 | chassisShape = {
55 | ...defaultBoxShape(),
56 | size: chassisSpec.size,
57 | }
58 | break
59 | case "polygon":
60 | chassisShape = {
61 | ...defaultPolygonShape(),
62 | verts: chassisSpec.verts,
63 | }
64 | break
65 | default:
66 | throw new Error("Unknown chassis type")
67 | }
68 | return {
69 | ...defaultEntityShape(),
70 | ...chassisShape,
71 | label: "chassis",
72 | roles: ["mouse-target", "robot"],
73 | brush: chassisSpec.texture
74 | ? chassisTextureBrush
75 | : chassisColorBrush,
76 | physics: {
77 | ...defaultShapePhysics(),
78 | friction: 0.3,
79 | restitution: 0.9,
80 | density: 0.1,
81 | },
82 | }
83 | }
84 |
85 | public get spec() {
86 | return this._spec
87 | }
88 |
89 | private cachedColor: number = -2
90 |
91 | constructor(
92 | private bot: Bot,
93 | private _spec: ChassisSpec
94 | ) {}
95 |
96 | public destroy() {}
97 |
98 | public update(dtSecs: number) {}
99 |
100 | public setColor(color: number) {
101 | // TODO: Add API to brush to change color
102 |
103 | if (this.cachedColor === color) return
104 | this.cachedColor = color
105 | let rgb = numberToRgb(color)
106 | // Brighten the color a little bit
107 | let hsl = rgbToHsl(rgb)
108 | hsl.l = clamp(hsl.l * 1.2, 0, 255)
109 | rgb = hslToRgb(hsl)
110 | // This is the worst way possible to change the color. works for now. don't do this.
111 | const renderShape = this.bot.entity.renderObj.shapes.get("chassis")
112 | const newSpec = Chassis.makeShapeSpec(this.bot.spec)
113 | const newBrush = newSpec.brush
114 | if (newBrush.type === "color") {
115 | const c = rgbToString(rgb)
116 | const alpha = "99"
117 | newBrush.fillColor = c + alpha
118 | const newGfx = createGraphics[newSpec.type][newSpec.brush.type](
119 | newSpec,
120 | newSpec.brush
121 | )
122 | renderShape?.setGfx(newGfx)
123 | } else if (newBrush.type === "texture") {
124 | if ((renderShape?.gfx as any).shader) {
125 | const shader = (renderShape?.gfx as any).shader as Pixi.Shader
126 | shader.uniforms.uColor = rgbToFloatArray(rgb)
127 | }
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/botsim/scripts/img2hull.js:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 |
3 | // Computes the convex hull of the given image.
4 |
5 | const commander = require("commander")
6 | const getPixels = require("get-pixels")
7 | const simplify = require("simplify-js")
8 |
9 | commander
10 | .usage("[options]")
11 | .option("-i, --image ", "Input image path or URL")
12 | .option("-s, --scale ", "Scale factor", parseFloat, 1)
13 | .parse()
14 |
15 | const options = commander.opts()
16 |
17 | const imagePath = options.image
18 | const scale = options.scale
19 |
20 | if (!imagePath) {
21 | console.error("Input image not specified")
22 | commander.help()
23 | process.exit(1)
24 | }
25 |
26 | console.log(`Reading image from ${imagePath}...`)
27 | getPixels(imagePath, (err, pixels) => {
28 | if (err) {
29 | console.error(err)
30 | process.exit(1)
31 | }
32 |
33 | console.warn("Computing hull...")
34 | const imgWidth = pixels.shape[0]
35 | const imgHalfWidth = imgWidth >> 1
36 | const imgHeight = pixels.shape[1]
37 | const imgHalfHeight = imgHeight >> 1
38 | console.warn(`Image size: ${imgWidth}x${imgHeight}`)
39 | console.warn(`Scale: ${scale}`)
40 |
41 | // Find the first and last non-transparent pixel in each row
42 | console.warn("Finding markers...")
43 | const markers = []
44 | for (let y = 0; y < imgHeight; y++) {
45 | let first = -1,
46 | last = -1
47 | for (let x = 0; x < imgWidth; x++) {
48 | const a = pixels.get(x, y, 3)
49 | if (a > 0) {
50 | if (first < 0) first = x
51 | last = x
52 | }
53 | }
54 | if (first < 0 && last > 0) first = last
55 | if (first > 0 && last < 0) last = first
56 | if (first < 0 && last < 0) continue
57 | markers.push([first, last])
58 | }
59 |
60 | if (!markers.length) {
61 | console.error("No markers found")
62 | process.exit(1)
63 | }
64 |
65 | // Convert markers to polygon vertices
66 | const verts = []
67 | for (let y = 0; y < markers.length; y++) {
68 | const [first, last] = markers[y]
69 | verts.push({ x: first, y })
70 | }
71 | for (let y = markers.length - 1; y >= 0; y--) {
72 | const [first, last] = markers[y]
73 | verts.push({ x: last, y })
74 | }
75 | console.warn(`\t${verts.length} vertices`)
76 |
77 | // Get the convex hull
78 | console.warn("Computing convex hull...")
79 | const hull = convexHull(verts)
80 | console.warn(`\t${hull.length} vertices`)
81 | //console.warn(JSON.stringify(hull))
82 |
83 | // Simplify the polygon
84 | console.warn("Simplifying hull...")
85 | const tolerance = 5
86 | const simplified = simplify(hull, tolerance, false)
87 | console.warn(`\t${simplified.length} vertices`)
88 | //console.warn(JSON.stringify(simplified))
89 |
90 | // Scale the hull
91 | console.warn("Scaling hull...")
92 | const scaledHull = simplified.map(({ x, y }) => ({
93 | x: (scale * (x - imgHalfWidth)) / imgWidth,
94 | y: (scale * (y - imgHalfHeight)) / imgHeight,
95 | }))
96 | console.log(JSON.stringify(scaledHull))
97 |
98 | console.warn("Done.")
99 | })
100 |
101 | const Orientation = {
102 | Collinear: 0,
103 | Clockwise: 1,
104 | CounterClockwise: 2,
105 | }
106 |
107 | function orientation(p, q, r) {
108 | const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y)
109 |
110 | if (val == 0) return Orientation.Collinear
111 | return val > 0 ? Orientation.Clockwise : Orientation.CounterClockwise
112 | }
113 |
114 | function convexHull(verts) {
115 | const n = verts.length
116 | if (n < 3) return []
117 |
118 | const hull = []
119 |
120 | let iLeftmost = 0
121 | for (let i = 1; i < n; i++) {
122 | if (verts[i].x < verts[iLeftmost].x) iLeftmost = i
123 | }
124 |
125 | let p = iLeftmost,
126 | q = 0
127 | do {
128 | hull.push(verts[p])
129 | q = (p + 1) % n
130 | for (let i = 0; i < n; i++) {
131 | if (
132 | orientation(verts[p], verts[i], verts[q]) ===
133 | Orientation.CounterClockwise
134 | ) {
135 | q = i
136 | }
137 | }
138 | p = q
139 | } while (p != iLeftmost)
140 |
141 | return hull
142 | }
143 |
--------------------------------------------------------------------------------
/robots/robot.ts:
--------------------------------------------------------------------------------
1 | namespace robot.robots {
2 | export class Robot {
3 | /**
4 | * Maximum speed while following a line with line assist
5 | */
6 | maxLineSpeed = 40
7 | /**
8 | * Threshold to saturate a speed to 0. Avoids small speed jitter near stop state.
9 | */
10 | stopThreshold = 2
11 | /**
12 | * Threshold to converge to the target speed, and avoid exponential convergence.
13 | */
14 | targetSpeedThreshold = 4
15 | /**
16 | * Exponential moving average factor for speed transitions, accelerating
17 | */
18 | speedTransitionAlpha = 0.97
19 | /**
20 | * Exponential moving average factor for speed transitions, braking
21 | */
22 | speedBrakeTransitionAlpha = 0.8
23 | /**
24 | * Threshold to converge the turn ratio, and avoid exponential convergence.
25 | */
26 | targetTurnRatioThreshold = 20
27 | /**
28 | * Exponential moving average factor for turn ratio transitions
29 | */
30 | turnRatioTransitionAlpha = 0.2
31 | /**
32 | * Minimum reading from ultrasonic sensor to be considered valid
33 | */
34 | sonarMinReading = 2
35 | /**
36 | * Number of iteration before the line is considered lost and line assist
37 | * disengages
38 | */
39 | lineLostThreshold = 72
40 | /**
41 | * Minimum value to consider a line detected
42 | */
43 | lineHighThreshold = 200
44 | /**
45 | * LED configuration
46 | */
47 | leds?: drivers.LEDStrip
48 | /**
49 | * Distance sensor configuration, if SR04
50 | */
51 | sonar?: drivers.Sonar
52 | /**
53 | * Line detector configuration
54 | */
55 | lineDetectors?: drivers.LineDetectors
56 | /**
57 | * Robotic arm configuration
58 | */
59 | arms?: drivers.Arm[]
60 | /**
61 | * A map from microcode command to speed, turn ratio values
62 | */
63 | commands: {
64 | [index: number]: { speed?: number; turnRatio?: number }
65 | } = {}
66 |
67 | /**
68 | * Constructs a new robot instance and loads configuration.
69 | * Do not start physical services like i2c, pins in the ctor, use start instead.
70 | */
71 | constructor(public readonly productId: number) {
72 | this.commands[
73 | robot.robots.RobotCompactCommand.MotorRunForward
74 | ] = {
75 | speed: 70,
76 | }
77 | this.commands[
78 | robot.robots.RobotCompactCommand.MotorRunForwardFast
79 | ] = {
80 | speed: 100,
81 | }
82 | this.commands[
83 | robot.robots.RobotCompactCommand.MotorRunBackward
84 | ] = {
85 | speed: -60,
86 | }
87 | this.commands[robot.robots.RobotCompactCommand.MotorTurnLeft] =
88 | {
89 | turnRatio: -50,
90 | speed: 70,
91 | }
92 | this.commands[robot.robots.RobotCompactCommand.MotorTurnRight] =
93 | {
94 | turnRatio: 50,
95 | speed: 70,
96 | }
97 | this.commands[robot.robots.RobotCompactCommand.MotorSpinLeft] =
98 | {
99 | turnRatio: -200,
100 | speed: 60,
101 | }
102 | this.commands[robot.robots.RobotCompactCommand.MotorSpinRight] =
103 | {
104 | turnRatio: 200,
105 | speed: 60,
106 | }
107 | }
108 |
109 | /**
110 | * Starts the robot drivers, including i2c drivers, etc...
111 | */
112 | start() {}
113 |
114 | /*
115 | Makes the robot move at % `speed` ([-100, 100]). Negative goes backward, 0 stops.
116 | */
117 | motorRun(left: number, right: number): void {}
118 |
119 | /**
120 | * Optional: sets the color on the LED array as a 24bit RGB color
121 | */
122 | headlightsSetColor(red: number, green: number, blue: number): void {}
123 |
124 | /**
125 | * Optional: reads the sonar, in cm.
126 | * @returns distance in cm; negative number if unsupported
127 | */
128 | ultrasonicDistance(maxCmDistance: number): number {
129 | return -1
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/robots/knotechcallibot.ts:
--------------------------------------------------------------------------------
1 | namespace robot {
2 | // Knotech Callibot 1&2
3 | const I2C_ADRESS = 0x20
4 | let c2Initialized = 0;
5 | let c2IsBot2 = 0;
6 | let c2LedState = 0;
7 |
8 | const enum I2Cservos {
9 | S0 = 0x14,
10 | S1 = 0x15
11 | }
12 | let StatePuffer = 0
13 |
14 | export function init() {
15 | if (c2Initialized != 1) {
16 | c2Initialized = 1;
17 | let buffer = pins.i2cReadBuffer(0x21, 1);
18 | if ((buffer[0] & 0x80) != 0) { // Check if it's a CalliBot2
19 | c2IsBot2 = 1;
20 | // setRgbLed(C2RgbLed.All, 0, 0, 0);
21 | }
22 | else {
23 | // setRgbLed1(C2RgbLed.All, 0, 0)
24 | }
25 | writeMotor(2, 0); //beide Motoren Stopp
26 | }
27 | return c2IsBot2
28 | }
29 |
30 | function writeMotor(nr: number, speed: number) {
31 | let direction = 0 //vorwärts
32 | init()
33 | if (speed < 0) { direction = 1 } //rückwärts
34 | switch (nr) {
35 | case 0: //links
36 | pins.i2cWriteBuffer(0x20, Buffer.fromArray([0x00, direction, Math.abs(speed)]))
37 | break
38 | case 2: //beide
39 | pins.i2cWriteBuffer(0x20, Buffer.fromArray([0x00, direction, Math.abs(speed), direction, Math.abs(speed)]))
40 | break
41 | case 1: //rechts
42 | pins.i2cWriteBuffer(0x20, Buffer.fromArray([0x02, direction, Math.abs(speed)]))
43 | break
44 | }
45 | }
46 |
47 | function readData(reg: number, len: number): Buffer {
48 | pins.i2cWriteNumber(I2C_ADRESS, reg, NumberFormat.UInt8BE);
49 | return pins.i2cReadBuffer(I2C_ADRESS, len, false);
50 | }
51 |
52 | function writeData(buf: number[]): void {
53 | pins.i2cWriteBuffer(I2C_ADRESS, pins.createBufferFromArray(buf));
54 | }
55 |
56 | class I2CLineDetector implements drivers.LineDetectors {
57 | start(): void { }
58 | lineState(state: number[]): void {
59 | const v = this.readPatrol()
60 | let toggle = v
61 | if (v == 3) { toggle = 0 } // Beim Callibot muss hell und dunkel
62 | if (v == 0) { toggle = 3 } // getauscht werden
63 | StatePuffer = (toggle & 0x02) == 0x02 ? 1023 : 0;
64 | state[RobotLineDetector.Right] = (StatePuffer << 0)
65 | StatePuffer = (toggle & 0x01) == 0x01 ? 1023 : 0;
66 | state[RobotLineDetector.Left] = (StatePuffer << 1)
67 | }
68 |
69 | private readPatrol() {
70 | return robots.i2cReadRegU8(0x21, 1)
71 | }
72 | }
73 |
74 | class I2CSonar implements drivers.Sonar {
75 | start(): void { }
76 | distance(maxCmDistance: number): number {
77 | let buffer = pins.i2cReadBuffer(0x21, 3)
78 | let distance = (256 * buffer[1] + buffer[2]) / 10
79 | return (distance > 399 || distance < 1) ? -1 : distance;
80 | }
81 | }
82 |
83 | class PwmArm implements drivers.Arm {
84 | constructor(public readonly servo: I2Cservos) { }
85 | start() { }
86 | open(aperture: number) {
87 | writeData([this.servo, aperture])
88 | }
89 | }
90 |
91 |
92 | class KnotechCallibotRobot extends robots.Robot {
93 | constructor() {
94 | super(0x325e1e40)
95 | this.lineDetectors = new I2CLineDetector()
96 | this.sonar = new I2CSonar()
97 | this.arms = [new PwmArm(I2Cservos.S0), new PwmArm(I2Cservos.S1)]
98 | }
99 |
100 | motorRun(left: number, right: number): void {
101 | writeMotor(0, left);
102 | writeMotor(1, right);
103 | }
104 |
105 | headlightsSetColor(r: number, g: number, b: number) {
106 | let index = 0;
107 | let buffer = pins.createBuffer(5)
108 | init()
109 | if (c2IsBot2 = 1) {
110 | for (index = 1; index < 5; index++) {
111 | buffer[0] = 0x03;
112 | buffer[1] = index;
113 | buffer[2] = r;
114 | buffer[3] = g;
115 | buffer[4] = b;
116 | pins.i2cWriteBuffer(0x22, buffer);
117 | }
118 | }
119 | else { basic.showString("RGB LEDs are V2 only!") }
120 | }
121 | }
122 |
123 | /**
124 | * Knotech Callibot 1 & 2
125 | */
126 | //% fixedInstance block="Knotech Callibot" whenUsed weight=80
127 | export const knotechcallibot = new RobotDriver(new KnotechCallibotRobot())
128 | }
--------------------------------------------------------------------------------
/radio.ts:
--------------------------------------------------------------------------------
1 | namespace robot {
2 | function decodeRobotCompactCommand(driver: RobotDriver, msg: number) {
3 | if (
4 | msg >= robot.robots.RobotCompactCommand.Command &&
5 | msg <= robot.robots.RobotCompactCommand.CommandLast
6 | ) {
7 | driver.playTone(440, 40)
8 | }
9 | switch (msg) {
10 | case robot.robots.RobotCompactCommand.MotorStop:
11 | case robot.robots.RobotCompactCommand.MotorTurnLeft:
12 | case robot.robots.RobotCompactCommand.MotorTurnRight:
13 | case robot.robots.RobotCompactCommand.MotorSpinLeft:
14 | case robot.robots.RobotCompactCommand.MotorSpinRight:
15 | case robot.robots.RobotCompactCommand.MotorRunForwardFast:
16 | case robot.robots.RobotCompactCommand.MotorRunForward:
17 | case robot.robots.RobotCompactCommand.MotorRunBackward: {
18 | const command = driver.robot.commands[msg] || {}
19 | const turnRatio = command.turnRatio || 0
20 | const speed = command.speed || 0
21 | driver.setAssist(RobotAssist.LineFollowing, msg !== robot.robots.RobotCompactCommand.MotorRunForwardFast)
22 | driver.motorSteer(turnRatio, speed)
23 | break
24 | }
25 | case robot.robots.RobotCompactCommand.LEDRed:
26 | driver.setColor(0xff0000)
27 | break
28 | case robot.robots.RobotCompactCommand.LEDGreen:
29 | driver.setColor(0x00ff00)
30 | break
31 | case robot.robots.RobotCompactCommand.LEDBlue:
32 | driver.setColor(0x0000ff)
33 | break
34 | case robot.robots.RobotCompactCommand.LEDOff:
35 | driver.setColor(0x00000)
36 | break
37 | case robot.robots.RobotCompactCommand.ArmOpen: {
38 | const n = driver.armsLength
39 | for (let i = 0; i < driver.armsLength; ++i)
40 | driver.armOpen(i, 100)
41 | break
42 | }
43 | case robot.robots.RobotCompactCommand.ArmClose: {
44 | const n = driver.armsLength
45 | for (let i = 0; i < driver.armsLength; ++i)
46 | driver.armOpen(i , 0)
47 | break
48 | }
49 | }
50 | }
51 |
52 | /**
53 | * Starts the reception and transmission of compact robot command messages (see protocol).
54 | */
55 | export function startCompactRadio() {
56 | const d = RobotDriver.instance()
57 | radio.setGroup(d.radioGroup)
58 | radio.onReceivedNumber(code => decodeRobotCompactCommand(d, code))
59 | d.useRadio = true
60 |
61 | handleLineDetected()
62 | }
63 |
64 | function handleLineDetected() {
65 | const d = RobotDriver.instance()
66 | let lost = false
67 | let prev: number[] = []
68 | messages.onEvent(messages.RobotEvents.LineAny, robots.RobotCompactCommand.LineAnyState, () => {
69 | const robot = d.robot
70 | const threshold = robot.lineHighThreshold
71 | const current = d.currentLineState
72 |
73 | if (!lost && current.length === prev.length && current.every((v,i) => prev[i] === v))
74 | return; // unchanged
75 |
76 | // TODO refactor this out
77 | // left, right, middle
78 | let msg = robots.RobotCompactCommand.LineState
79 | if (current[RobotLineDetector.Middle] >= threshold)
80 | msg |= robots.RobotLineState.Left | robots.RobotLineState.Right
81 | else {
82 | if (current[RobotLineDetector.Left] >= threshold)
83 | msg |= robots.RobotLineState.Left
84 | if (current[RobotLineDetector.Right] >= threshold)
85 | msg |= robots.RobotLineState.Right
86 | }
87 | // line lost
88 | lost = false
89 | if (current.every(v => v < threshold)) {
90 | if (prev[RobotLineDetector.Left] >= threshold) {
91 | msg = robots.RobotCompactCommand.LineLostLeft
92 | lost = true
93 | }
94 | else if (prev[RobotLineDetector.Right] >= threshold) {
95 | msg = robots.RobotCompactCommand.LineLostRight
96 | lost = true
97 | }
98 | }
99 | sendCompactCommand(msg)
100 | prev = current
101 | })
102 | }
103 |
104 | //% shim=TD_NOOP
105 | function nativeSendNumber(msg: number) {
106 | radio.sendNumber(msg)
107 | }
108 |
109 | function sendCompactCommand(cmd: robot.robots.RobotCompactCommand) {
110 | radio.sendNumber(cmd)
111 | nativeSendNumber(cmd)
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/robots/keystudiominismartrobot.ts:
--------------------------------------------------------------------------------
1 | namespace robot {
2 | // https://github.com/mworkfun/pxt-k-bit/blob/master/main.ts
3 | const PCA9685_ADDRESS = 0x43 //device address
4 | const MODE1 = 0x00
5 | const MODE2 = 0x01
6 | const SUBADR1 = 0x02
7 | const SUBADR2 = 0x03
8 | const SUBADR3 = 0x04
9 | const PRESCALE = 0xfe
10 | const LED0_ON_L = 0x06
11 | const LED0_ON_H = 0x07
12 | const LED0_OFF_L = 0x08
13 | const LED0_OFF_H = 0x09
14 | const ALL_LED_ON_L = 0xfa
15 | const ALL_LED_ON_H = 0xfb
16 | const ALL_LED_OFF_L = 0xfc
17 | const ALL_LED_OFF_H = 0xfd
18 | const TRIG_PIN = DigitalPin.P14
19 | const ECHO_PIN = DigitalPin.P15
20 |
21 | function i2cRead(reg: number) {
22 | return robots.i2cReadRegU8(PCA9685_ADDRESS, reg)
23 | }
24 |
25 | function i2cWrite(reg: number, value: number) {
26 | let buf = pins.createBuffer(2)
27 | buf[0] = reg
28 | buf[1] = value
29 | pins.i2cWriteBuffer(PCA9685_ADDRESS, buf)
30 | }
31 |
32 | function setFreq(freq: number): void {
33 | // Constrain the frequency
34 | let prescaleval = 25000000
35 | prescaleval /= 4096
36 | prescaleval /= freq
37 | prescaleval -= 1
38 | let prescale = prescaleval //Math.Floor(prescaleval + 0.5);
39 | let oldmode = i2cRead(MODE1)
40 | let newmode = (oldmode & 0x7f) | 0x10 // sleep
41 | i2cWrite(MODE1, newmode) // go to sleep
42 | i2cWrite(PRESCALE, prescale) // set the prescaler
43 | i2cWrite(MODE1, oldmode)
44 | control.waitMicros(5000)
45 | i2cWrite(MODE1, oldmode | 0xa1)
46 | }
47 |
48 | function setPwm(channel: number, on: number, off: number): void {
49 | const buf = pins.createBuffer(5)
50 | buf[0] = LED0_ON_L + channel >> 2
51 | buf[1] = on & 0xff
52 | buf[2] = (on >> 8) & 0xff
53 | buf[3] = off & 0xff
54 | buf[4] = (off >> 8) & 0xff
55 | pins.i2cWriteBuffer(PCA9685_ADDRESS, buf)
56 | }
57 |
58 | const enum MotorObs {
59 | LeftSide = 0,
60 | RightSide = 1,
61 | }
62 | const enum MotorDir {
63 | Forward = 0,
64 | Back = 1,
65 | }
66 |
67 | function Motor(M: MotorObs, MD: MotorDir, speed: number) {
68 | const speed_value = Math.map(speed, 0, 100, 0, 4095)
69 | if (M == 0 && MD == 0) {
70 | setPwm(1, 0, speed_value) //control speed : 0---4095
71 | setPwm(0, 0, 0)
72 | }
73 | if (M == 0 && MD == 1) {
74 | setPwm(1, 0, speed_value) //control speed : 0---4095
75 | setPwm(0, 0, 4095)
76 | }
77 |
78 | if (M == 1 && MD == 0) {
79 | setPwm(3, 0, speed_value) //control speed : 0---4095
80 | setPwm(2, 0, 0)
81 | }
82 | if (M == 1 && MD == 1) {
83 | setPwm(3, 0, speed_value) //control speed : 0---4095
84 | setPwm(2, 0, 4095)
85 | }
86 | }
87 |
88 | class KeyStudioMiniSmartRobot extends robots.Robot {
89 | constructor() {
90 | super(0x3c767e02)
91 | this.sonar = new drivers.SR04Sonar(ECHO_PIN, TRIG_PIN)
92 | this.lineDetectors = new drivers.DigitalPinLineDetectors(
93 | DigitalPin.P13,
94 | DigitalPin.P14,
95 | true
96 | )
97 | }
98 |
99 | start() {
100 | this.init_PCA9685()
101 | }
102 |
103 | motorRun(left: number, right: number) {
104 | Motor(
105 | MotorObs.LeftSide,
106 | left > 0 ? MotorDir.Forward : MotorDir.Back,
107 | Math.abs(left)
108 | )
109 | Motor(
110 | MotorObs.RightSide,
111 | right > 0 ? MotorDir.Forward : MotorDir.Back,
112 | Math.abs(right)
113 | )
114 | }
115 |
116 | headlightsSetColor(red: number, green: number, blue: number) {
117 | const L_brightness = 0 //control the rgb-led brightness
118 | const R = Math.map(red, 0, 255, 4095, L_brightness)
119 | const G = Math.map(green, 0, 255, 4095, L_brightness)
120 | const B = Math.map(blue, 0, 255, 4095, L_brightness)
121 |
122 | setPwm(6, 0, R)
123 | setPwm(5, 0, G)
124 | setPwm(4, 0, B)
125 | }
126 |
127 | private init_PCA9685(): void {
128 | i2cWrite(MODE1, 0x00) //initialize the mode register 1
129 | setFreq(50) //20ms
130 | for (let idx = 0; idx < 16; idx++) {
131 | setPwm(idx, 0, 0)
132 | }
133 | }
134 | }
135 |
136 | /**
137 | * Mini Smart Robot from KeyStudio
138 | */
139 | //% fixedInstance whenUsed block="keystudio mini smart robot" weight=10
140 | export const keyStudioMiniSmartRobot = new RobotDriver(
141 | new KeyStudioMiniSmartRobot()
142 | )
143 | }
144 |
--------------------------------------------------------------------------------
/robots/kittenbotrobotbit.ts:
--------------------------------------------------------------------------------
1 | namespace robot {
2 | // https://github.com/KittenBot/pxt-robotbit/blob/master/main.ts
3 | const PCA9685_ADDRESS = 0x40
4 | const MODE1 = 0x00
5 | const MODE2 = 0x01
6 | const SUBADR1 = 0x02
7 | const SUBADR2 = 0x03
8 | const SUBADR3 = 0x04
9 | const PRESCALE = 0xfe
10 | const LED0_ON_L = 0x06
11 | const LED0_ON_H = 0x07
12 | const LED0_OFF_L = 0x08
13 | const LED0_OFF_H = 0x09
14 | const ALL_LED_ON_L = 0xfa
15 | const ALL_LED_ON_H = 0xfb
16 | const ALL_LED_OFF_L = 0xfc
17 | const ALL_LED_OFF_H = 0xfd
18 |
19 | const STP_CHA_L = 2047
20 | const STP_CHA_H = 4095
21 |
22 | const STP_CHB_L = 1
23 | const STP_CHB_H = 2047
24 |
25 | const STP_CHC_L = 1023
26 | const STP_CHC_H = 3071
27 |
28 | const STP_CHD_L = 3071
29 | const STP_CHD_H = 1023
30 |
31 | const enum Servos {
32 | S1 = 0x01,
33 | S2 = 0x02,
34 | S3 = 0x03,
35 | S4 = 0x04,
36 | S5 = 0x05,
37 | S6 = 0x06,
38 | S7 = 0x07,
39 | S8 = 0x08,
40 | }
41 |
42 | const enum Motors {
43 | M1A = 0x1,
44 | M1B = 0x2,
45 | M2A = 0x3,
46 | M2B = 0x4,
47 | }
48 |
49 | function i2cwrite(addr: number, reg: number, value: number) {
50 | const buf = pins.createBuffer(2)
51 | buf[0] = reg
52 | buf[1] = value
53 | pins.i2cWriteBuffer(addr, buf)
54 | }
55 |
56 | function i2cread(addr: number, reg: number) {
57 | const req = pins.createBuffer(1)
58 | req[0] = reg
59 | pins.i2cWriteBuffer(addr, req)
60 | const resp = pins.i2cReadBuffer(addr, 1)
61 | return resp[0]
62 | }
63 |
64 | function setFreq(): void {
65 | const oldmode = i2cread(PCA9685_ADDRESS, MODE1)
66 | const newmode = (oldmode & 0x7f) | 0x10 // sleep
67 | i2cwrite(PCA9685_ADDRESS, MODE1, newmode) // go to sleep
68 | i2cwrite(PCA9685_ADDRESS, PRESCALE, 121) // set the prescaler
69 | i2cwrite(PCA9685_ADDRESS, MODE1, oldmode)
70 | control.waitMicros(5000)
71 | i2cwrite(PCA9685_ADDRESS, MODE1, oldmode | 0xa1)
72 | }
73 |
74 | function initPCA9685(): void {
75 | i2cwrite(PCA9685_ADDRESS, MODE1, 0x00)
76 | setFreq()
77 | for (let idx = 0; idx < 16; idx++) {
78 | setPwm(idx, 0, 0)
79 | }
80 | }
81 |
82 | function setPwm(channel: number, on: number, off: number): void {
83 | if (channel < 0 || channel > 15) return
84 |
85 | const buf = pins.createBuffer(5)
86 | buf[0] = LED0_ON_L + 4 * channel
87 | buf[1] = on & 0xff
88 | buf[2] = (on >> 8) & 0xff
89 | buf[3] = off & 0xff
90 | buf[4] = (off >> 8) & 0xff
91 | pins.i2cWriteBuffer(PCA9685_ADDRESS, buf)
92 | }
93 |
94 | function MotorRun(index: Motors, speed: number): void {
95 | // speed = [-100, 100] x 41
96 | speed = Math.clamp(-4095, 4095, speed * 41)
97 | if (index > 4 || index <= 0) return
98 | let pp = (index - 1) * 2
99 | let pn = pp + 1
100 | if (speed >= 0) {
101 | setPwm(pp, 0, speed)
102 | setPwm(pn, 0, 0)
103 | } else {
104 | setPwm(pp, 0, 0)
105 | setPwm(pn, 0, -speed)
106 | }
107 | }
108 |
109 | function setServoAngle(index: Servos, degree: number): void {
110 | // 50hz: 20,000 us
111 | const v_us = (degree * 1800) / 180 + 600 // 0.6 ~ 2.4
112 | const value = (v_us * 4096) / 20000
113 | setPwm(index + 7, 0, value)
114 | }
115 |
116 | class PwmArm implements drivers.Arm {
117 | constructor(public readonly servo: Servos) {}
118 | start() {}
119 | open(aperture: number) {
120 | setServoAngle(this.servo, aperture > 50 ? 0 : 90)
121 | }
122 | }
123 |
124 | class KittenbotRobotbitRobot extends robots.Robot {
125 | constructor() {
126 | super(0x3dd2ed30)
127 | this.leds = new drivers.WS2812bLEDStrip(DigitalPin.P16, 4)
128 | this.sonar = new drivers.SR04Sonar(DigitalPin.P15, DigitalPin.P15)
129 | this.lineDetectors = new drivers.DigitalPinLineDetectors(
130 | DigitalPin.P1,
131 | DigitalPin.P2,
132 | false
133 | )
134 | this.maxLineSpeed = 150
135 | // this.arms = [new PwmArm(Servos.S1)] // remove to fit in V1 space
136 | }
137 |
138 | start() {
139 | initPCA9685()
140 | }
141 |
142 | motorRun(left: number, right: number): void {
143 | MotorRun(Motors.M1A, left)
144 | MotorRun(Motors.M1B, -right)
145 | }
146 | }
147 |
148 | /**
149 | * Kittenbot robotbit
150 | */
151 | //% fixedInstance whenUsed block="Kittenbot robotbit"
152 | export const kittenbotRobotbit = new RobotDriver(
153 | new KittenbotRobotbitRobot()
154 | )
155 | }
156 |
--------------------------------------------------------------------------------
/robots/yahboomtinybit.ts:
--------------------------------------------------------------------------------
1 | namespace robot {
2 | // https://github.com/YahboomTechnology/Tiny-bitLib/blob/master/main.ts
3 | const PWM_ADD = 0x01
4 | const MOTOR = 0x02
5 | const RGB = 0x01
6 |
7 | function setPwmRGB(red: number, green: number, blue: number): void {
8 | const buf = pins.createBuffer(4)
9 | buf[0] = RGB
10 | buf[1] = red
11 | buf[2] = green
12 | buf[3] = blue
13 | pins.i2cWriteBuffer(PWM_ADD, buf)
14 | }
15 |
16 | class YahboomTinybitRobot extends robots.Robot {
17 | constructor() {
18 | super(0x345f8369)
19 | this.sonar = new drivers.SR04Sonar(DigitalPin.P15, DigitalPin.P16)
20 | this.lineDetectors = new drivers.DigitalPinLineDetectors(
21 | DigitalPin.P13,
22 | DigitalPin.P14,
23 | true
24 | )
25 | this.maxLineSpeed = 64
26 | this.speedTransitionAlpha = 0.5
27 | this.turnRatioTransitionAlpha = 0.5
28 | }
29 |
30 | private car_flag_old = 0 //0:两电机正转 1:两电机反转 2:左旋 3:右旋
31 | private car_flag_new = 0 //0:两电机正转 1:两电机反转 2:左旋 3:右旋
32 | private setPwmMotor(
33 | mode: number,
34 | speed1: number,
35 | speed2: number
36 | ): void {
37 | if (mode < 0 || mode > 6) return
38 |
39 | const buf = pins.createBuffer(5)
40 | buf[0] = MOTOR
41 | switch (mode) {
42 | case 0:
43 | //buf[1] = 0
44 | //buf[2] = 0
45 | //buf[3] = 0
46 | //buf[4] = 0
47 | break //stop
48 | case 1:
49 | buf[1] = speed1
50 | //buf[2] = 0
51 | buf[3] = speed2
52 | //buf[4] = 0
53 | this.car_flag_new = 0
54 | break //run
55 | case 2:
56 | //buf[1] = 0
57 | buf[2] = speed1
58 | //buf[3] = 0
59 | buf[4] = speed2
60 | this.car_flag_new = 1
61 | break //back
62 | case 3:
63 | //buf[1] = 0
64 | //buf[2] = 0
65 | buf[3] = speed2
66 | //buf[4] = 0
67 | this.car_flag_new = 0
68 | break //left
69 | case 4:
70 | buf[1] = speed1
71 | buf[2] = 0
72 | buf[3] = 0
73 | buf[4] = 0
74 | this.car_flag_new = 0
75 | break //right
76 | case 5:
77 | buf[1] = 0
78 | buf[2] = speed1
79 | buf[3] = speed2
80 | buf[4] = 0
81 | this.car_flag_new = 2
82 | break //tleft
83 | case 6:
84 | buf[1] = speed1
85 | //buf[2] = 0
86 | //buf[3] = 0
87 | buf[4] = speed2
88 | this.car_flag_new = 3
89 | break //tright
90 | }
91 | if (this.car_flag_new != this.car_flag_old) {
92 | //上一次状态是正转,这次是反转
93 | const bufff = pins.createBuffer(5)
94 | bufff[0] = MOTOR
95 | //bufff[1] = 0
96 | //bufff[2] = 0
97 | //bufff[3] = 0
98 | //bufff[4] = 0
99 | pins.i2cWriteBuffer(PWM_ADD, buf) //停止100ms
100 | basic.pause(100)
101 | this.car_flag_old = this.car_flag_new
102 | }
103 | pins.i2cWriteBuffer(PWM_ADD, buf)
104 | }
105 |
106 | motorRun(left: number, right: number): void {
107 | const speed = (left + right) >> 1
108 | const spin = Math.sign(left) != Math.sign(right)
109 | if (left === 0 && right === 0) this.setPwmMotor(0, 0, 0)
110 | else if (left >= 0 && right >= 0) this.setPwmMotor(1, left, right)
111 | else if (left <= 0 && right <= 0) this.setPwmMotor(2, -left, -right)
112 | else if (right > left) {
113 | if (spin) this.setPwmMotor(5, Math.abs(left), right)
114 | else this.setPwmMotor(3, Math.abs(left), right)
115 | } else {
116 | if (spin) this.setPwmMotor(6, left, Math.abs(right))
117 | else this.setPwmMotor(4, left, Math.abs(right))
118 | }
119 | }
120 |
121 | headlightsSetColor(red: number, green: number, blue: number) {
122 | setPwmRGB(red, green, blue)
123 | }
124 | }
125 |
126 | /**
127 | * Yahboom Tiny:bit
128 | */
129 | //% fixedInstance whenUsed block="yahboom tiny:bit" weight=99
130 | export const yahboomTinyBit = new RobotDriver(new YahboomTinybitRobot())
131 | }
132 |
--------------------------------------------------------------------------------
/botsim/src/maps/testMap.ts:
--------------------------------------------------------------------------------
1 | import { MAP_ASPECT_RATIO, MICROBIT_COLORS } from "../constants"
2 | import {
3 | defaultBoxShape,
4 | defaultColorBrush,
5 | defaultDynamicPhysics,
6 | defaultEntity,
7 | defaultPathShape,
8 | defaultShapePhysics,
9 | defaultStaticPhysics,
10 | } from "../sim/specs"
11 | import { pickRandom } from "../util"
12 | import { MapSpec } from "./specs"
13 |
14 | const MAP_WIDTH = 90 // cm
15 | const MAP_HEIGHT = MAP_WIDTH / MAP_ASPECT_RATIO
16 |
17 | export const name = "Test Map"
18 | export const create = (): MapSpec => ({
19 | name,
20 | width: 90, // cm
21 | aspectRatio: MAP_ASPECT_RATIO,
22 | color: "#E7E9E7",
23 | spawns: [
24 | {
25 | pos: { x: 20.5, y: 18 },
26 | //pos: { x: 21, y: 45 / MAP_ASPECT_RATIO },
27 | angle: 90,
28 | },
29 | {
30 | pos: { x: 20.5, y: MAP_HEIGHT - 18 },
31 | angle: 90,
32 | },
33 | ],
34 | entities: [
35 | // Line-following path
36 | {
37 | ...defaultEntity(),
38 | pos: { x: 0, y: 0 },
39 | angle: 0,
40 | physics: defaultStaticPhysics(),
41 | shapes: [
42 | {
43 | ...defaultPathShape(),
44 | offset: { x: 0, y: 0 },
45 | angle: 0,
46 | roles: ["follow-line"],
47 | width: 3, // cm
48 | stepSize: 0.1,
49 | closed: true,
50 | verts: [
51 | { x: 10, y: MAP_HEIGHT / 2 + 8 },
52 | { x: 10, y: MAP_HEIGHT / 2 - 8 },
53 | { x: 20, y: 19 },
54 | { x: MAP_WIDTH / 2, y: 22 },
55 | { x: MAP_WIDTH - 20, y: 19 },
56 | { x: MAP_WIDTH - 10, y: MAP_HEIGHT / 2 - 8 },
57 | { x: MAP_WIDTH - 10, y: MAP_HEIGHT / 2 + 8 },
58 | { x: MAP_WIDTH - 20, y: MAP_HEIGHT - 19 },
59 | { x: MAP_WIDTH / 2, y: MAP_HEIGHT - 22 },
60 | { x: 20, y: MAP_HEIGHT - 19 },
61 | ],
62 | brush: {
63 | ...defaultColorBrush(),
64 | fillColor: "#555555",
65 | zIndex: -5,
66 | },
67 | physics: {
68 | ...defaultShapePhysics(),
69 | sensor: true,
70 | },
71 | },
72 | ],
73 | },
74 | // Box obstacles
75 | {
76 | ...defaultEntity(),
77 | pos: { x: MAP_WIDTH / 2, y: MAP_HEIGHT / 2 },
78 | angle: 0,
79 | physics: {
80 | ...defaultDynamicPhysics(),
81 | linearDamping: 10,
82 | angularDamping: 10,
83 | },
84 | shapes: [
85 | {
86 | ...defaultBoxShape(),
87 | offset: { x: -8, y: 0 },
88 | size: { x: 10, y: 5 },
89 | angle: 90,
90 | roles: ["obstacle", "mouse-target"],
91 | brush: {
92 | ...defaultColorBrush(),
93 | fillColor: pickRandom(Object.values(MICROBIT_COLORS)),
94 | borderColor: "#444444",
95 | borderWidth: 0.25,
96 | },
97 | physics: {
98 | ...defaultShapePhysics(),
99 | friction: 0.1,
100 | restitution: 0.5,
101 | density: 3,
102 | },
103 | },
104 | ],
105 | },
106 | {
107 | ...defaultEntity(),
108 | pos: { x: MAP_WIDTH / 2, y: MAP_HEIGHT / 2 },
109 | angle: 0,
110 | physics: {
111 | ...defaultDynamicPhysics(),
112 | linearDamping: 10,
113 | angularDamping: 10,
114 | },
115 | shapes: [
116 | {
117 | ...defaultBoxShape(),
118 | offset: { x: 8, y: 0 },
119 | size: { x: 10, y: 5 },
120 | angle: 90,
121 | roles: ["obstacle", "mouse-target"],
122 | brush: {
123 | ...defaultColorBrush(),
124 | fillColor: pickRandom(Object.values(MICROBIT_COLORS)),
125 | borderColor: "#444444",
126 | borderWidth: 0.25,
127 | },
128 | physics: {
129 | ...defaultShapePhysics(),
130 | friction: 0.1,
131 | restitution: 0.5,
132 | density: 3,
133 | },
134 | },
135 | ],
136 | },
137 | ],
138 | })
139 |
--------------------------------------------------------------------------------
/botsim/src/bots/specs.ts:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * This file contains the data structures that describe a bot.
4 | *
5 | */
6 | import { Vec2Like } from "../types/vec2"
7 |
8 | /**
9 | * A specification for a bot.
10 | *
11 | * - the coordinate 0,0 is the center of the chassis.
12 | * - x is horizontal, y is inverted vertical
13 | * - all units are `cm`, `degrees` and `g` unless specified
14 | *
15 | * ---------
16 | * / o o \
17 | * | |
18 | * [] -> x []
19 | * | | |
20 | * | y |
21 | *. \ - - - - /
22 | */
23 | export type BotSpec = {
24 | name: string
25 | productId: number
26 | /**
27 | * mass of the robot without the ballast
28 | * TODO: replace with weight?
29 | */
30 | mass: number
31 | /**
32 | * weight of the robot without the ballast in grams
33 | */
34 | weight?: number
35 | /**
36 | * Robot PCB chassis color, default is black.
37 | */
38 | silkColor?: string
39 | chassis: ChassisSpec
40 | wheels: WheelSpec[]
41 | rangeSensor?: RangeSensorSpec
42 | lineSensors?: LineSensorSpec[]
43 | leds?: LEDSpec[]
44 | ballast?: BallastSpec
45 | }
46 |
47 | export const LINE_SENSORS = {
48 | ["outer-left"]: 0,
49 | ["left"]: 1,
50 | ["middle"]: 2,
51 | ["right"]: 3,
52 | ["outer-right"]: 4,
53 | }
54 |
55 | export type WheelSlotName = "left" | "right"
56 | export type LineSensorSlotName = keyof typeof LINE_SENSORS
57 | export type LEDSlotName = "left" | "right" | "general" // TODO: rename to all
58 |
59 | export type CircleChassisSpec = {
60 | shape: "circle"
61 | /**
62 | * unit: cm
63 | */
64 | radius: number
65 | }
66 |
67 | export type BoxChassisSpec = {
68 | shape: "box"
69 | /**
70 | * unit: cm
71 | */
72 | size: Vec2Like
73 | }
74 |
75 | export type PolygonChassisSpec = {
76 | shape: "polygon"
77 | /**
78 | * vertices of the polygon, relative to the center of the chassis.
79 | * unit: cm
80 | */
81 | verts: Vec2Like[]
82 | }
83 |
84 | export type ChassisSpec = (
85 | | CircleChassisSpec
86 | | BoxChassisSpec
87 | | PolygonChassisSpec
88 | ) & {
89 | /**
90 | * Chassis texture (optional)
91 | */
92 | texture?: string
93 | }
94 |
95 | export type WheelSpec = {
96 | name: WheelSlotName
97 | /**
98 | * max speed forward (positive).
99 | * no units, just influences force computation.
100 | */
101 | maxSpeed: number
102 |
103 | /**
104 | * Time to move at 80% over 1m.
105 | */
106 | dashTime: number
107 |
108 | /**
109 | * offset from chassis center
110 | * TODO: maybe position of the inside edge
111 | */
112 | pos: Vec2Like
113 | /**
114 | * unit: cm
115 | */
116 | width: number
117 | /**
118 | * unit: cm
119 | */
120 | radius: number
121 |
122 | /**
123 | * If true, the wheel will be rendered.
124 | * default: true
125 | */
126 | visible?: boolean
127 | }
128 |
129 | export type LineSensorSpec = {
130 | name: LineSensorSlotName
131 | /**
132 | * offset from chassis center
133 | */
134 | pos: Vec2Like
135 | }
136 |
137 | export type RangeSensorSpec = {
138 | /**
139 | * offset from chassis center
140 | */
141 | pos: Vec2Like
142 | /**
143 | * degrees
144 | * TODO: make optional, provide defaults for SR04
145 | */
146 | beamAngle: number
147 | /**
148 | * TODO: make optional, provide defaults for SR04
149 | */
150 | maxRange: number
151 | }
152 |
153 | export type LEDSpec = {
154 | name: LEDSlotName
155 | /**
156 | * offset from chassis center
157 | */
158 | pos: Vec2Like
159 | /**
160 | * Radius
161 | * exception: the "general" LED takes its shape and size from the chassis
162 | */
163 | radius: number
164 | /**
165 | *
166 | */
167 | filter?: [number, number, number]
168 | }
169 |
170 | /**
171 | * Ballast is an invisible mass that can be added to the bot to change its
172 | * center of mass and movement characteristics. It can be used to simulate a
173 | * battery or other heavy component.
174 | */
175 | export type BallastSpec = {
176 | /**
177 | * offset from chassis center
178 | */
179 | pos: Vec2Like
180 | size: Vec2Like
181 | /**
182 | * Additional mass added to the robot
183 | */
184 | mass: number
185 | }
186 |
187 | export function toWheels(spec: {
188 | separation: number
189 | diameter: number
190 | width: number
191 | y: number
192 | visible?: boolean
193 | }): WheelSpec[] {
194 | const { separation, diameter, width, y, visible } = spec
195 | const radius = diameter / 2
196 | return [
197 | {
198 | name: "left",
199 | maxSpeed: 100,
200 | pos: { x: -(separation + width) / 2, y },
201 | width,
202 | radius,
203 | dashTime: 0.5,
204 | visible
205 | },
206 | {
207 | name: "right",
208 | maxSpeed: 100,
209 | pos: { x: (separation + width) / 2, y },
210 | width,
211 | radius,
212 | dashTime: 0.5,
213 | visible
214 | },
215 | ]
216 | }
217 |
--------------------------------------------------------------------------------
/botsim/src/external/protocol.ts:
--------------------------------------------------------------------------------
1 | /*****************************************************************************/
2 | // CONTENT BELOW COPIED FROM /protocol/protocol.ts, and updated for module
3 | // symantics (exporting symbols). Keep this in sync with that file.
4 | /*****************************************************************************/
5 |
6 | /**
7 | * Index of the line detectors
8 | */
9 | export enum RobotLineDetector {
10 | /**
11 | * block="outer left"
12 | */
13 | OuterLeft = 0,
14 | /**
15 | * block="left"
16 | */
17 | Left = 1,
18 | /**
19 | * block="middle"
20 | */
21 | Middle = 2,
22 | /**
23 | * block="right"
24 | */
25 | Right = 3,
26 | /**
27 | * block="outer right"
28 | */
29 | OuterRight = 4,
30 | }
31 |
32 | /**
33 | * Robot driver builtin assists
34 | */
35 | export enum RobotAssist {
36 | //% block="line following"
37 | LineFollowing = 1 << 0,
38 | //% block="speed smoothing"
39 | Speed = 1 << 1,
40 | //% block="sensor and motor display"
41 | Display = 2 << 1,
42 | }
43 |
44 | export namespace robot.robots {
45 | export const MAGIC = 0x8429
46 |
47 | export const enum RobotLineState {
48 | //% block="none"
49 | None = 0,
50 | //% block="left"
51 | Left = 0x01,
52 | //% block="right"
53 | Right = 0x02,
54 | //% block="both"
55 | Both = Left | Right,
56 | //% block="lost left"
57 | LostLeft = None | 0x04,
58 | //% block="lost right"
59 | LostRight = None | 0x0a,
60 | }
61 |
62 | /**
63 | * Compact commands through radio numbers
64 | */
65 | export const enum RobotCompactCommand {
66 | KeepAlive = 0xffffff0,
67 |
68 | Command = 0xfffff00,
69 | MotorRunForward = Command | 0x1,
70 | MotorRunBackward = Command | 0x2,
71 | MotorTurnLeft = Command | 0x3,
72 | MotorTurnRight = Command | 0x4,
73 | MotorStop = Command | 0x5,
74 | MotorRunForwardFast = Command | 0x6,
75 | MotorSpinLeft = Command | 0x7,
76 | MotorSpinRight = Command | 0x8,
77 | LEDRed = Command | 0x09,
78 | LEDGreen = Command | 0x0a,
79 | LEDBlue = Command | 0x0b,
80 | LEDOff = Command | 0x0c,
81 | ArmOpen = Command | 0x0d,
82 | ArmClose = Command | 0x0e,
83 |
84 | CommandLast = Command | ArmClose,
85 |
86 | /**
87 | * sonar detected obstable
88 | */
89 | ObstacleState = 0xfffff20,
90 | Obstacle1 = ObstacleState | 0x1,
91 | Obstacle2 = ObstacleState | 0x2,
92 | Obstacle3 = ObstacleState | 0x3,
93 | Obstacle4 = ObstacleState | 0x4,
94 | Obstacle5 = ObstacleState | 0x5,
95 |
96 | /**
97 | * Line sensor state change
98 | */
99 | LineState = 0xfffff30,
100 | LineLeft = LineState | RobotLineState.Left,
101 | LineRight = LineState | RobotLineState.Right,
102 | LineBoth = LineState | RobotLineState.Both,
103 | LineNone = LineState | RobotLineState.None,
104 | LineLostLeft = LineState | RobotLineState.LostLeft,
105 | LineLostRight = LineState | RobotLineState.LostRight,
106 |
107 | LineAnyState = 0xfffff50,
108 | LineLeftRightState = 0xfffff60,
109 | LineLeftRightMiddleState = 0xfffff70,
110 | LineOuterLeftLeftRightOuterRightState = 0xfffff80,
111 | }
112 |
113 | export const enum RobotCommand {
114 | Motor = RobotCompactCommand.MotorRunForward,
115 | Arm = RobotCompactCommand.ArmOpen,
116 | LED = RobotCompactCommand.LEDRed,
117 | Line = RobotCompactCommand.LineState,
118 | Obstacle = RobotCompactCommand.ObstacleState,
119 | }
120 |
121 | /**
122 | * state message is sent by the robot; sensors is sent by the world simulator
123 | */
124 | export interface RobotSimMessage {
125 | type: "state" | "sensors"
126 | /**
127 | * Identifier for the current run
128 | */
129 | id: string
130 | /**
131 | * Device serial identifier
132 | */
133 | deviceId: number
134 | }
135 |
136 | export enum Sensors {
137 | None = 0,
138 | LineDetector = 1 << 0,
139 | Sonar = 1 << 1,
140 | }
141 |
142 | export interface RobotSimStateMessage extends RobotSimMessage {
143 | type: "state"
144 | /**
145 | * Product ID of the robot; allow to discover the hardware configuration
146 | * of the robot
147 | */
148 | productId: number
149 | motorTurnRatio: number
150 | motorSpeed: number
151 | motorLeft: number
152 | motorRight: number
153 | armAperture: number
154 | /**
155 | * RGB 24bit color
156 | */
157 | color: number
158 | /**
159 | * Assistance enabled on the robot
160 | */
161 | assists: RobotAssist
162 | /**
163 | * Sensors used by the current program
164 | */
165 | sensors: Sensors
166 | }
167 |
168 | export interface RobotSensorsMessage extends RobotSimMessage {
169 | type: "sensors"
170 | lineDetectors: number[]
171 | obstacleDistance: number
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/protocol/protocol.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Index of the line detectors
3 | */
4 | enum RobotLineDetector {
5 | /**
6 | * Line detector located on the outer left of the robot. Few robots have this sensor.
7 | */
8 | //% block="outer left"
9 | OuterLeft = 0,
10 | /**
11 | * Line detector located on the left of the directional ball.
12 | */
13 | //% block="left"
14 | Left = 1,
15 | /**
16 | * Line detector located between the left and right sensors, at the center of the robot.
17 | * Few robots have this sensor.
18 | */
19 | //% block="middle"
20 | Middle = 2,
21 | /**
22 | * Line detector located on the right of the directional ball.
23 | */
24 | //% block="right"
25 | Right = 3,
26 | /**
27 | * Line detector located on the outer right of the robot. Few robots have this sensor.
28 | */
29 | //% block="outer right"
30 | OuterRight = 4,
31 | }
32 |
33 | /**
34 | * Robot driver builtin assists
35 | */
36 | enum RobotAssist {
37 | //% block="line following"
38 | LineFollowing = 1 << 0,
39 | //% block="speed smoothing"
40 | Speed = 1 << 1,
41 | //% block="sensor and motor display"
42 | Display = 2 << 1,
43 | }
44 |
45 | namespace robot.robots {
46 | export const MAGIC = 0x8429
47 |
48 | export const enum RobotLineState {
49 | //% block="none"
50 | None = 0,
51 | //% block="left"
52 | Left = 0x01,
53 | //% block="right"
54 | Right = 0x02,
55 | //% block="both"
56 | Both = Left | Right,
57 | //% block="lost left"
58 | LostLeft = None | 0x04,
59 | //% block="lost right"
60 | LostRight = None | 0x0a,
61 | }
62 |
63 | /**
64 | * Compact commands through radio numbers
65 | */
66 | export const enum RobotCompactCommand {
67 | KeepAlive = 0xffffff0,
68 |
69 | Command = 0xfffff00,
70 | MotorRunForward = Command | 0x1,
71 | MotorRunBackward = Command | 0x2,
72 | MotorTurnLeft = Command | 0x3,
73 | MotorTurnRight = Command | 0x4,
74 | MotorStop = Command | 0x5,
75 | MotorRunForwardFast = Command | 0x6,
76 | MotorSpinLeft = Command | 0x7,
77 | MotorSpinRight = Command | 0x8,
78 | LEDRed = Command | 0x09,
79 | LEDGreen = Command | 0x0a,
80 | LEDBlue = Command | 0x0b,
81 | LEDOff = Command | 0x0c,
82 | ArmOpen = Command | 0x0d,
83 | ArmClose = Command | 0x0e,
84 |
85 | CommandLast = Command | ArmClose,
86 |
87 | /**
88 | * sonar detected obstable
89 | */
90 | ObstacleState = 0xfffff20,
91 | Obstacle1 = ObstacleState | 0x1,
92 | Obstacle2 = ObstacleState | 0x2,
93 | Obstacle3 = ObstacleState | 0x3,
94 | Obstacle4 = ObstacleState | 0x4,
95 | Obstacle5 = ObstacleState | 0x5,
96 |
97 | /**
98 | * Line sensor state change
99 | */
100 | LineState = 0xfffff30,
101 | LineLeft = LineState | RobotLineState.Left,
102 | LineRight = LineState | RobotLineState.Right,
103 | LineBoth = LineState | RobotLineState.Both,
104 | LineNone = LineState | RobotLineState.None,
105 | LineLostLeft = LineState | RobotLineState.LostLeft,
106 | LineLostRight = LineState | RobotLineState.LostRight,
107 |
108 | LineAnyState = 0xfffff50,
109 | LineLeftRightState = 0xfffff60,
110 | LineLeftRightMiddleState = 0xfffff70,
111 | LineOuterLeftLeftRightOuterRightState = 0xfffff80,
112 | }
113 |
114 | export const enum RobotCommand {
115 | Motor = RobotCompactCommand.MotorRunForward,
116 | Arm = RobotCompactCommand.ArmOpen,
117 | LED = RobotCompactCommand.LEDRed,
118 | Line = RobotCompactCommand.LineState,
119 | Obstacle = RobotCompactCommand.ObstacleState,
120 | }
121 |
122 | /**
123 | * state message is sent by the robot; sensors is sent by the world simulator
124 | */
125 | export interface RobotSimMessage {
126 | type: "state" | "sensors"
127 | /**
128 | * Identifier for the current run
129 | */
130 | id: string
131 | /**
132 | * Device serial identifier
133 | */
134 | deviceId: number
135 | }
136 |
137 | export enum Sensors {
138 | None = 0,
139 | LineDetector = 1 << 0,
140 | Sonar = 1 << 1
141 | }
142 |
143 | export interface RobotSimStateMessage extends RobotSimMessage {
144 | type: "state"
145 | /**
146 | * Product ID of the robot; allow to discover the hardware configuration
147 | * of the robot
148 | */
149 | productId: number
150 | motorTurnRatio: number
151 | motorSpeed: number
152 | motorLeft: number
153 | motorRight: number
154 | armAperture: number
155 | /**
156 | * RGB 24bit color
157 | */
158 | color: number
159 | /**
160 | * Assistance enabled on the robot
161 | */
162 | assists: RobotAssist
163 |
164 | /**
165 | * Sensors used by the current program
166 | */
167 | sensors: Sensors
168 | }
169 |
170 | export interface RobotSensorsMessage extends RobotSimMessage {
171 | type: "sensors"
172 | lineDetectors: number[]
173 | obstacleDistance: number
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/botsim/src/types/line.ts:
--------------------------------------------------------------------------------
1 | import { toDegrees, toRadians } from "../util"
2 | import { Vec2, Vec2Like } from "./vec2"
3 |
4 | type IntersectionResultNone = {
5 | type: "none"
6 | }
7 |
8 | type IntersectionResultParallel = {
9 | type: "parallel"
10 | }
11 |
12 | type IntersectionResultColinear = {
13 | type: "colinear"
14 | }
15 |
16 | type IntersectionResultPoint = {
17 | type: "point"
18 | p: Vec2Like
19 | }
20 |
21 | export type IntersectionResult =
22 | | IntersectionResultNone
23 | | IntersectionResultParallel
24 | | IntersectionResultColinear
25 | | IntersectionResultPoint
26 |
27 | export type LineSegmentLike = {
28 | p0: Vec2Like
29 | p1: Vec2Like
30 | }
31 |
32 | export type LineSegmentInfo = {
33 | delta: Vec2Like
34 | dir: Vec2Like
35 | len: number
36 | }
37 |
38 | export type LineSegmentWithInfo = LineSegmentLike & LineSegmentInfo
39 |
40 | export class LineSegment implements LineSegmentLike {
41 | public p0 = Vec2.zero()
42 | public p1 = Vec2.zero()
43 |
44 | public static like(p0: Vec2Like, p1: Vec2Like): LineSegmentLike {
45 | return { p0, p1 }
46 | }
47 | public static withInfo(p0: Vec2Like, p1: Vec2Like): LineSegmentWithInfo {
48 | const line = LineSegment.like(p0, p1)
49 | const info = LineSegment.info(line)
50 | return { ...line, ...info }
51 | }
52 | public static addInfo(l: LineSegmentLike): LineSegmentWithInfo {
53 | const info = LineSegment.info(l)
54 | return { ...l, ...info }
55 | }
56 |
57 | public static intersection(
58 | l0: LineSegmentLike,
59 | l1: LineSegmentLike
60 | ): IntersectionResult {
61 | return intersection(l0.p0, l0.p1, l1.p0, l1.p1)
62 | }
63 |
64 | public static intersectionAll(
65 | l: LineSegmentLike,
66 | ls: LineSegmentLike[]
67 | ): IntersectionResult[] {
68 | return ls.map((l2) => LineSegment.intersection(l, l2))
69 | }
70 |
71 | public static angleBetween(
72 | l1: LineSegmentLike,
73 | l2: LineSegmentLike
74 | ): number {
75 | const v1 = {
76 | x: l1.p1.x - l1.p0.x,
77 | y: l1.p1.y - l1.p0.y,
78 | }
79 | const v2 = {
80 | x: l2.p1.x - l2.p0.x,
81 | y: l2.p1.y - l2.p0.y,
82 | }
83 | const dot = Vec2.dot(v1, v2)
84 | const det = Vec2.cross(v1, v2)
85 | return Math.atan2(det, dot)
86 | }
87 |
88 | public static angleBetweenDeg(
89 | l1: LineSegmentLike,
90 | l2: LineSegmentLike
91 | ): number {
92 | return toDegrees(LineSegment.angleBetween(l1, l2))
93 | }
94 |
95 | public static transform(
96 | l: LineSegmentLike,
97 | p: Vec2Like,
98 | angle: number
99 | ): LineSegmentLike {
100 | const p0 = Vec2.transform(l.p0, p, angle)
101 | const p1 = Vec2.transform(l.p1, p, angle)
102 | return { p0, p1 }
103 | }
104 |
105 | public static transformDeg(
106 | l: LineSegmentLike,
107 | p: Vec2Like,
108 | angle: number
109 | ): LineSegmentLike {
110 | return LineSegment.transform(l, p, toRadians(angle))
111 | }
112 |
113 | public static untransform(
114 | l: LineSegmentLike,
115 | p: Vec2Like,
116 | angle: number
117 | ): LineSegmentLike {
118 | const p0 = Vec2.untransform(l.p0, p, angle)
119 | const p1 = Vec2.untransform(l.p1, p, angle)
120 | return { p0, p1 }
121 | }
122 |
123 | public static untransformDeg(
124 | l: LineSegmentLike,
125 | p: Vec2Like,
126 | angle: number
127 | ): LineSegmentLike {
128 | return LineSegment.untransform(l, p, toRadians(angle))
129 | }
130 |
131 | public static scale(l: LineSegmentLike, scale: number): LineSegmentLike {
132 | const p0 = Vec2.scale(l.p0, scale)
133 | const p1 = Vec2.scale(l.p1, scale)
134 | return { p0, p1 }
135 | }
136 |
137 | public static info(l: LineSegmentLike): LineSegmentInfo {
138 | const delta = Vec2.sub(l.p1, l.p0)
139 | const len = Vec2.len(delta)
140 | const dir = Vec2.scale(delta, 1 / len)
141 | return { delta, dir, len }
142 | }
143 | }
144 |
145 | const EPSILON = 0.0001
146 |
147 | export function intersection(
148 | // line 1
149 | p0: Vec2Like,
150 | p1: Vec2Like,
151 | // line 2
152 | p2: Vec2Like,
153 | p3: Vec2Like
154 | ): IntersectionResult {
155 | const x1 = p0.x
156 | const y1 = p0.y
157 | const x2 = p1.x
158 | const y2 = p1.y
159 | const x3 = p2.x
160 | const y3 = p2.y
161 | const x4 = p3.x
162 | const y4 = p3.y
163 | const denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
164 | const numeA = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
165 | const numeB = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
166 | if (denom === 0) {
167 | if (numeA === 0 && numeB === 0) {
168 | return { type: "colinear" }
169 | }
170 | return { type: "parallel" }
171 | }
172 | const uA = numeA / denom
173 | const uB = numeB / denom
174 |
175 | if (
176 | uA >= 0 - EPSILON &&
177 | uA <= 1 + EPSILON &&
178 | uB >= 0 - EPSILON &&
179 | uB <= 1 + EPSILON
180 | ) {
181 | return {
182 | type: "point",
183 | p: {
184 | x: x1 + uA * (x2 - x1),
185 | y: y1 + uA * (y2 - y1),
186 | },
187 | }
188 | }
189 |
190 | return { type: "none" }
191 | }
192 |
--------------------------------------------------------------------------------
/robots/elecfreakscutebotpro.ts:
--------------------------------------------------------------------------------
1 | namespace robot {
2 | // https://github.com/elecfreaks/pxt-Cutebot-Pro/blob/master/main.ts
3 | const i2cAddr: number = 0x10
4 |
5 | const enum CutebotProWheel {
6 | //%block="left wheel"
7 | LeftWheel = 1,
8 | //%block="right wheel"
9 | RightWheel = 2,
10 | //%block="all wheel"
11 | AllWheel = 3,
12 | }
13 |
14 | function pwmCruiseControl(speedL: number, speedR: number): void {
15 | const i2cBuffer = pins.createBuffer(7)
16 |
17 | if (speedL == 0) speedL = 200
18 | else if (speedL > 0) Math.map(speedL, 0, 100, 20, 100)
19 | else Math.map(speedL, -100, 0, -100, -20)
20 |
21 | if (speedR == 0) speedR = 200
22 | else if (speedR > 0) Math.map(speedR, 0, 100, 20, 100)
23 | else Math.map(speedR, -100, 0, -100, -20)
24 |
25 | if (speedL > 0) {
26 | i2cBuffer[0] = 0x99
27 | i2cBuffer[1] = 0x01
28 | i2cBuffer[2] = CutebotProWheel.LeftWheel
29 | i2cBuffer[3] = 0x01
30 | i2cBuffer[4] = speedL
31 | //i2cBuffer[5] = 0x00
32 | i2cBuffer[6] = 0x88
33 | } else {
34 | i2cBuffer[0] = 0x99
35 | i2cBuffer[1] = 0x01
36 | i2cBuffer[2] = CutebotProWheel.LeftWheel
37 | i2cBuffer[3] = 0x00
38 | i2cBuffer[4] = -speedL
39 | //i2cBuffer[5] = 0x00
40 | i2cBuffer[6] = 0x88
41 | }
42 | pins.i2cWriteBuffer(i2cAddr, i2cBuffer)
43 | //basic.pause
44 | if (speedR > 0) {
45 | i2cBuffer[0] = 0x99
46 | i2cBuffer[1] = 0x01
47 | i2cBuffer[2] = CutebotProWheel.RightWheel
48 | i2cBuffer[3] = 0x01
49 | i2cBuffer[4] = speedR
50 | //i2cBuffer[5] = 0x00
51 | i2cBuffer[6] = 0x88
52 | } else {
53 | i2cBuffer[0] = 0x99
54 | i2cBuffer[1] = 0x01
55 | i2cBuffer[2] = CutebotProWheel.RightWheel
56 | i2cBuffer[3] = 0x00
57 | i2cBuffer[4] = -speedR
58 | //i2cBuffer[5] = 0x00
59 | i2cBuffer[6] = 0x88
60 | }
61 | pins.i2cWriteBuffer(i2cAddr, i2cBuffer)
62 | }
63 |
64 | const enum TrackbitStateType {
65 | //% block="◌ ◌ ◌ ◌"
66 | Tracking_State_0 = 0,
67 | //% block="◌ ● ● ◌"
68 | Tracking_State_1 = 6,
69 | //% block="◌ ◌ ● ◌"
70 | Tracking_State_2 = 4,
71 | //% block="◌ ● ◌ ◌"
72 | Tracking_State_3 = 2,
73 |
74 | //% block="● ◌ ◌ ●"
75 | Tracking_State_4 = 9,
76 | //% block="● ● ● ●"
77 | Tracking_State_5 = 15,
78 | //% block="● ◌ ● ●"
79 | Tracking_State_6 = 13,
80 | //% block="● ● ◌ ●"
81 | Tracking_State_7 = 11,
82 |
83 | //% block="● ◌ ◌ ◌"
84 | Tracking_State_8 = 1,
85 | //% block="● ● ● ◌"
86 | Tracking_State_9 = 7,
87 | //% block="● ◌ ● ◌"
88 | Tracking_State_10 = 5,
89 | //% block="● ● ◌ ◌"
90 | Tracking_State_11 = 3,
91 |
92 | //% block="◌ ◌ ◌ ●"
93 | Tracking_State_12 = 8,
94 | //% block="◌ ● ● ●"
95 | Tracking_State_13 = 14,
96 | //% block="◌ ◌ ● ●"
97 | Tracking_State_14 = 12,
98 | //% block="◌ ● ◌ ●"
99 | Tracking_State_15 = 10,
100 | }
101 |
102 | class I2CLineDetector implements drivers.LineDetectors {
103 | start(): void {}
104 | lineState(state: number[]): void {
105 | const v = this.trackbitStateValue()
106 | state[RobotLineDetector.Left] =
107 | v & TrackbitStateType.Tracking_State_11 ? 1023 : 0
108 | state[RobotLineDetector.Right] =
109 | v & TrackbitStateType.Tracking_State_14 ? 1023 : 0
110 | state[RobotLineDetector.OuterLeft] =
111 | v & TrackbitStateType.Tracking_State_8 ? 1023 : 0
112 | state[RobotLineDetector.OuterRight] =
113 | v & TrackbitStateType.Tracking_State_12 ? 1023 : 0
114 | }
115 |
116 | private trackbitStateValue() {
117 | const i2cBuffer = pins.createBuffer(7)
118 | i2cBuffer[0] = 0x99
119 | i2cBuffer[1] = 0x12
120 | /*i2cBuffer[2] = 0x00
121 | i2cBuffer[3] = 0x00
122 | i2cBuffer[4] = 0x00
123 | i2cBuffer[5] = 0x00*/
124 | i2cBuffer[6] = 0x88
125 | pins.i2cWriteBuffer(i2cAddr, i2cBuffer)
126 | return robots.i2cReadU8(i2cAddr)
127 | }
128 | }
129 |
130 | class ElecfreaksCutebotProRobot extends robots.Robot {
131 | constructor() {
132 | super(0x31e95c0a)
133 | this.leds = new drivers.WS2812bLEDStrip(DigitalPin.P15, 8)
134 | this.sonar = new drivers.SR04Sonar(DigitalPin.P12, DigitalPin.P8)
135 | this.lineDetectors = new I2CLineDetector()
136 | this.maxLineSpeed = 30
137 | }
138 |
139 | motorRun(left: number, right: number) {
140 | pwmCruiseControl(left, right)
141 | }
142 |
143 | headlightsSetColor(r: number, g: number, b: number) {
144 | const buf = pins.createBuffer(7)
145 | buf[0] = 0x99
146 | buf[1] = 0x0f
147 | buf[2] = 0x03
148 | buf[3] = r
149 | buf[4] = g
150 | buf[5] = b
151 | buf[6] = 0x88
152 | pins.i2cWriteBuffer(i2cAddr, buf)
153 | }
154 | }
155 |
156 | /**
157 | * Cute:bot PRO from Elecfreaks
158 | */
159 | //% fixedInstance whenUsed block="elecfreaks cutebot PRO" weight=50
160 | export const elecfreaksCuteBotPro = new RobotDriver(
161 | new ElecfreaksCutebotProRobot()
162 | )
163 | }
164 |
--------------------------------------------------------------------------------
/botsim/src/sim/bot/wheel.ts:
--------------------------------------------------------------------------------
1 | import { BotSpec, WheelSpec } from "../../bots/specs"
2 | import {
3 | BrushSpec,
4 | EntityShapeSpec,
5 | defaultBoxShape,
6 | defaultColorBrush,
7 | defaultEntityShape,
8 | defaultShapePhysics,
9 | } from "../specs"
10 | import { Bot } from "."
11 | import { Vec2, Vec2Like } from "../../types/vec2"
12 | import Planck from "planck-js"
13 |
14 | const wheelBrush: BrushSpec = {
15 | // TODO: Make this an animated texture or pattern
16 | ...defaultColorBrush(),
17 | fillColor: "#212738",
18 | borderColor: "black",
19 | borderWidth: 0.2,
20 | zIndex: 1,
21 | }
22 |
23 | export class Wheel {
24 | public static makeShapeSpecs(botSpec: BotSpec): EntityShapeSpec[] {
25 | const specs: EntityShapeSpec[] = []
26 | for (const wheelSpec of botSpec.wheels) {
27 | specs.push(Wheel.makeShapeSpec(botSpec, wheelSpec))
28 | }
29 | return specs
30 | }
31 |
32 | public static makeShapeSpec(botSpec: BotSpec, wheelSpec: WheelSpec): EntityShapeSpec {
33 | return {
34 | ...defaultEntityShape(),
35 | ...defaultBoxShape(),
36 | label: wheelSpec.name,
37 | roles: ["mouse-target", "robot"],
38 | offset: wheelSpec.pos,
39 | size: { x: wheelSpec.width, y: wheelSpec.radius * 2 },
40 | brush: {
41 | ...wheelBrush,
42 | zIndex: 6,
43 | //visible: wheelSpec.visible ?? !botSpec.chassis.texture,
44 | visible: wheelSpec.visible ?? true,
45 | },
46 | physics: {
47 | ...defaultShapePhysics(),
48 | friction: 0.2,
49 | restitution: 0.2,
50 | density: 10,
51 | },
52 | }
53 | }
54 |
55 | private maxSpeed: number
56 | private currSpeed: number
57 | private localPos: Vec2Like
58 | private friction?: Planck.FrictionJoint
59 |
60 | constructor(
61 | private bot: Bot,
62 | private spec: WheelSpec
63 | ) {
64 | this.localPos = this.spec.pos
65 | this.maxSpeed = spec.maxSpeed
66 | this.currSpeed = 0
67 | // Handles some of the friction between wheel and ground. Additional
68 | // friction is handled in updateFriction(), below.
69 | this.friction = this.bot.entity.physicsObj.addFrictionJoint(
70 | this.spec.pos
71 | )
72 | // hand-tuned values
73 | this.friction?.m_bodyB.setAngularDamping(10)
74 | this.friction?.m_bodyB.setLinearDamping(10)
75 | this.friction?.setMaxForce(5000)
76 | this.friction?.setMaxTorque(2)
77 | }
78 |
79 | public destroy() {}
80 |
81 | public update(dtSecs: number) {
82 | // If bot is held or paused, don't apply movement forces
83 | if (this.bot.held || this.bot.paused) return
84 | this.updateFriction(dtSecs)
85 | this.updateForce(dtSecs)
86 | }
87 |
88 | public setSpeed(speed: number) {
89 | speed = Math.min(Math.abs(speed / 100), 1) * Math.sign(speed)
90 | this.currSpeed = this.maxSpeed * speed
91 | }
92 |
93 | public updateFriction(dtSecs: number) {
94 | const worldPos = this.bot.entity.physicsObj.getWorldPoint(this.localPos)
95 |
96 | // Debug flags. These should be commented out in production
97 | //const dampenAngularVelocity = true
98 | //const dampenLateralVelocity = true
99 |
100 | // Tweak this value to adjust top spin rate
101 | const maxAngularVelocity = 10 // The maximum angular velocity (hand-tuned)
102 |
103 | //// Dampen angular velocity
104 | //if (dampenAngularVelocity)
105 | {
106 | const angularDamping = 1 // The amount of angular velocity dampening to apply
107 | const angularDampingScalar = 1 // hand-tuned
108 | this.bot.entity.physicsObj.applyAngularForce(
109 | -this.bot.entity.physicsObj.getAngularVelocity() *
110 | angularDamping *
111 | angularDampingScalar *
112 | this.bot.entity.physicsObj.body.getInertia()
113 | )
114 | const angularVel = this.bot.entity.physicsObj.getAngularVelocity()
115 | if (Math.abs(angularVel) > maxAngularVelocity) {
116 | this.bot.entity.physicsObj.setAngularVelocity(
117 | maxAngularVelocity * Math.sign(angularVel)
118 | )
119 | }
120 | }
121 |
122 | //// Dampen lateral velocity
123 | //if (dampenLateralVelocity)
124 | {
125 | const lateralDamping = 1 // The amount of lateral velocity dampening to apply
126 | const lateralDampingScalar = 3 // hand-tuned
127 | const lateralVel =
128 | this.bot.entity.physicsObj.getLateralVelocity(worldPos)
129 | this.bot.entity.physicsObj.applyForce(
130 | Vec2.scale(
131 | Vec2.neg(lateralVel),
132 | lateralDamping *
133 | lateralDampingScalar *
134 | this.bot.entity.physicsObj.body.getMass()
135 | ),
136 | worldPos
137 | )
138 | }
139 | }
140 |
141 | public updateForce(dtSecs: number) {
142 | const worldPos = this.bot.entity.physicsObj.getWorldPoint(this.localPos)
143 | const forwardDir = this.bot.forward
144 |
145 | // Tweak this value to adjust top speed
146 | const speedMagScalar = 4.5 // hand-tuned
147 | const forceMag =
148 | this.currSpeed *
149 | speedMagScalar *
150 | this.bot.entity.physicsObj.body.getMass()
151 |
152 | // Apply forward force
153 | const force = Vec2.scale(forwardDir, forceMag)
154 | this.bot.entity.physicsObj.applyForce(force, worldPos)
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/botsim/src/sim/bot/lineSensor.ts:
--------------------------------------------------------------------------------
1 | import { Bot } from "."
2 | import { LineSensorSpec } from "../../bots/specs"
3 | import { nextId } from "../../util"
4 | import {
5 | EntityShapeSpec,
6 | defaultCircleShape,
7 | defaultColorBrush,
8 | defaultEntityShape,
9 | defaultShapePhysics,
10 | } from "../specs"
11 | import { testOverlap } from "../util"
12 |
13 | const lineSensorBrush = {
14 | on: {
15 | ...defaultColorBrush(),
16 | fillColor: "white",
17 | borderColor: "white",
18 | borderWidth: 0.1,
19 | },
20 | off: {
21 | ...defaultColorBrush(),
22 | fillColor: "black",
23 | borderColor: "black",
24 | borderWidth: 0.1,
25 | },
26 | }
27 |
28 | export class LineSensor {
29 | sensorId: string
30 | _shapeSpecs: EntityShapeSpec[]
31 | _value: number
32 |
33 | public get shapeSpecs(): EntityShapeSpec[] {
34 | return this._shapeSpecs
35 | }
36 | public get value(): number {
37 | return this._value
38 | }
39 |
40 | private makeShapeSpecs(spec: LineSensorSpec): EntityShapeSpec[] {
41 | // The visual representation of the sensor (larger than actual size)
42 | const onLineSpec: EntityShapeSpec = {
43 | ...defaultEntityShape(),
44 | ...defaultCircleShape(),
45 | label: this.sensorId + ".on",
46 | offset: spec.pos,
47 | radius: 0.5,
48 | brush: {
49 | ...lineSensorBrush.on,
50 | visible: false,
51 | zIndex: 6,
52 | },
53 | physics: {
54 | ...defaultShapePhysics(),
55 | friction: 0,
56 | restitution: 0,
57 | density: 0,
58 | sensor: true,
59 | },
60 | }
61 | const offLineSpec: EntityShapeSpec = {
62 | ...defaultEntityShape(),
63 | ...defaultCircleShape(),
64 | label: this.sensorId + ".off",
65 | offset: spec.pos,
66 | radius: 0.5,
67 | brush: {
68 | ...lineSensorBrush.off,
69 | visible: true,
70 | zIndex: 6,
71 | },
72 | physics: {
73 | ...defaultShapePhysics(),
74 | friction: 0,
75 | restitution: 0,
76 | density: 0,
77 | sensor: true,
78 | },
79 | }
80 | // The actual sensor (very small)
81 | const sensorSpec: EntityShapeSpec = {
82 | ...defaultEntityShape(),
83 | ...defaultCircleShape(),
84 | label: this.sensorId + ".sensor",
85 | offset: spec.pos,
86 | radius: 0.3,
87 | roles: ["line-sensor"],
88 | brush: {
89 | ...defaultColorBrush(),
90 | fillColor: "transparent",
91 | borderColor: "transparent",
92 | borderWidth: 0,
93 | zIndex: 1,
94 | },
95 | physics: {
96 | ...defaultShapePhysics(),
97 | friction: 0,
98 | restitution: 0,
99 | density: 0,
100 | sensor: true,
101 | },
102 | }
103 | return [onLineSpec, offLineSpec, sensorSpec]
104 | }
105 |
106 | constructor(
107 | private bot: Bot,
108 | private spec: LineSensorSpec
109 | ) {
110 | this.sensorId = "line-sensor." + nextId()
111 | this._shapeSpecs = this.makeShapeSpecs(spec)
112 | this._value = 0
113 | }
114 |
115 | public destroy() {}
116 |
117 | public update(dtSecs: number) {
118 | this.setDetecting(false)
119 | for (
120 | let ce = this.bot.entity.physicsObj.body.getContactList();
121 | ce;
122 | ce = ce.next ?? null
123 | ) {
124 | // TODO: Refactor contacts to work from Simulation, providing a
125 | // single contact listener instead of embedding polling like this in
126 | // components.
127 | const contact = ce.contact
128 | const fixtureA = contact.getFixtureA()
129 | const fixtureB = contact.getFixtureB()
130 | const userDataA = fixtureA.getUserData() as EntityShapeSpec
131 | const userDataB = fixtureB.getUserData() as EntityShapeSpec
132 | if (!userDataA || !userDataB) continue
133 | const labelA = userDataA.label
134 | const labelB = userDataB.label
135 | const rolesA = userDataA.roles
136 | const rolesB = userDataB.roles
137 | if (
138 | labelA === this.sensorId + ".sensor" &&
139 | rolesB.includes("follow-line")
140 | ) {
141 | const overlap = testOverlap(fixtureA, fixtureB)
142 | if (overlap) {
143 | this.setDetecting(true)
144 | }
145 | } else if (
146 | labelB === this.sensorId + ".sensor" &&
147 | rolesA.includes("follow-line")
148 | ) {
149 | const overlap = testOverlap(fixtureA, fixtureB)
150 | if (overlap) {
151 | this.setDetecting(true)
152 | }
153 | }
154 | }
155 | }
156 |
157 | public setDetecting(detecting: boolean) {
158 | this._value = detecting ? 1023 : 0
159 | const onShape = this.bot.entity.renderObj.shapes.get(
160 | this.sensorId + ".on"
161 | )
162 | const offShape = this.bot.entity.renderObj.shapes.get(
163 | this.sensorId + ".off"
164 | )
165 | if (onShape) onShape.visible = detecting
166 | if (offShape) offShape.visible = !detecting
167 | }
168 |
169 | public setUsed(used: boolean) {
170 | // Anything to do here?
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/botsim/src/sim/bot/index.ts:
--------------------------------------------------------------------------------
1 | import { LineSensorValues, Simulation } from ".."
2 | import {
3 | BotSpec,
4 | LEDSlotName,
5 | LineSensorSlotName,
6 | WheelSlotName,
7 | } from "../../bots/specs"
8 | import { Chassis } from "./chassis"
9 | import { Wheel } from "./wheel"
10 | import { RangeSensor } from "./rangeSensor"
11 | import { LineSensor } from "./lineSensor"
12 | import { LED } from "./led"
13 | import { makeBallastSpec } from "./ballast"
14 | import {
15 | EntityShapeSpec,
16 | EntitySpec,
17 | defaultDynamicPhysics,
18 | defaultEntity,
19 | } from "../specs"
20 | import { Entity } from "../entity"
21 | import { Vec2Like } from "../../types/vec2"
22 | import { SpawnSpec } from "../../maps/specs"
23 |
24 | /**
25 | * The Bot class is a controller for a robot in the simulation. It contains
26 | * references to the Entity objects that make up the robot, and provides
27 | * methods for controlling the robot's motors and reading its sensors.
28 | */
29 | export class Bot {
30 | public entity: Entity
31 | public chassis: Chassis
32 | public wheels = new Map()
33 | public rangeSensor?: RangeSensor
34 | public lineSensors = new Map()
35 | public leds = new Map()
36 | public paused = false
37 |
38 | public get pos(): Vec2Like {
39 | return this.entity.physicsObj.pos
40 | }
41 | public get angle(): number {
42 | return this.entity.physicsObj.angle
43 | }
44 | public get forward(): Vec2Like {
45 | return this.entity.physicsObj.forward
46 | }
47 | public get held(): boolean {
48 | const heldBody = this.entity.sim.physics.mouseJoint?.getBodyB()
49 | return heldBody === this.entity.physicsObj.body
50 | }
51 |
52 | constructor(
53 | public sim: Simulation,
54 | spawn: SpawnSpec,
55 | public spec: BotSpec
56 | ) {
57 | const chassisShape = Chassis.makeShapeSpec(spec)
58 | const wheelShapes = Wheel.makeShapeSpecs(spec)
59 | const ballastShape = makeBallastSpec(spec)
60 |
61 | spec.lineSensors?.forEach((sensorSpec) => {
62 | const sensor = new LineSensor(this, sensorSpec)
63 | this.lineSensors.set(sensorSpec.name, sensor)
64 | })
65 | const lineSensorShapes = Array.from(this.lineSensors.values()).map(
66 | (sensor) => sensor.shapeSpecs
67 | )
68 | if (spec.rangeSensor)
69 | this.rangeSensor = new RangeSensor(this, spec.rangeSensor)
70 | const rangeSensorShapes = this.rangeSensor?.shapeSpecs ?? []
71 | const ledShapes =
72 | spec.leds?.map((ledSpec) => LED.makeShapeSpec(spec, ledSpec)) ?? []
73 |
74 | const shapes: EntityShapeSpec[] = [
75 | chassisShape,
76 | ...wheelShapes,
77 | ...lineSensorShapes.flat(),
78 | //...ledShapes,
79 | ...rangeSensorShapes,
80 | ]
81 | if (ballastShape) shapes.push(ballastShape)
82 |
83 | const entitySpec: EntitySpec = {
84 | ...defaultEntity(),
85 | pos: { ...spawn.pos },
86 | angle: spawn.angle,
87 | physics: {
88 | ...defaultDynamicPhysics(),
89 | // hand-tuned values
90 | linearDamping: 10,
91 | angularDamping: 10,
92 | },
93 | shapes,
94 | }
95 |
96 | this.entity = sim.createEntity(entitySpec)
97 |
98 | this.chassis = new Chassis(this, spec.chassis)
99 | spec.wheels.forEach((wheelSpec) =>
100 | this.wheels.set(wheelSpec.name, new Wheel(this, wheelSpec))
101 | )
102 | spec.leds?.forEach((ledSpec) =>
103 | this.leds.set(ledSpec.name, new LED(this, ledSpec))
104 | )
105 | }
106 |
107 | public destroy() {
108 | this.chassis.destroy()
109 | this.wheels.forEach((wheel) => wheel.destroy())
110 | this.rangeSensor?.destroy()
111 | this.lineSensors.forEach((sensor) => sensor.destroy())
112 | this.leds.forEach((led) => led.destroy())
113 | }
114 |
115 | public update(dtSecs: number) {
116 | this.chassis.update(dtSecs)
117 | this.wheels.forEach((wheel) => wheel.update(dtSecs))
118 | this.rangeSensor?.update(dtSecs)
119 | this.lineSensors.forEach((sensor) => sensor.update(dtSecs))
120 | this.leds.forEach((led) => led.update(dtSecs))
121 | }
122 |
123 | private setWheelSpeed(name: WheelSlotName, speed: number) {
124 | const wheel = this.wheels.get(name)
125 | if (!wheel) return
126 | wheel.setSpeed(speed)
127 | }
128 |
129 | public setMotors(left: number, right: number) {
130 | if (this.held) return
131 | this.setWheelSpeed("left", left)
132 | this.setWheelSpeed("right", right)
133 | }
134 |
135 | public setColor(name: LEDSlotName, color: number) {
136 | /*
137 | const led = this.leds.get(name)
138 | if (!led) return
139 | led.setColor(color)
140 | */
141 | this.chassis.setColor(color)
142 | }
143 |
144 | public setLineSensorUsed(used: boolean) {
145 | this.lineSensors.forEach((sensor) => sensor.setUsed(used))
146 | }
147 |
148 | public setRangeSensorUsed(used: boolean) {
149 | this.rangeSensor?.setUsed(used)
150 | }
151 |
152 | public readLineSensors(): LineSensorValues {
153 | return {
154 | ["outer-left"]: this.lineSensors.get("outer-left")?.value ?? -1,
155 | ["left"]: this.lineSensors.get("left")?.value ?? -1,
156 | ["middle"]: this.lineSensors.get("middle")?.value ?? -1,
157 | ["right"]: this.lineSensors.get("right")?.value ?? -1,
158 | ["outer-right"]: this.lineSensors.get("outer-right")?.value ?? -1,
159 | }
160 | }
161 |
162 | public readRangeSensor(): number {
163 | return this.rangeSensor?.value ?? -1
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/robots/kittenbotminilfr.ts:
--------------------------------------------------------------------------------
1 | namespace robot {
2 | // https://github.com/KittenBot/pxt-minilfr/blob/master/main.ts
3 |
4 | function writeCmd(cmd: string) {
5 | serial.writeLine(cmd)
6 | }
7 |
8 | function speedTrim(speed: number) {
9 | // motor deadzone fix and map to [-255, 255]
10 | if (speed > 0) {
11 | return Math.floor(Math.map(speed, 0, 100, 20, 255))
12 | } else if (speed < 0) {
13 | return Math.floor(Math.map(speed, -100, 0, -255, -20))
14 | }
15 | return 0
16 | }
17 |
18 | function splitSpaces(input: string): string[] {
19 | const result: string[] = []
20 | let currentWord = ""
21 | for (let i = 0; i < input.length; i++) {
22 | const char = input[i]
23 | if (char === " ") {
24 | if (currentWord.length > 0) {
25 | result.push(currentWord)
26 | }
27 | currentWord = ""
28 | } else {
29 | currentWord += char
30 | }
31 | }
32 | if (currentWord.length > 0) {
33 | result.push(currentWord)
34 | }
35 | return result
36 | }
37 |
38 | function parseIntFromDigits(input: string): number {
39 | let result = 0
40 | let placeValue = 1
41 | const zero = "0".charCodeAt(0)
42 | for (let i = input.length - 1; i >= 0; i--) {
43 | const digit = input[i].charCodeAt(0) - zero
44 | result += digit * placeValue
45 | placeValue *= 10
46 | }
47 | return result
48 | }
49 |
50 | class SerialLineDetector implements drivers.LineDetectors {
51 | sensorUpdated: number
52 | sensorValue: number[]
53 | constructor() {
54 | this.sensorUpdated = 0
55 | this.sensorValue = [-1, -1, -1, -1, -1]
56 | }
57 | start(): void { }
58 | lineState(state: number[]): void {
59 | const v = this.sensorValue
60 | state[RobotLineDetector.OuterLeft] = v[0]
61 | state[RobotLineDetector.Left] = v[1]
62 | state[RobotLineDetector.Middle] = v[2]
63 | state[RobotLineDetector.Right] = v[3]
64 | state[RobotLineDetector.OuterRight] = v[4]
65 | }
66 |
67 | parse(tmp: string[]) {
68 | this.sensorValue[0] = parseIntFromDigits(tmp[1])
69 | this.sensorValue[1] = parseIntFromDigits(tmp[2])
70 | this.sensorValue[2] = parseIntFromDigits(tmp[3])
71 | this.sensorValue[3] = parseIntFromDigits(tmp[4])
72 | this.sensorValue[4] = parseIntFromDigits(tmp[5])
73 | this.sensorUpdated = control.millis()
74 | }
75 | }
76 |
77 | class KittenbotMiniLFRRobot extends robots.Robot {
78 | //private mode: MiniLFRMode = MiniLFRMode.IDLE
79 | private ultrasonicUpdated: number = 0
80 |
81 | private ultrasonicValue: number = 0
82 |
83 | constructor() {
84 | super(0x320f9de2)
85 | this.lineDetectors = new SerialLineDetector()
86 | this.commands[robot.robots.RobotCompactCommand.MotorTurnLeft] = {
87 | turnRatio: -50,
88 | speed: 40,
89 | }
90 |
91 | this.commands[robot.robots.RobotCompactCommand.MotorTurnRight] = {
92 | turnRatio: 50,
93 | speed: 40,
94 | }
95 | }
96 |
97 | start() {
98 | serial.redirect(SerialPin.P0, SerialPin.P1, 115200)
99 | serial.writeString("\n\n")
100 | serial.setRxBufferSize(64)
101 |
102 | serial.onDataReceived("\n", () => {
103 | let s = serial.readString()
104 | let tmp = splitSpaces(s) // string.split is 500b
105 |
106 | if (tmp[0] === "M7") {
107 | this.ultrasonicValue = parseIntFromDigits(tmp[1])
108 | this.ultrasonicUpdated = control.millis()
109 | } else if (tmp[0] === "M10") {
110 | ; (this.lineDetectors as SerialLineDetector).parse(tmp)
111 | } else if (tmp[0] === "M33") {
112 | //this.mode = MiniLFRMode.IDLE
113 | }
114 | })
115 |
116 | basic.forever(() => {
117 | // read line sensor
118 | if (
119 | control.millis() -
120 | (this.lineDetectors as SerialLineDetector)
121 | .sensorUpdated >
122 | 100
123 | ) {
124 | writeCmd("M10")
125 | }
126 | basic.pause(50)
127 | if (control.millis() - this.ultrasonicUpdated > 100) {
128 | writeCmd("M7")
129 | }
130 | basic.pause(50)
131 | })
132 |
133 | writeCmd("M23 14")
134 | basic.pause(500)
135 | writeCmd("M6 1 1")
136 | basic.pause(500)
137 | writeCmd("M6 0 0")
138 | }
139 |
140 | motorRun(left: number, right: number): void {
141 | let spdL = speedTrim(left)
142 | let spdR = speedTrim(right)
143 | writeCmd(`M200 ${spdL} ${spdR}`)
144 | }
145 |
146 | playTone(frequency: number, duration: number) {
147 | writeCmd(`M18 ${frequency} ${duration}`)
148 | }
149 |
150 | headlightsSetColor(red: number, green: number, blue: number) {
151 | // set rgb to both ultrasonic and hover led
152 | writeCmd(
153 | `M16 0 ${red} ${green} ${blue}\nM13 0 ${red} ${green} ${blue}`
154 | )
155 | }
156 |
157 | ultrasonicDistance(maxCmDistance: number): number {
158 | return this.ultrasonicValue
159 | }
160 | }
161 |
162 | /**
163 | * Kittenbot MiniLFR
164 | */
165 | //% fixedInstance whenUsed block="kittenbot minilfr"
166 | export const kittenbotMiniLFR = new RobotDriver(new KittenbotMiniLFRRobot())
167 | }
168 |
--------------------------------------------------------------------------------