{"id":28923808,"url":"https://github.com/liatemplates/microbit-simulator","last_synced_at":"2026-02-03T19:01:34.155Z","repository":{"id":294710842,"uuid":"987840733","full_name":"LiaTemplates/MicroBit-Simulator","owner":"LiaTemplates","description":"Embed the MicroBit-Simulator into LiaScript","archived":false,"fork":false,"pushed_at":"2025-05-21T19:38:05.000Z","size":7,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-22T09:46:14.079Z","etag":null,"topics":["liascript","liascript-template","markdown","microbit","micropython","oer","programming","python"],"latest_commit_sha":null,"homepage":"https://liascript.github.io/course/?https://raw.githubusercontent.com/liaTemplates/MicroBit-Simulator/main/README.md","language":"CSS","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"cc0-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LiaTemplates.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-21T17:01:47.000Z","updated_at":"2025-05-21T19:39:19.000Z","dependencies_parsed_at":"2025-05-26T01:32:36.132Z","dependency_job_id":null,"html_url":"https://github.com/LiaTemplates/MicroBit-Simulator","commit_stats":null,"previous_names":["liatemplates/microbit-simulator"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/LiaTemplates/MicroBit-Simulator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LiaTemplates%2FMicroBit-Simulator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LiaTemplates%2FMicroBit-Simulator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LiaTemplates%2FMicroBit-Simulator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LiaTemplates%2FMicroBit-Simulator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LiaTemplates","download_url":"https://codeload.github.com/LiaTemplates/MicroBit-Simulator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LiaTemplates%2FMicroBit-Simulator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29054040,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-03T15:43:47.601Z","status":"ssl_error","status_checked_at":"2026-02-03T15:43:46.709Z","response_time":96,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["liascript","liascript-template","markdown","microbit","micropython","oer","programming","python"],"created_at":"2025-06-22T09:40:33.191Z","updated_at":"2026-02-03T19:01:34.150Z","avatar_url":"https://github.com/LiaTemplates.png","language":"CSS","readme":"\u003c!--\nauthor: André Dietrich\n\nemail: LiaScript@web.de\n\ncomment: Embed the MicroPython simulator for the BBC MicroBit into a LiaScript course.\n\nversion: 0.0.1\n\nedit:  true\n\nlink:  style.css\n\n@onload\nwindow.createSensorUI = function (simulator, selector, state) {\n  const parent = document.querySelector(selector)\n  parent.classList.add('sensor-ui')\n  parent.innerHTML = ''\n\n  function onChange(id, value) {\n    simulator.postMessage({ kind: 'set_value', id, value }, '*')\n  }\n\n  function makeCard(id) {\n    const card = document.createElement('div')\n    card.className = 'sensor-card'\n    const label = document.createElement('span')\n    label.innerText = id\n    card.appendChild(label)\n    return card\n  }\n\n  for (const sensor of Object.values(state)) {\n    const { type, id } = sensor\n    const card = makeCard(id)\n\n    if (type === 'range') {\n      const input = document.createElement('input')\n      input.type = 'range'\n      input.min = sensor.min\n      input.max = sensor.max\n      input.value = sensor.value\n      input.addEventListener('input', (e) =\u003e onChange(id, e.target.value))\n      card.appendChild(input)\n    } else if (type === 'enum') {\n      const select = document.createElement('select')\n      for (const choice of sensor.choices) {\n        const opt = new Option(\n          choice,\n          choice,\n          choice === sensor.value,\n          choice === sensor.value\n        )\n        select.appendChild(opt)\n      }\n      select.addEventListener('change', (e) =\u003e onChange(id, e.target.value))\n      card.appendChild(select)\n    } else {\n      const info = { ...sensor }\n      delete info.type\n      const infoCode = document.createElement('code')\n      infoCode.innerText = JSON.stringify(info)\n      card.appendChild(infoCode)\n    }\n\n    parent.appendChild(card)\n  }\n}\n\n@end\n\n\n@microbit: @microbit_(@uid,```@input```,false)\n\n@microbit.withSensorUI: @microbit_(@uid,```@input```,true)\n\n@microbit_\n\u003cscript\u003e\nconst iframe = document.querySelector('#simulator_@0')\nconst simulator = iframe.contentWindow\nlet state = {}\n\nconst onMessage = (e) =\u003e {\n    if (e.source !== simulator) return;\n    const { data } = e;\n    switch (data.kind) {\n      case 'serial_output':\n        console.stream(data.data);\n        break;\n      case 'log_output':\n        if (data.headings) console.log(data.headings);\n        if (data.data)     console.log(data.data);\n        break;\n      case 'radio_output':\n        const text = new TextDecoder()\n          .decode(e.data.data)\n          .replace(/^\\x01\\x00\\x01/, '')\n        console.log(text)\n        break\n      case 'ready':\n        state = data.state\n        window.console.log('ready', data.state);\n        if(@2) {\n          createSensorUI(simulator, \"#sensors_@0\", state)\n        }\n        break\n      case 'state_change':\n        state = {\n          ...state,\n          ...data.change,\n        }\n        if(@2) {\n          createSensorUI(simulator, \"#sensors_@0\", state)\n        }\n        break\n      case 'log_delete':\n        console.debug('[log_delete]');\n        break;\n      case 'request_flash':\n        simulator.postMessage({\n          kind: 'flash',\n          filesystem: {\n            'main.py': new TextEncoder().encode(`@1`),\n          },\n        }, '*');\n        simulator.postMessage({\n          kind: 'reset',\n        }, '*');\n        send.lia(\"LIA: terminal\");\n        break;\n      case 'internal_error':\n        console.error('Simulator internal error:', data.error);\n        break;\n      default:\n        console.warn('Unknown message kind:', data.kind);\n        break;\n    }\n  };\n\nwindow.addEventListener('message', onMessage);\n\nsend.handle(\"input\", (data) =\u003e {\n  simulator.postMessage({\n      kind: 'serial_input',\n      data: data + \"\\r\\n\",\n    },\n    '*'\n  )\n})\n\nsend.handle(\"stop\", () =\u003e {\n    // detach the message listener\n    window.removeEventListener('message', onMessage);\n\n    // reset the iframe by reloading its src\n    // (you can also set src=\"\" then back, or completely remove/add the element)\n    iframe.src = iframe.src;\n\n    // optionally hide it if you want:\n    iframe.style.display = \"none\";\n\n    document.getElementById(\"sensors_@0\").innerHTML = \"\";\n  });\n\niframe.style.display = \"block\"\n\n\"LIA: wait\"\n\u003c/script\u003e\n\n\u003ciframe\n  id=\"simulator_@0\"\n  src=\"https://python-simulator.usermbit.org/v/0.1/simulator.html?color=green\"\n  title=\"Simulator\"\n  frameborder=\"0\"\n  scrolling=\"no\"\n  sandbox=\"allow-scripts allow-same-origin\"\n  style=\"width: 100%; max-width: 500px; aspect-ratio: 10 / 8; display: none\"\n\u003e\u003c/iframe\u003e\n\n\u003cdiv id=\"sensors_@0\" class=\"sensor-ui\"\u003e\u003c/div\u003e\n@end\n\n--\u003e\n\n# MicroBit-Simulator\n\n\n                          --{{0}}--\nThe MicroBit-Simulator allows you to run MicroPython code in a browser for the simulator of the BBC MicroBit.\n\n\n__Try it on LiaScript:__\n\nhttps://liascript.github.io/course/?https://raw.githubusercontent.com/liaTemplates/MicroBit-Simulator/main/README.md\n\n__See the project on Github:__\n\nhttps://github.com/liaTemplates/MicroBit-Simulator\n\n                         --{{1}}--\nLike with other LiaScript templates, there are three ways to integrate\nWebSerial, but the easiest way is to copy the import statement into your project.\nSee the implementation in [Sec. Implementation](#implementation).\n\n                           {{1}}\n1. Load the latest macros via (this might cause breaking changes)\n\n   `import: https://raw.githubusercontent.com/liaTemplates/MicroBit-Simulator/main/README.md`\n\n   or the current version 0.0.1 via:\n\n   `import: https://raw.githubusercontent.com/LiaTemplates/MicroBit-Simulator/0.0.1/README.md`\n\n2. __Copy the definitions into your Project__\n\n3. Clone this repository on GitHub\n\n                          --{{2}}--\nAdditionally it is recommended to set the `persistent` option to `true` in the\nLiaScript course. This will will prevent the simulator from being destroyed if a new slide is loaded.\n\n    {{2}}\n```` markdown\n\u003c!--\n...\npersistent: true\n--\u003e\n\n# title ...\n````\n\n\n## `@microbit`\n\n                     --{{0}}--\nThe `@microbit` macro attached to a LiaScript code-block will instantiate a simulator, whereby the user has to activate the simulator at first use. It is possible to touch the buttons or to change the display...\n\n```` markdown\n``` python\nfrom microbit import *\n\nwhile True:\n    if button_a.was_pressed():\n        print(\"A\")\n        display.show('A')\n    if button_b.was_pressed():\n        print(\"B\")\n        display.show('B')\n```\n@microbit\n````\n\n__Result:__\n\n\nButtons\n\n``` python\nfrom microbit import *\n\nwhile True:\n    if button_a.was_pressed():\n        print(\"A\")\n        display.show('A')\n    if button_b.was_pressed():\n        print(\"B\")\n        display.show('B')\n```\n@microbit\n\n\n## `@microbit.withSensorUI`\n\n                     --{{0}}--\nThe `@microbit.withSensorUI` macro attached to a LiaScript code-block will instantiate a simulator with a sensor UI. The user can change the values of the sensors in the simulator.\n\n```` markdown\n``` python\nfrom microbit import *\n\nwhile True:\n    print(display.read_light_level())\n    sleep(1000);\n```\n@microbit.withSensorUI\n````\n\n__Result:__\n\n``` python\nfrom microbit import *\n\nwhile True:\n    print(display.read_light_level())\n    sleep(1000);\n```\n@microbit.withSensorUI\n\n\n## Examples\n\n### Accelerometer\n\n``` python\nfrom microbit import *\n\nlast = None\nwhile True:\n    current = accelerometer.current_gesture()\n    if current != last:\n        last = current\n        print(current)\n```\n@microbit.withSensorUI\n\n\n### Audio\n\n\n``` python\n# micro:bit demo, playing a sine wave using AudioFrame's\n# This doesn't sound great in the sim or on V2.\n# The time per frame is significantly higher in the sim.\n\nimport math\nimport audio\nfrom microbit import pin0, running_time\n\nprint(\"frames should take {} ms to play\".format(32 / 7812.5 * 1000))\n\n\ndef repeated_frame(frame, count):\n    for i in range(count):\n        yield frame\n\n\nframe = audio.AudioFrame()\nnframes = 100\n\nfor freq in range(2, 17, 2):\n    l = len(frame)\n    for i in range(l):\n        frame[i] = int(127 * (math.sin(math.pi * i / l * freq) + 1))\n    print(\"play tone at frequency {}\".format(freq * 7812.5 / 32))\n    t0 = running_time()\n    audio.play(repeated_frame(frame, nframes), wait=True, pin=pin0)\n    dt_ms = running_time() - t0\n    print(\"{} frames took {} ms = {} ms/frame\".format(nframes, dt_ms, dt_ms / nframes))\n\n```\n@microbit\n\n\n### Background music and display\n\n\n``` python\nfrom microbit import *\nimport music\n\nmusic.play(['f', 'a', 'c', 'e'], wait=False)\ndisplay.scroll(\"Music and scrolling in the background\", wait=False)\nprint(\"This should be printed before the scroll and play complete\")\n```\n@microbit\n\n\n### Compass\n\n``` python\n# Imports go at the top\nfrom microbit import *\n\n# Code in a 'while True:' loop repeats forever\nwhile True:\n    print(\"x: \", compass.get_x())\n    print(\"y: \", compass.get_y())\n    print(\"z: \", compass.get_z())\n    print(\"heading: \", compass.heading())\n    print(\"field strength: \", compass.get_field_strength())\n    sleep(1000)\n```\n@microbit.withSensorUI\n\n### Data logging\n\n``` python\n# Imports go at the top\nfrom microbit import *\nimport log\n\n\n# Code in a 'while True:' loop repeats forever\nwhile True:\n    log.set_labels('temperature', 'sound', 'light')\n    log.add({\n      'temperature': temperature(),\n      'sound': microphone.sound_level(),\n      'light': display.read_light_level()\n    })\n    sleep(1000)\n```\n@microbit.withSensorUI\n\n### Display\n\n``` python\nfrom microbit import *\n\ndisplay.scroll(\"Hello\")\ndisplay.scroll(\"World\", wait=False)\nfor _ in range(0, 50):\n    print(display.get_pixel(2, 2))\n    sleep(100)\ndisplay.show(Image.HEART)\nsleep(1000)\ndisplay.clear()\n```\n@microbit\n\n### Inline assembler\n\n``` python\nfrom microbit import *\n\n# Unsupported in the simulator\n@micropython.asm_thumb\ndef asm_add(r0, r1):\n    add(r0, r0, r1)\n\nwhile True:\n    if button_a.was_pressed():\n        print(1 + 2)\n    elif button_b.was_pressed():\n        print(asm_add(1, 2))\n```\n@microbit\n\n### Microphone\n\n``` python\nfrom microbit import *\n\nwhile True:\n    print(microphone.sound_level())\n    if microphone.was_event(SoundEvent.LOUD):\n        display.show(Image.HAPPY)\n    elif microphone.current_event() == SoundEvent.QUIET:\n        display.show(Image.ASLEEP)\n    else:\n        display.clear()\n    sleep(500)\n```\n@microbit.withSensorUI\n\n\n### Music\n\n``` python\nimport music\n\nmusic.play(['f', 'a', 'c', 'e'])\n```\n@microbit\n\n### Pin logo\n\n``` python\nfrom microbit import *\n\nwhile True:\n    while pin_logo.is_touched():\n        display.show(Image.HAPPY)\n    display.clear()\n```\n@microbit\n\n\n### Radio\n\n``` python\nfrom microbit import *\nimport radio\n\n\nradio.on();\nwhile True:\n    message = radio.receive_bytes()\n    if message:\n        print(message)\n```\n@microbit.withSensorUI\n\n\n### Random\n\n``` python\nfrom microbit import display, sleep\nimport random\nimport machine\n\n\nn = random.randrange(0, 100)\nprint(\"Default seed \", n)\nrandom.seed(1)\nn = random.randrange(0, 100)\nprint(\"Fixed seed \", n)\nprint()\nsleep(2000)\nmachine.reset()\n```\n@microbit\n\n\n### Sensors\n\n``` python\nfrom microbit import *\n\nwhile True:\n    print(display.read_light_level())\n    sleep(1000);\n```\n@microbit.withSensorUI\n\n### Sensor effects (builtin)\n\n``` python\nfrom microbit import *\n\n# You don't hear this one because we don't wait then play another which stops this one.\ndisplay.show(Image.HAPPY)\naudio.play(Sound.GIGGLE, wait=False)\naudio.play(Sound.GIGGLE)\ndisplay.show(Image.SAD) # This doesn't happen until the giggling is over.\naudio.play(Sound.SAD)\n```\n@microbit\n\n### Sensor effects (custom)\n\n``` python\nfrom microbit import *\n\n# Create a Sound Effect and immediately play it\naudio.play(\n    audio.SoundEffect(\n        freq_start=400,\n        freq_end=2000,\n        duration=500,\n        vol_start=100,\n        vol_end=255,\n        waveform=audio.SoundEffect.WAVEFORM_TRIANGLE,\n        fx=audio.SoundEffect.FX_VIBRATO,\n        shape=audio.SoundEffect.SHAPE_LOG,\n    )\n)\n```\n@microbit\n\n### Speech\n\n``` python\nimport speech\nimport time\n\nt = time.ticks_ms()\nspeech.pronounce(' /HEHLOW OHP AHL')\nprint(time.ticks_diff(time.ticks_ms(), t))\n```\n@microbit\n\n### Stacksize\n\n``` python\ndef g():\n    global depth\n    depth += 1\n    g()\ndepth = 0\ntry:\n    g()\nexcept RuntimeError:\n    pass\nprint('maximum recursion depth g():', depth)\n```\n@microbit\n\n### Volume\n\n``` python\nfrom microbit import *\nimport music\n\n\nmusic.pitch(440, wait=False)\nwhile True:\n  # Conveniently both 0..255.\n  set_volume(display.read_light_level())\n```\n@microbit.withSensorUI\n\n\n## Implementation\n\n```` javascript\nlink:  style.css\n\n@onload\nwindow.createSensorUI = function (simulator, selector, state) {\n  const parent = document.querySelector(selector)\n  parent.classList.add('sensor-ui')\n  parent.innerHTML = ''\n\n  function onChange(id, value) {\n    simulator.postMessage({ kind: 'set_value', id, value }, '*')\n  }\n\n  function makeCard(id) {\n    const card = document.createElement('div')\n    card.className = 'sensor-card'\n    const label = document.createElement('span')\n    label.innerText = id\n    card.appendChild(label)\n    return card\n  }\n\n  for (const sensor of Object.values(state)) {\n    const { type, id } = sensor\n    const card = makeCard(id)\n\n    if (type === 'range') {\n      const input = document.createElement('input')\n      input.type = 'range'\n      input.min = sensor.min\n      input.max = sensor.max\n      input.value = sensor.value\n      input.addEventListener('input', (e) =\u003e onChange(id, e.target.value))\n      card.appendChild(input)\n    } else if (type === 'enum') {\n      const select = document.createElement('select')\n      for (const choice of sensor.choices) {\n        const opt = new Option(\n          choice,\n          choice,\n          choice === sensor.value,\n          choice === sensor.value\n        )\n        select.appendChild(opt)\n      }\n      select.addEventListener('change', (e) =\u003e onChange(id, e.target.value))\n      card.appendChild(select)\n    } else {\n      const info = { ...sensor }\n      delete info.type\n      const infoCode = document.createElement('code')\n      infoCode.innerText = JSON.stringify(info)\n      card.appendChild(infoCode)\n    }\n\n    parent.appendChild(card)\n  }\n}\n\n@end\n\n\n@microbit: @microbit_(@uid,```@input```,false)\n\n@microbit.withSensorUI: @microbit_(@uid,```@input```,true)\n\n@microbit_\n\u003cscript\u003e\nconst iframe = document.querySelector('#simulator_@0')\nconst simulator = iframe.contentWindow\nlet state = {}\n\nconst onMessage = (e) =\u003e {\n    if (e.source !== simulator) return;\n    const { data } = e;\n    switch (data.kind) {\n      case 'serial_output':\n        console.stream(data.data);\n        break;\n      case 'log_output':\n        if (data.headings) console.log(data.headings);\n        if (data.data)     console.log(data.data);\n        break;\n      case 'radio_output':\n        const text = new TextDecoder()\n          .decode(e.data.data)\n          .replace(/^\\x01\\x00\\x01/, '')\n        console.log(text)\n        break\n      case 'ready':\n        state = data.state\n        window.console.log('ready', data.state);\n        if(@2) {\n          createSensorUI(simulator, \"#sensors_@0\", state)\n        }\n        break\n      case 'state_change':\n        state = {\n          ...state,\n          ...data.change,\n        }\n        if(@2) {\n          createSensorUI(simulator, \"#sensors_@0\", state)\n        }\n        break\n      case 'log_delete':\n        console.debug('[log_delete]');\n        break;\n      case 'request_flash':\n        simulator.postMessage({\n          kind: 'flash',\n          filesystem: {\n            'main.py': new TextEncoder().encode(`@1`),\n          },\n        }, '*');\n        simulator.postMessage({\n          kind: 'reset',\n        }, '*');\n        send.lia(\"LIA: terminal\");\n        break;\n      case 'internal_error':\n        console.error('Simulator internal error:', data.error);\n        break;\n      default:\n        console.warn('Unknown message kind:', data.kind);\n        break;\n    }\n  };\n\nwindow.addEventListener('message', onMessage);\n\nsend.handle(\"input\", (data) =\u003e {\n  simulator.postMessage({\n      kind: 'serial_input',\n      data: data + \"\\r\\n\",\n    },\n    '*'\n  )\n})\n\nsend.handle(\"stop\", () =\u003e {\n    // detach the message listener\n    window.removeEventListener('message', onMessage);\n\n    // reset the iframe by reloading its src\n    // (you can also set src=\"\" then back, or completely remove/add the element)\n    iframe.src = iframe.src;\n\n    // optionally hide it if you want:\n    iframe.style.display = \"none\";\n\n    document.getElementById(\"sensors_@0\").innerHTML = \"\";\n  });\n\niframe.style.display = \"block\"\n\n\"LIA: wait\"\n\u003c/script\u003e\n\n\u003ciframe\n  id=\"simulator_@0\"\n  src=\"https://python-simulator.usermbit.org/v/0.1/simulator.html?color=green\"\n  title=\"Simulator\"\n  frameborder=\"0\"\n  scrolling=\"no\"\n  sandbox=\"allow-scripts allow-same-origin\"\n  style=\"width: 100%; max-width: 500px; aspect-ratio: 10 / 8; display: none\"\n\u003e\u003c/iframe\u003e\n\n\u003cdiv id=\"sensors_@0\" class=\"sensor-ui\"\u003e\u003c/div\u003e\n@end\n````\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliatemplates%2Fmicrobit-simulator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fliatemplates%2Fmicrobit-simulator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliatemplates%2Fmicrobit-simulator/lists"}