├── 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 | --------------------------------------------------------------------------------