Skip to content

Commit

Permalink
feat: ESP32 MicroPython (#108)
Browse files Browse the repository at this point in the history
  • Loading branch information
sverben authored Jul 10, 2024
1 parent dbbd6b2 commit df60353
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 57 deletions.
3 changes: 2 additions & 1 deletion src/assets/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,5 +122,6 @@
"EXPLANATION": "Explanation",
"EXPLAIN_BLOCK": "Explain Block ✨",
"META_ATTRIBUTION": "Built with Meta Llama 3",
"AI_RATE_LIMITED": "The explanation robot is a little too busy right now, please try again later."
"AI_RATE_LIMITED": "The explanation robot is a little too busy right now, please try again later.",
"CHOOSING_ROBOT": "Choose your board"
}
3 changes: 2 additions & 1 deletion src/assets/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,5 +122,6 @@
"EXPLANATION": "Uitleg",
"EXPLAIN_BLOCK": "Leg blok uit ✨",
"META_ATTRIBUTION": "Gemaakt met Meta Llama 3",
"AI_RATE_LIMITED": "De uitleg robot is op dit moment erg druk, probeer het later nog eens."
"AI_RATE_LIMITED": "De uitleg robot is op dit moment erg druk, probeer het later nog eens.",
"CHOOSING_ROBOT": "Kies je arduino"
}
3 changes: 1 addition & 2 deletions src/lib/components/core/header/Header.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Select from "$components/ui/Select.svelte";
import { loadWorkspaceFromString } from "$domain/blockly/blockly";
import { FileHandle } from "$domain/handles";
import { robots } from "$domain/robots";
import { Screen, Theme, screen, selected, theme } from "$state/app.svelte";
import { Screen, Theme, screen, theme } from "$state/app.svelte";
import {
audio,
canRedo,
Expand Down Expand Up @@ -85,7 +85,6 @@ async function connect() {
async function newProject() {
popups.clear();
willRestore.set(false);
selected.set(null);
screen.set(Screen.START);
}
Expand Down
47 changes: 32 additions & 15 deletions src/lib/components/core/popups/popups/PythonUploader.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<script lang="ts">
import { _ } from "svelte-i18n";
import RobotSelector from "$components/start/RobotSelector.svelte";
import Button from "$components/ui/Button.svelte";
import ProgressBar from "$components/ui/ProgressBar.svelte";
import { type RobotDevice, robots } from "$domain/robots";
import { type PopupState, popups } from "$state/popup.svelte";
import { usbRequest } from "$state/upload.svelte";
import {
Expand All @@ -13,7 +15,7 @@ import {
robot,
} from "$state/workspace.svelte";
import { getContext, onMount } from "svelte";
import type { Writable } from "svelte/store";
import { type Writable, get } from "svelte/store";
import type MicroPythonIO from "../../../../micropython";
interface Props {
Expand All @@ -25,6 +27,7 @@ let progress = $state(0);
let currentState = $state("CONNECTING");
let error = $state<string | null>(null);
let done = $state(false);
let robotRequest = $state<(robot: RobotDevice) => void>();
class UploadError extends Error {
constructor(
Expand All @@ -49,32 +52,43 @@ async function upload(res: Record<string, string>) {
onMount(async () => {
try {
const installed = await io.enterREPLMode();
progress += 100 / 5;
progress += 100 / 6;
currentState = "CHOOSING_ROBOT";
if (!installed) {
const newRobot = await new Promise<RobotDevice>(
(resolve) => (robotRequest = resolve),
);
robot.set(newRobot);
}
robotRequest = undefined;
progress += 100 / 6;
currentState = "DOWNLOADING_FIRMWARE";
let firmware: Record<string, string>;
if (!installed) firmware = await io.getFirmware();
progress += 100 / 5;
if (!installed) firmware = await io.getFirmware(get(robot));
progress += 100 / 6;
currentState = "UPLOADING_FIRMWARE";
if (!installed) await upload(firmware);
progress += 100 / 5;
progress += 100 / 6;
currentState = "CONNECTING";
if (!installed) await io.enterREPLMode();
progress += 100 / 5;
progress += 100 / 6;
currentState = "INSTALLING_LIBRARIES";
await io.packageManager.flashLibrary(
"github:leaphy-robotics/leaphy-micropython/package.json",
);
progress += 100 / 5;
progress += 100 / 6;
popups.close($popupState.id);
} catch (e) {
done = true;
currentState = "UPLOAD_FAILED";
currentState = e?.name || "UPDATE_FAILED";
error = e.description;
throw e;
}
});
Expand All @@ -95,15 +109,15 @@ async function connectUSB() {
);
}
</script>

<div class="content" class:error={!!error}>
{#if $usbRequest}
<h2 class="state">{$_("RECONNECT")}</h2>
<div class="info">{$_("RECONNECT_INFO")}</div>
<Button name={"Reconnect"} mode={"primary"} onclick={connectUSB} />
{:else}
<h2 class="state">{$_(currentState)}</h2>

{#if error}
<code class="error-result">{error}</code>
{/if}
Expand All @@ -115,15 +129,19 @@ async function connectUSB() {
/>
{:else}
<ProgressBar {progress} />

{#if robotRequest}
<RobotSelector onselect="{robotRequest}" robots={[[robots.l_nano_rp2040, robots.l_nano_esp32]]} secondary="{false}" compact />
{/if}
{/if}
{/if}
</div>

<style>
h2 {
margin: 0;
}
.content {
display: flex;
flex-direction: column;
Expand All @@ -136,11 +154,11 @@ async function connectUSB() {
min-height: 200px;
max-height: 80vh;
}
.state {
font-weight: bold;
}
.error h2 {
color: red;
}
Expand All @@ -151,4 +169,3 @@ async function connectUSB() {
color: red;
}
</style>

47 changes: 18 additions & 29 deletions src/lib/components/start/RobotSelector.svelte
Original file line number Diff line number Diff line change
@@ -1,46 +1,31 @@
<script lang="ts">
import { type Robot, robots as allRobots } from "$domain/robots";
import { Screen, screen, selected } from "$state/app.svelte";
import { restore } from "$state/blockly.svelte";
import { Mode, code, mode, robot, saveState } from "$state/workspace.svelte";
import type { Robot, RobotListing } from "$domain/robots";
interface Props {
robots: Robot[][];
secondary: boolean;
onselect: (robot: Robot) => void;
selected?: RobotListing;
compact?: boolean;
}
const { robots, secondary }: Props = $props();
function select(type: Robot) {
window._paq.push(["trackEvent", "SelectRobot", type.name]);
if ("variants" in type) return selected.set(type);
if ("mode" in type) {
code.set(localStorage.getItem(`session_${type.id}`) || type.defaultProgram);
robot.set(allRobots[type.defaultRobot]);
mode.set(type.mode);
screen.set(Screen.WORKSPACE);
saveState.set(true);
return;
}
if (localStorage.getItem(`session_blocks_${type.id}`)) {
restore.set(JSON.parse(localStorage.getItem(`session_blocks_${type.id}`)));
}
robot.set(type);
mode.set(Mode.BLOCKS);
screen.set(Screen.WORKSPACE);
}
const {
robots,
secondary,
onselect,
selected,
compact = false,
}: Props = $props();
</script>

<div class="selector" class:secondary>
<div class="selector" class:secondary class:compact>
{#each robots as row}
<div class="row">
{#each row as robot}
<button
class="robot"
onclick={() => select(robot)}
class:selected={$selected?.id === robot.id}
onclick={() => onselect(robot)}
class:selected={selected?.id === robot.id}
>
<span class="icon">
<img class="image" src={robot.icon} alt={robot.name}/>
Expand All @@ -63,6 +48,10 @@ function select(type: Robot) {
gap: 3vh;
}
.selector.compact {
width: unset;
}
.secondary {
background: var(--background);
}
Expand Down
37 changes: 33 additions & 4 deletions src/lib/components/start/Start.svelte
Original file line number Diff line number Diff line change
@@ -1,18 +1,47 @@
<script lang="ts">
import RobotSelector from "$components/start/RobotSelector.svelte";
import { robotListing } from "$domain/robots";
import { selected } from "$state/app.svelte";
import {
type Robot,
type RobotListing,
robots as allRobots,
robotListing,
} from "$domain/robots";
import { Screen, screen } from "$state/app.svelte";
import { restore } from "$state/blockly.svelte";
import { Mode, code, mode, robot, saveState } from "$state/workspace.svelte";
import { flip } from "svelte/animate";
import { cubicOut } from "svelte/easing";
import { fly } from "svelte/transition";
let selected = $state<RobotListing>();
const selectors = $derived(
$selected ? [robotListing, $selected.variants] : [robotListing],
selected ? [robotListing, selected.variants] : [robotListing],
);
const animationOptions = {
easing: cubicOut,
duration: 300,
};
function onselect(type: Robot) {
window._paq.push(["trackEvent", "SelectRobot", type.name]);
if ("variants" in type) return (selected = type);
if ("mode" in type) {
code.set(localStorage.getItem(`session_${type.id}`) || type.defaultProgram);
robot.set(allRobots[type.defaultRobot]);
mode.set(type.mode);
screen.set(Screen.WORKSPACE);
saveState.set(true);
return;
}
if (localStorage.getItem(`session_blocks_${type.id}`)) {
restore.set(JSON.parse(localStorage.getItem(`session_blocks_${type.id}`)));
}
robot.set(type);
mode.set(Mode.BLOCKS);
screen.set(Screen.WORKSPACE);
}
</script>

<div class="start">
Expand All @@ -21,7 +50,7 @@ const animationOptions = {
in:fly={{ x: "100%", ...animationOptions }}
animate:flip={animationOptions}
>
<RobotSelector {robots} secondary={i > 0} />
<RobotSelector {onselect} {robots} {selected} secondary={i > 0} />
</div>
{/each}
</div>
Expand Down
16 changes: 12 additions & 4 deletions src/lib/micropython/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import PythonUploader from "$components/core/popups/popups/PythonUploader.svelte";
import Uploader from "$components/core/popups/popups/Uploader.svelte";
import type { RobotDevice } from "$domain/robots";
import { popups } from "$state/popup.svelte";
import { port } from "$state/workspace.svelte";
import base64 from "base64-js";
import { get } from "svelte/store";
import { delay } from "../programmers/utils";
import { Commands } from "./commands";
import { FileSystem } from "./filesystem";
import { PackageManager } from "./packagagemanager";
Expand Down Expand Up @@ -73,10 +75,15 @@ export default class MicroPythonIO {
});
}

async getFirmware() {
const res = await fetch(
"https://raw.githubusercontent.com/leaphy-robotics/leaphy-firmware/main/micropython/firmware.uf2",
);
async getFirmware(robot: RobotDevice) {
const firmwareSources = {
l_nano_esp32:
"https://raw.githubusercontent.com/leaphy-robotics/leaphy-firmware/main/micropython/esp32.bin",
l_nano_rp2040:
"https://raw.githubusercontent.com/leaphy-robotics/leaphy-firmware/main/micropython/firmware.uf2",
};

const res = await fetch(firmwareSources[robot.id]);
const content = await res.arrayBuffer();

return {
Expand Down Expand Up @@ -126,6 +133,7 @@ export default class MicroPythonIO {
const data = encoder.encode(`${code}\x04`);
for (let offset = 0; offset < data.length; offset += 256) {
await this.writer.write(data.slice(offset, offset + 256));
await delay(5);
}

let content = "";
Expand Down
1 change: 0 additions & 1 deletion src/lib/state/app.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,4 @@ function createLibraryState() {
}

export const screen = writable<ComponentType>(Screen.START);
export const selected = writable<RobotListing | null>(null);
export const libraries = createLibraryState();

0 comments on commit df60353

Please sign in to comment.