From 9969dd00247f3024276a8d592e2a0962947b1c9b Mon Sep 17 00:00:00 2001 From: c2coder Date: Sun, 7 Jul 2024 22:23:48 +0200 Subject: [PATCH 01/14] Add library to docs with index file --- .../Robutek-library/@types/adc.d.ts | 24 ++ .../Robutek-library/@types/basicStream.d.ts | 22 ++ .../Robutek-library/@types/fs.d.ts | 80 +++++ .../Robutek-library/@types/gpio.d.ts | 49 +++ .../Robutek-library/@types/gridui.d.ts | 321 ++++++++++++++++++ .../Robutek-library/@types/i2c.d.ts | 34 ++ .../Robutek-library/@types/ledc.d.ts | 44 +++ .../Robutek-library/@types/misc.d.ts | 5 + .../Robutek-library/@types/path.d.ts | 36 ++ .../Robutek-library/@types/platform.d.ts | 6 + .../Robutek-library/@types/pulseCounter.d.ts | 64 ++++ .../Robutek-library/@types/simpleradio.d.ts | 84 +++++ .../Robutek-library/@types/smartled.d.ts | 55 +++ .../Robutek-library/@types/stdio.d.ts | 13 + .../Robutek-library/@types/timers.d.ts | 31 ++ .../Robutek-library/@types/timestamp.d.ts | 11 + .../Robutek-library/@types/wifi.d.ts | 6 + .../Robutek-library/build/index.js | 182 ++++++++++ .../Robutek-library/build/libs/colors.js | 57 ++++ .../build/libs/custom_motors.js | 40 +++ .../Robutek-library/build/libs/motors.js | 0 .../Robutek-library/build/libs/readline.js | 66 ++++ .../Robutek-library/build/libs/robot.js | 114 +++++++ .../Robutek-library/build/libs/servo.js | 28 ++ .../Robutek-library/src/index.ts | 42 +++ .../Robutek-library/src/libs/colors.ts | 81 +++++ .../Robutek-library/src/libs/motors.ts | 42 +++ .../Robutek-library/src/libs/readline.ts | 84 +++++ .../Robutek-library/src/libs/robot.ts | 219 ++++++++++++ .../Robutek-library/src/libs/servo.ts | 34 ++ .../Robutek-library/src/libs/simplemotors.ts | 47 +++ .../Robutek-library/tsconfig.json | 11 + docs/robutekLibrary/index.md | 3 + mkdocs.yml | 1 + 34 files changed, 1936 insertions(+) create mode 100644 docs/robutekLibrary/Robutek-library/@types/adc.d.ts create mode 100644 docs/robutekLibrary/Robutek-library/@types/basicStream.d.ts create mode 100644 docs/robutekLibrary/Robutek-library/@types/fs.d.ts create mode 100644 docs/robutekLibrary/Robutek-library/@types/gpio.d.ts create mode 100644 docs/robutekLibrary/Robutek-library/@types/gridui.d.ts create mode 100644 docs/robutekLibrary/Robutek-library/@types/i2c.d.ts create mode 100644 docs/robutekLibrary/Robutek-library/@types/ledc.d.ts create mode 100644 docs/robutekLibrary/Robutek-library/@types/misc.d.ts create mode 100644 docs/robutekLibrary/Robutek-library/@types/path.d.ts create mode 100644 docs/robutekLibrary/Robutek-library/@types/platform.d.ts create mode 100644 docs/robutekLibrary/Robutek-library/@types/pulseCounter.d.ts create mode 100644 docs/robutekLibrary/Robutek-library/@types/simpleradio.d.ts create mode 100644 docs/robutekLibrary/Robutek-library/@types/smartled.d.ts create mode 100644 docs/robutekLibrary/Robutek-library/@types/stdio.d.ts create mode 100644 docs/robutekLibrary/Robutek-library/@types/timers.d.ts create mode 100644 docs/robutekLibrary/Robutek-library/@types/timestamp.d.ts create mode 100644 docs/robutekLibrary/Robutek-library/@types/wifi.d.ts create mode 100755 docs/robutekLibrary/Robutek-library/build/index.js create mode 100644 docs/robutekLibrary/Robutek-library/build/libs/colors.js create mode 100644 docs/robutekLibrary/Robutek-library/build/libs/custom_motors.js create mode 100644 docs/robutekLibrary/Robutek-library/build/libs/motors.js create mode 100644 docs/robutekLibrary/Robutek-library/build/libs/readline.js create mode 100644 docs/robutekLibrary/Robutek-library/build/libs/robot.js create mode 100644 docs/robutekLibrary/Robutek-library/build/libs/servo.js create mode 100644 docs/robutekLibrary/Robutek-library/src/index.ts create mode 100644 docs/robutekLibrary/Robutek-library/src/libs/colors.ts create mode 100644 docs/robutekLibrary/Robutek-library/src/libs/motors.ts create mode 100644 docs/robutekLibrary/Robutek-library/src/libs/readline.ts create mode 100644 docs/robutekLibrary/Robutek-library/src/libs/robot.ts create mode 100644 docs/robutekLibrary/Robutek-library/src/libs/servo.ts create mode 100644 docs/robutekLibrary/Robutek-library/src/libs/simplemotors.ts create mode 100644 docs/robutekLibrary/Robutek-library/tsconfig.json create mode 100644 docs/robutekLibrary/index.md diff --git a/docs/robutekLibrary/Robutek-library/@types/adc.d.ts b/docs/robutekLibrary/Robutek-library/@types/adc.d.ts new file mode 100644 index 00000000..871de201 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/@types/adc.d.ts @@ -0,0 +1,24 @@ +declare module "adc" { + type Atten = number; + + const Attenuation: { + readonly Db0: Atten; + readonly Db2_5: Atten; + readonly Db6: Atten; + readonly Db11: Atten; + }; + + + /** + * Enable ADC on the given pin. + * @param pin The pin to enable ADC on. + */ + function configure(pin: number, attenuation?: Atten): void; + + /** + * Read the value of the given pin. + * @param pin The pin to read. + * @returns The value of the pin (0-1023) + */ + function read(pin: number): number; +} diff --git a/docs/robutekLibrary/Robutek-library/@types/basicStream.d.ts b/docs/robutekLibrary/Robutek-library/@types/basicStream.d.ts new file mode 100644 index 00000000..70c9e06f --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/@types/basicStream.d.ts @@ -0,0 +1,22 @@ +declare interface Writable { + /** + * Write the given data to the stream. + * @param data The data to write. + */ + write(data: string): void; +} + +declare interface Readable { + /** + * Read a single character from the stream. + * @returns Promise that resolves to the character read. + */ + get(): Promise; + + /** + * Read a chunk of data from the stream. The size of the chunk is + * given by the implementation and available data. + * @returns Promise that resolves to the data read. + */ + read(): Promise; +} diff --git a/docs/robutekLibrary/Robutek-library/@types/fs.d.ts b/docs/robutekLibrary/Robutek-library/@types/fs.d.ts new file mode 100644 index 00000000..a21dc650 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/@types/fs.d.ts @@ -0,0 +1,80 @@ +declare module "fs" { + interface File { + path: string; + + /** + * Check if the file is open. + */ + isOpen(): boolean; + + /** + * Close the file. + */ + close(): void; + + /** + * Read characters from the file. + * @param len The number of characters to read. + */ + read(len: number): string; + + /** + * Write text to the file. + * @param text The text to write. + */ + write(text: string): void; + } + + /** + * Open the given file in the given mode. + * @param path The path to the file. + * @param mode The mode to open the file in ("r", "w", "a" and combinations). + */ + function open(path: string, mode: string): File; + + /** + * Check if the given path exists. + * @param path The path to check. + * @returns True if the path exists, false otherwise. + */ + function exists(path: string): boolean; + + /** + * Check if the given path is a file. + * @param path The path to check. + * @returns True if the path is a file, false otherwise. + */ + function isFile(path: string): boolean; + + /** + * Check if the given path is a directory. + * @param path The path to check. + * @returns True if the path is a directory, false otherwise. + */ + function isDirectory(path: string): boolean; + + /** + * Create a directory at the given path. + * @param path The path to create the directory at. + */ + function mkdir(path: string): void; + + /** + * Remove the file at the given path. + * @param path The path to the file to remove. + */ + function rm(path: string): void; + + /** + * Remove the directory at the given path. + * @param path The path to the directory to remove. + */ + function rmdir(path: string): void; + + /** + * List the files in the given directory. + * @param path The path to the directory to list. + * @returns An array of file names in the directory. + */ + function readdir(path: string): string[]; +} diff --git a/docs/robutekLibrary/Robutek-library/@types/gpio.d.ts b/docs/robutekLibrary/Robutek-library/@types/gpio.d.ts new file mode 100644 index 00000000..09c167db --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/@types/gpio.d.ts @@ -0,0 +1,49 @@ +declare module "gpio" { + const PinMode: { + readonly DISABLE: number, + readonly OUTPUT: number, + readonly INPUT: number, + readonly INPUT_PULLUP: number, + readonly INPUT_PULLDOWN: number, + }; + + interface EventInfo { + timestamp: Timestamp; + } + + /** + * Configure the given pin. + * @param pin The pin to configure. + * @param mode The mode to configure the pin in. + */ + function pinMode(pin: number, mode: number): void; + + /** + * Write digital value to the given pin. + * @param pin The pin to write to. + * @param value The value to write. + */ + function write(pin: number, value: number): void; + + /** + * Read digital value from the given pin. + * @param pin The pin to read from. + * @returns The value of the pin (0 or 1). + */ + function read(pin: number): number; + + /** + * Set event handler for the given pin. + * @param event The event to handle. + * @param pin The pin to handle the event for. + * @param callback The callback to call when the event occurs. + */ + function on(event: "rising" | "falling" | "change", pin: number, callback: (info: EventInfo) => void): void; + + /** + * Remove event handler for the given pin. + * @param event The event to remove. + * @param pin The pin to remove the event handler for. + */ + function off(event: "rising" | "falling" | "change", pin: number): void; +} diff --git a/docs/robutekLibrary/Robutek-library/@types/gridui.d.ts b/docs/robutekLibrary/Robutek-library/@types/gridui.d.ts new file mode 100644 index 00000000..8ffaba74 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/@types/gridui.d.ts @@ -0,0 +1,321 @@ +declare module "gridui" { + namespace widget { + interface Base { + readonly uuid: number + widgetX: number + widgetY: number + widgetW: number + widgetH: number + widgetTab: number + css(key: string): string + setCss(key: string, value: string): void + } + + interface Arm extends Base { + readonly x: number + readonly y: number + } + + interface Bar extends Base { + color: string + fontSize: number + min: number + max: number + value: number + showValue: boolean + } + + interface Button extends Base { + text: string + fontSize: number + color: string + background: string + align: string + valign: string + disabled: boolean + readonly pressed: boolean + } + + interface Camera extends Base { + rotation: number + clip: boolean + } + + interface Checkbox extends Base { + fontSize: number + checked: boolean + color: string + text: string + } + + interface Circle extends Base { + color: string + fontSize: number + min: number + max: number + lineWidth: number + valueStart: number + value: number + showValue: boolean + } + + interface Input extends Base { + text: string + color: string + type: string + disabled: boolean + } + + interface Joystick extends Base { + color: string + keys: string + text: string + readonly x: number + readonly y: number + } + + interface Led extends Base { + color: string + on: boolean + } + + interface Orientation extends Base { + color: string + readonly yaw: number + readonly pitch: number + readonly roll: number + readonly joystickX: number + readonly joystickY: number + } + + interface Select extends Base { + color: string + background: string + disabled: boolean + options: string + selectedIndex: number + } + + interface Slider extends Base { + color: string + fontSize: number + min: number + max: number + value: number + precision: number + showValue: boolean + } + + interface SpinEdit extends Base { + fontSize: number + color: string + value: number + step: number + precision: number + } + + interface Switcher extends Base { + fontSize: number + color: string + value: number + min: number + max: number + } + + interface Text extends Base { + text: string + fontSize: number + color: string + background: string + align: string + valign: string + prefix: string + suffix: string + } + } + + namespace builder { + interface Base { + css(key: string, value: string): this + finish(): this + } + + interface Arm extends Base { + info(info: Record): Arm + + onGrab(callback: (arm: widget.Arm) => void): Arm + onPositionChanged(callback: (arm: widget.Arm) => void): Arm + } + + interface Bar extends Base { + color(color: string): Bar + fontSize(fontSize: number): Bar + min(min: number): Bar + max(max: number): Bar + value(value: number): Bar + showValue(showValue: boolean): Bar + } + + interface Button extends Base { + text(text: string): Button + fontSize(fontSize: number): Button + color(color: string): Button + background(background: string): Button + align(align: string): Button + valign(valign: string): Button + disabled(disabled: boolean): Button + + onPress(callback: (button: widget.Button) => void): Button + onRelease(callback: (button: widget.Button) => void): Button + } + + interface Camera extends Base { + rotation(rotation: number): Camera + clip(clip: boolean): Camera + tags(tags: any /* TODO: fix type */): Camera + } + + interface Checkbox extends Base { + fontSize(fontSize: number): Checkbox + checked(checked: boolean): Checkbox + color(color: string): Checkbox + text(text: string): Checkbox + + onChanged(callback: (checkbox: widget.Checkbox) => void): Checkbox + } + + interface Circle extends Base { + color(color: string): Circle + fontSize(fontSize: number): Circle + min(min: number): Circle + max(max: number): Circle + lineWidth(lineWidth: number): Circle + valueStart(valueStart: number): Circle + value(value: number): Circle + showValue(showValue: boolean): Circle + } + + interface Input extends Base { + text(text: string): Input + color(color: string): Input + type(type: string): Input + disabled(disabled: boolean): Input + + onChanged(callback: (input: widget.Input) => void): Input + } + + interface Joystick extends Base { + color(color: string): Joystick + keys(keys: string): Joystick + text(text: string): Joystick + + onClick(callback: (joystick: widget.Joystick) => void): Joystick + onPositionChanged(callback: (joystick: widget.Joystick) => void): Joystick + } + + interface Led extends Base { + color(color: string): Led + on(on: boolean): Led + } + + interface Orientation extends Base { + color(color: string): Orientation + + onPositionChanged(callback: (orientation: widget.Orientation) => void): Orientation + } + + interface Select extends Base { + color(color: string): Select + background(background: string): Select + disabled(disabled: boolean): Select + options(options: string): Select + selectedIndex(selectedIndex: number): Select + + onChanged(callback: (select: widget.Select) => void): Select + } + + interface Slider extends Base { + color(color: string): Slider + fontSize(fontSize: number): Slider + min(min: number): Slider + max(max: number): Slider + value(value: number): Slider + precision(precision: number): Slider + showValue(showValue: boolean): Slider + + onChanged(callback: (slider: widget.Slider) => void): Slider + } + + interface SpinEdit extends Base { + fontSize(fontSize: number): SpinEdit + color(color: string): SpinEdit + value(value: number): SpinEdit + step(step: number): SpinEdit + precision(precision: number): SpinEdit + + onChanged(callback: (spinEdit: widget.SpinEdit) => void): SpinEdit + } + + interface Switcher extends Base { + fontSize(fontSize: number): Switcher + color(color: string): Switcher + value(value: number): Switcher + min(min: number): Switcher + max(max: number): Switcher + + onChanged(callback: (switcher: widget.Switcher) => void): Switcher + } + + interface Text extends Base { + text(text: string): Text + fontSize(fontSize: number): Text + color(color: string): Text + background(background: string): Text + align(align: string): Text + valign(valign: string): Text + prefix(prefix: string): Text + suffix(suffix: string): Text + } + + interface Widget extends Base {} + } + + class Builder { + arm(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Arm + bar(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Bar + button(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Button + camera(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Camera + checkbox(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Checkbox + circle(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Circle + input(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Input + joystick(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Joystick + led(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Led + orientation(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Orientation + select(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Select + slider(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Slider + spinEdit(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.SpinEdit + switcher(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Switcher + text(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Text + widget(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Widget + } + + /** + * Initialize GridUI. + * @param ownerName name of the owner, must match the name entered in RBController app. + * @param deviceName name of this device, visible in the RBController app. + * @param builderCallback callback, which receives the builder instance that can be used to create widgets. + */ + function begin(ownerName: string, deviceName: string, builderCallback: (builder: Builder) => void): void + + /** + * Stop GridUI. + */ + function end(): void + + /** + * Returns included GridUI version as number, to be compared with hex representation of the version. + * + * For example, for version 5.1.0, do: `gridui.version() >= 0x050100` + */ + function version(): number +} diff --git a/docs/robutekLibrary/Robutek-library/@types/i2c.d.ts b/docs/robutekLibrary/Robutek-library/@types/i2c.d.ts new file mode 100644 index 00000000..fd6685a9 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/@types/i2c.d.ts @@ -0,0 +1,34 @@ +declare module "i2c" { + interface I2C { + /** + * Find an I2C interface by its pin. + * @param pin The pin the I2C interface is connected to. + * @returns The I2C interface, or undefined if not found. + */ + find(pin: number): I2C | undefined; + + /** + * Read from the given address. + * @param address The address to read from. + * @param quantity The number of bytes to read. + * @returns The bytes read. + */ + readFrom(address: number, quantity: number): Uint8Array; + + /** + * Write to the given address. + * @param address The address to write to. + * @param buffer The data to write. + */ + writeTo(address: number, buffer: ArrayBuffer | Uint8Array | number[] | string | number): void; + + /** + * Setup the I2C interface. + * @param options The options to use when setting up the I2C interface. + */ + setup(options: { scl?: number, sda?: number, bitrate?: number }): void; + } + + const I2C1: I2C; + const I2C2: I2C | undefined; +} diff --git a/docs/robutekLibrary/Robutek-library/@types/ledc.d.ts b/docs/robutekLibrary/Robutek-library/@types/ledc.d.ts new file mode 100644 index 00000000..49b016e5 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/@types/ledc.d.ts @@ -0,0 +1,44 @@ +declare module "ledc" { + /** + * Configure the given timer. + * @param timer The timer to configure. + * @param frequency The frequency to configure the timer to. + * @param resolution The resolution to configure the timer to (default 10 bits, changes frequency range) + */ + function configureTimer(timer: number, frequency: number, resolution?: number): void; + + /** + * Configure the given LEDC channel. + * @param channel The channel to configure. + * @param pin The pin to configure the channel to. + * @param timer The timer to configure the channel to. + * @param duty The duty to configure the channel to (0-1023). + */ + function configureChannel(channel: number, pin: number, timer: number, duty: number): void; + + /** + * Set the frequency of the given timer. + * @param timer The timer to set the frequency of. + * @param frequency The frequency to set the timer to. + */ + function setFrequency(timer: number, frequency: number): void; + + /** + * Set the duty of the given channel. + * @param channel The channel to set the duty of. + * @param duty The duty to set the channel to (0-1023). + */ + function setDuty(channel: number, duty: number): void; + + /** + * Stop the given timer. + * @param timer The timer to stop. + */ + function stopTimer(timer: number): void; + + /** + * Stop the given channel. + * @param channel The channel to stop. + */ + function stopChannel(channel: number): void; +} diff --git a/docs/robutekLibrary/Robutek-library/@types/misc.d.ts b/docs/robutekLibrary/Robutek-library/@types/misc.d.ts new file mode 100644 index 00000000..05e77a20 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/@types/misc.d.ts @@ -0,0 +1,5 @@ +/** + * Exits the current program. + * @param code The exit code to use. + */ +declare function exit(code?: number): void; diff --git a/docs/robutekLibrary/Robutek-library/@types/path.d.ts b/docs/robutekLibrary/Robutek-library/@types/path.d.ts new file mode 100644 index 00000000..c4434c60 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/@types/path.d.ts @@ -0,0 +1,36 @@ +declare module "path" { + /** + * Normalize the given path. + * @param path The path to normalize. + * @returns The normalized path. + */ + function normalize(path: string): string; + + /** + * Get the parent directory of the given path. + * @param path The path to get the parent directory of. + * @returns The parent directory. + */ + function dirname(path: string): string; + + /** + * Get the last part of the given path. + * @param path The path to get the last part of. + * @returns The last part of the path. + */ + function basename(path: string): string; + + /** + * Join the given paths. + * @param paths The paths to join. + * @returns The joined path. + */ + function join(...paths: string[]): string; + + /** + * Check if the given path is absolute. + * @param path The path to check. + * @returns True if the path is absolute, false otherwise. + */ + function isAbsolute(path: string): boolean; +} diff --git a/docs/robutekLibrary/Robutek-library/@types/platform.d.ts b/docs/robutekLibrary/Robutek-library/@types/platform.d.ts new file mode 100644 index 00000000..6fe4c710 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/@types/platform.d.ts @@ -0,0 +1,6 @@ +declare const PlatformInfo: { + /** + * The name of the platform the program is running on. + */ + name: string +} diff --git a/docs/robutekLibrary/Robutek-library/@types/pulseCounter.d.ts b/docs/robutekLibrary/Robutek-library/@types/pulseCounter.d.ts new file mode 100644 index 00000000..76ad8edd --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/@types/pulseCounter.d.ts @@ -0,0 +1,64 @@ +declare module "pulseCounter" { + type LevelAction = number; + type EdgeAction = number; + + const LevelAction: { + Keep: LevelAction; + Inverse: LevelAction; + Hold: LevelAction; + }; + + const EdgeAction: { + Hold: EdgeAction; + Increase: EdgeAction; + Decrease: EdgeAction; + }; + + type LevelMode = { + low: LevelAction, + high: LevelAction, + } + + type EdgeMode = { + pos: EdgeAction, + neg: EdgeAction, + } + + class PulseCounter { + /** + * Create a new pulse counter instance. + * @param options The options for the pulse counter. + */ + constructor(options: { + pinLevel: number, + pinEdge: number, + levelMode: LevelMode, + edgeMode: EdgeMode, + }) + + /** + * Read the current value of the pulse counter. + */ + read(): number; + + /** + * Reset the pulse counter to 0. + */ + clear(): void; + + /** + * Start the pulse counter. + */ + start(): void; + + /** + * Stop the pulse counter. + */ + stop(): void; + + /** + * Close the pulse counter. + */ + close(): void; + } +} diff --git a/docs/robutekLibrary/Robutek-library/@types/simpleradio.d.ts b/docs/robutekLibrary/Robutek-library/@types/simpleradio.d.ts new file mode 100644 index 00000000..a1cb8608 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/@types/simpleradio.d.ts @@ -0,0 +1,84 @@ +declare module "simpleradio" { + type PacketDataType = "number" | "string" | "keyvalue"; + + interface PacketInfo { + group: number; + address: string; + rssi: number; + } + + /** + * Initialize the radio. + * @param group The radio group to use. + */ + function begin(group: number): void; + + /** + * Set the radio group. + * @param group The radio group to use, between 0 and 15 inclusive. + */ + function setGroup(group: number): void; + + /** + * Get current radio group + * @returns ID of the current group + */ + function group(): number; + + /** + * Get the local device address. + * @returns the local device address. Only works after begin() is called. + */ + function address(): string; + + /** + * Send a string. + * @param str The string to send. + */ + function sendString(str: string): void; + + /** + * Send a number. + * @param num The number to send. + */ + function sendNumber(num: number): void; + + /** + * Send a key-value pair. + * @param key The key to send. + * @param value The number to send. + */ + function sendKeyValue(key: string, value: number): void; + + /** + * Register a callback for a packet type. + * @param type The packet type to register for. + * @param callback The callback to register. + */ + function on(type: "number", callback: (num: number, info: PacketInfo) => void): void; + + /** + * Register a callback for a packet type. + * @param type The packet type to register for. + * @param callback The callback to register. + */ + function on(type: "string", callback: (str: string, info: PacketInfo) => void): void; + + /** + * Register a callback for a packet type. + * @param type The packet type to register for. + * @param callback The callback to register. + */ + function on(type: "keyvalue", callback: (key: string, value: number, info: PacketInfo) => void): void; + + /** + * Unregister a callback for a packet type. + * @param type The packet type to unregister for. + */ + function off(type: PacketDataType): void; + + /** + * Stop the radio. + */ + function end(): void; +} diff --git a/docs/robutekLibrary/Robutek-library/@types/smartled.d.ts b/docs/robutekLibrary/Robutek-library/@types/smartled.d.ts new file mode 100644 index 00000000..a9c51e80 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/@types/smartled.d.ts @@ -0,0 +1,55 @@ +declare module "smartled" { + interface Rgb { + r: number; + g: number; + b: number; + } + + interface LedType { + T0H: number; + T1H: number; + T0L: number; + T1L: number; + TRS: number; + } + + class SmartLed { + /** + * Create a new Smart LED strip. + * @param pin The pin the strip is connected to. + * @param count The number of LEDs in the strip. + * @param type The type of LED strip. + */ + constructor(pin: number, count: number, type?: LedType); + + /** + * Show the current buffer on the strip. + */ + public show(): void; + + /** + * Set the color of the given LED. + * @param index The index of the LED to set. + * @param rgb The color to set the LED to. + */ + public set(index: number, rgb: Rgb): void; + + /** + * Get the color of the given LED. + * @param index The index of the LED to get. + * @returns The color of the LED. + */ + public get(index: number): Rgb; + + /** + * Clear the buffer. + */ + public clear(): void; + } + + const LED_WS2812: LedType; + const LED_WS2812B: LedType; + const LED_WS2812B_2020: LedType; + const LED_SK6812: LedType; + const LED_WS2813: LedType; +} diff --git a/docs/robutekLibrary/Robutek-library/@types/stdio.d.ts b/docs/robutekLibrary/Robutek-library/@types/stdio.d.ts new file mode 100644 index 00000000..f0c1881d --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/@types/stdio.d.ts @@ -0,0 +1,13 @@ +declare module "stdio" { + let stdout: Writable; + let stderr: Writable; + let stdin: Readable; +} + +declare const console: { + debug(arg: any): void; + log(arg: any): void; + warn(arg: any): void; + info(arg: any): void; + error(arg: any): void; +} diff --git a/docs/robutekLibrary/Robutek-library/@types/timers.d.ts b/docs/robutekLibrary/Robutek-library/@types/timers.d.ts new file mode 100644 index 00000000..4f73380a --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/@types/timers.d.ts @@ -0,0 +1,31 @@ +/** + * Returns a promise that resolves after the specified time. + * @param ms The number of milliseconds to wait before resolving the promise. + */ +declare function sleep(ms: number): Promise; + +/** + * Calls a function after the specified time. + * @param callback The function to call. + * @param ms The number of milliseconds to wait before calling the function. + */ +declare function setTimeout(callback: () => void, ms: number): number; + +/** + * Calls a function repeatedly, with a fixed time delay between each call. + * @param callback The function to call. + * @param ms The number of milliseconds to wait before calling the function. + */ +declare function setInterval(callback: () => void, ms: number): number; + +/** + * Cancels a timeout previously established by calling setTimeout(). + * @param id The identifier of the timeout to cancel. + */ +declare function clearTimeout(id: number): void; + +/** + * Cancels a timeout previously established by calling setInterval(). + * @param id The identifier of the interval to cancel. + */ +declare function clearInterval(id: number): void; diff --git a/docs/robutekLibrary/Robutek-library/@types/timestamp.d.ts b/docs/robutekLibrary/Robutek-library/@types/timestamp.d.ts new file mode 100644 index 00000000..bd9ab6c8 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/@types/timestamp.d.ts @@ -0,0 +1,11 @@ +declare interface Timestamp { + /** + * Convert the timestamp to milliseconds. + */ + millis(): number; + + /** + * Get the lower 10^3 part of the timestamp in microseconds. + */ + micros(): number; +} diff --git a/docs/robutekLibrary/Robutek-library/@types/wifi.d.ts b/docs/robutekLibrary/Robutek-library/@types/wifi.d.ts new file mode 100644 index 00000000..7e532194 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/@types/wifi.d.ts @@ -0,0 +1,6 @@ +declare module "wifi" { + /** + * Return current IPv4 of the device, or null if WiFi is disabled or not connected. + */ + function currentIp(): string | null; +} diff --git a/docs/robutekLibrary/Robutek-library/build/index.js b/docs/robutekLibrary/Robutek-library/build/index.js new file mode 100755 index 00000000..22b2a48b --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/build/index.js @@ -0,0 +1,182 @@ +import { stdin } from "stdio"; +import * as Robot from "./libs/robot.js"; +import * as gpio from "gpio"; +import * as colors from "./libs/colors.js"; +import * as motors from "./libs/custom_motors.js"; +Robot.init(); +const pen = new Robot.Pen(38); +const sensors = new Robot.Sensors(); +const strip = new Robot.Strip(); +const LMOT = new motors.Motor(11, 12, 1, 2, 3); +const RMOT = new motors.Motor(45, 13, 1, 4, 5); +function mapRange(fromMin, fromMax, toMin, toMax, number) { + return ((number - fromMin) * (toMax - toMin)) / (fromMax - fromMin) + toMin; +} +function clamp(value, min, max) { + return Math.min(Math.max(value, min), max); +} +gpio.pinMode(0, gpio.PinMode.INPUT_PULLUP); // nastavíme pin na input pullup +gpio.pinMode(2, gpio.PinMode.INPUT_PULLUP); // nastavíme pin na input pullup +gpio.on("falling", 0, () => { + pen.move(Robot.Pen.DOWN); +}); +gpio.on("falling", 2, () => { + pen.move(Robot.Pen.UP); +}); +async function readSensors() { + const max_val = 300; + const max_rgb = 5; + let sens_1 = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('W_FR')), 0, max_rgb); + strip.set(1, { r: sens_1, g: sens_1, b: sens_1 }); + let sens_2 = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('W_FL')), 0, max_rgb); + strip.set(2, { r: sens_2, g: sens_2, b: sens_2 }); + let sens_3 = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('W_BL')), 0, max_rgb); + strip.set(3, { r: sens_3, g: sens_3, b: sens_3 }); + let sens_4 = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('W_BR')), 0, max_rgb); + strip.set(4, { r: sens_4, g: sens_4, b: sens_4 }); + let sens_5 = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('L_FR')), 0, max_rgb); + strip.set(5, { r: sens_5, g: sens_5, b: sens_5 }); + let sens_6 = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('L_FL')), 0, max_rgb); + strip.set(6, { r: sens_6, g: sens_6, b: sens_6 }); + let sens_7 = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('L_BL')), 0, max_rgb); + strip.set(7, { r: sens_7, g: sens_7, b: sens_7 }); + let sens_8 = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('L_BR')), 0, max_rgb); + strip.set(8, { r: sens_8, g: sens_8, b: sens_8 }); +} +setInterval(() => { + readSensors(); +}, 500); +let hue = 0; +async function rgb() { + //for (let i = 0; i < 9; i++) { + // let h = hue + // strip.set(i, colors.rainbow((hue + (12 * i)) % 360, 5)); + //} + strip.set(0, colors.rainbow(hue % 360, 5)); + hue += 1; + strip.show(); +} +setInterval(() => { + rgb(); +}, 20); +let closeCon = false; +let speed = 0.5; +function onGet(data) { + switch (data) { + case "1": + speed = 0.1; + break; + case "2": + speed = 0.2; + break; + case "3": + speed = 0.3; + break; + case "4": + speed = 0.4; + break; + case "5": + speed = 0.5; + break; + case "6": + speed = 0.6; + break; + case "7": + speed = 0.7; + break; + case "8": + speed = 0.8; + break; + case "9": + speed = 0.9; + break; + case "0": + speed = 1; + break; + case "w": + Robot.move(0, 10, 10); + LMOT.write(speed); + RMOT.write(speed); + break; + case "s": + LMOT.write(-speed); + RMOT.write(-speed); + break; + case "a": + LMOT.write(-speed); + RMOT.write(speed); + break; + case "d": + LMOT.write(speed); + RMOT.write(-speed); + break; + case " ": + LMOT.write(0); + RMOT.write(0); + break; + case "q": + LMOT.write(0); + RMOT.write(0); + closeCon = true; + break; + default: + break; + } + if (!closeCon) { + stdin.get().then((data) => onGet(data)); // recursive function + } +} +stdin.get().then((data) => onGet(data)); // start +/*import * as motors from "motors" + +const leftMotor:motors.MotorConf = {neg: 1, pos: 2, encA:3, encB:4, encTicks:200} +const rightMotor:motors.MotorConf = {neg: 5, pos: 6, encA:7, encB:8, encTicks:200, reverse:true} + +const wheels = new motors.Wheels({left:leftMotor, right:rightMotor, diameter:20, width:100}) + +wheels.move(0, {distance:10}) + + + +import { SmartLed, LED_WS2812 } from "smartled"; +import * as colors from "./libs/colors.js" +import * as gpio from "gpio"; +import { stdout } from "stdio"; +import * as adc from "adc"; +import * as readline from "./libs/readline.js" +import * as radio from "simpleradio" +import * as motor from "./libs/motors.js" + +const A1 = 11; +const A2 = 12; +const B1 = 45; +const B2 = 11; + + +const motor_1 = new motor.Motor(A1, A2, 1, 2, 3, false); +const motor_2 = new motor.Motor(B1, B2, 1, 4, 5, false); + +radio.begin(8); +radio.on("string", (str:string, info:radio.PacketInfo) => { + let values = str.split(";") + motor_1.write(Number(values[0])) + motor_2.write(Number(values[1])) +}) + + +async function test() { + + motor_1.write(0); + await sleep(1000); + motor_1.write(0.5); + await sleep(1000); + motor_1.write(1); + await sleep(1000); + motor_1.write(0); + await sleep(1000); + motor_1.write(-1); + await sleep(1000); + motor_1.write(0); +} + +test()*/ diff --git a/docs/robutekLibrary/Robutek-library/build/libs/colors.js b/docs/robutekLibrary/Robutek-library/build/libs/colors.js new file mode 100644 index 00000000..acd8f591 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/build/libs/colors.js @@ -0,0 +1,57 @@ +/** + * Mezi jednotlivými reprezentacemi lze převádět + * @param hsl {Number} Hue (0-360), Saturation (0-1), Lightness (0-1) + * @returns {Rgb} Red (0-255), Green (0-255), Blue (0-255) + */ +export function hsl_to_rbg(hsl) { + const chroma = (1 - Math.abs(2 * hsl.l - 1) * hsl.s); + const hue = hsl.h / 60; + const x = chroma * (1 - Math.abs((hue % 2) - 1)); + let color = { r: 0, g: 0, b: 0 }; + if (hue > 0 && hue < 1) { + color = { r: chroma, g: x, b: 0 }; + } + else if (hue >= 1 && hue < 2) { + color = { r: x, g: chroma, b: 0 }; + } + else if (hue >= 2 && hue < 3) { + color = { r: 0, g: chroma, b: x }; + } + else if (hue >= 3 && hue < 4) { + color = { r: 0, g: x, b: chroma }; + } + else if (hue >= 4 && hue < 5) { + color = { r: x, g: 0, b: chroma }; + } + else { + color = { r: chroma, g: 0, b: x }; + } + const correction = hsl.l - chroma / 2; + color.r = (color.r + correction) * 255; + color.g = (color.g + correction) * 255; + color.b = (color.b + correction) * 255; + return color; +} +/** + * Funkce rainbow zafixuje sytost a světlost, a prochází barvami + * @param hue (0-360) + * @param brightness (0-100) - 50 je defaultní hodnota + * @returns {Rgb} + */ +export function rainbow(hue, brightness = 50) { + hue = Math.min(hue, 360); // Zajistíme, že zadaná hodnota není mimo rozsah + // fix range to 0-100 + let brightness_mapped = Math.min(Math.max(brightness, 0), 100); + return hsl_to_rbg({ h: hue, s: 1, l: brightness_mapped / 100 }); +} +/* Základní barvy pro LED pásky*/ +export const red = rainbow(0); +export const orange = rainbow(27); +export const yellow = rainbow(54); +export const green = rainbow(110); +export const light_blue = rainbow(177); +export const blue = rainbow(240); +export const purple = rainbow(285); +export const pink = rainbow(323); +export const white = { r: 100, g: 100, b: 100 }; +export const off = { r: 0, g: 0, b: 0 }; diff --git a/docs/robutekLibrary/Robutek-library/build/libs/custom_motors.js b/docs/robutekLibrary/Robutek-library/build/libs/custom_motors.js new file mode 100644 index 00000000..e5cb22c3 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/build/libs/custom_motors.js @@ -0,0 +1,40 @@ +import * as ledc from "ledc"; +/** + * A class for controlling a servo motor. + */ +export class Motor { + /** + * Create a new servo object. + * @param pin1 The pin the servo is connected to. + * @param pin2 The pin the servo is connected to. + * @param timer The timer to use for PWM. + * @param channel1 The channel to use for PWM. + * @param channel2 The channel to use for PWM. + */ + constructor(pin1, pin2, timer, channel1, channel2) { + this.channel1 = channel1; + this.channel2 = channel2; + ledc.configureTimer(timer, 50, 12); // 50Hz is the frequency of a servo, 12 is the resolution of the timer + ledc.configureChannel(channel1, pin1, timer, 1023); // 1023 is the resolution of a servo + ledc.configureChannel(channel2, pin2, timer, 1023); // 1023 is the resolution of a servo + } + /** + * Set the servo position. + * @param value The position to set the servo to, from -1 to 1. + */ + write(value) { + console.log(value); + if (value > 0) { + ledc.setDuty(this.channel1, 1); + ledc.setDuty(this.channel2, (Math.abs(value) * 1023)); + } + else if (value < 0) { + ledc.setDuty(this.channel1, (Math.abs(value) * 1023)); + ledc.setDuty(this.channel2, 1); + } + else { + ledc.setDuty(this.channel1, 1); + ledc.setDuty(this.channel2, 1); + } + } +} diff --git a/docs/robutekLibrary/Robutek-library/build/libs/motors.js b/docs/robutekLibrary/Robutek-library/build/libs/motors.js new file mode 100644 index 00000000..e69de29b diff --git a/docs/robutekLibrary/Robutek-library/build/libs/readline.js b/docs/robutekLibrary/Robutek-library/build/libs/readline.js new file mode 100644 index 00000000..0f6f797e --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/build/libs/readline.js @@ -0,0 +1,66 @@ +import { stdout, stdin } from "stdio"; +/** + * A class for reading standard input line by line. + */ +export class readline { + onGet(str) { + if (this.echo) { + stdout.write(str); + } + if (str == "\n") { + if (this.resolve) { + this.resolve(this.buffer); + } + this.buffer = ""; + this.promise = null; + this.resolve = null; + this.reject = null; + return; + } + this.buffer += str; + if (!this.closed) { + stdin.get() + .then((data) => this.onGet(data)) + .catch((reason) => { + if (this.reject) { + this.reject(reason); + } + }); + } + } + constructor(echo = false) { + this.buffer = ""; + this.promise = null; + this.resolve = null; + this.reject = null; + this.closed = false; + this.echo = echo; + } + read() { + if (this.promise != null) { + return Promise.reject("Already reading"); + } + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + stdin.get() + .then((data) => this.onGet(data)) + .catch((reason) => { + if (this.reject) { + this.reject(reason); + } + }); + return this.promise; + } + close() { + this.closed = true; + if (this.reject) { + this.reject("Stopped"); + } + this.buffer = ""; + this.promise = null; + this.resolve = null; + this.reject = null; + } +} diff --git a/docs/robutekLibrary/Robutek-library/build/libs/robot.js b/docs/robutekLibrary/Robutek-library/build/libs/robot.js new file mode 100644 index 00000000..0996c056 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/build/libs/robot.js @@ -0,0 +1,114 @@ +/* +import * as motors from "motors" + +const leftMotor: motors.MotorConf = { neg: 1, pos: 2, encA: 3, encB: 4, encTicks: 200 } +const rightMotor: motors.MotorConf = { neg: 5, pos: 6, encA: 7, encB: 8, encTicks: 200, reverse: true } + +const wheels = new motors.Wheels({ left: leftMotor, right: rightMotor, diameter: 20, width: 100 }) +*/ +import * as adc from "adc"; +import * as gpio from "gpio"; +import { Servo } from "./servo.js"; +import { SmartLed, LED_WS2812 } from "smartled"; +export function init() { + adc.configure(Sensors.S_1, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 + adc.configure(Sensors.S_2, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 + adc.configure(Sensors.S_3, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 + adc.configure(Sensors.S_4, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 + gpio.pinMode(Sensors.S_PWR, gpio.PinMode.OUTPUT); // nastavíme mód pinu podsvícení na output + gpio.pinMode(Sensors.S_SW, gpio.PinMode.OUTPUT); + gpio.write(Sensors.S_PWR, 1); // zapneme podsvícení robůtka +} +export function move(curve, distance, time) { + //wheels.move(curve, {distance:distance, time:time}) +} +export function rotate(angle) { + //wheels.rotate(angle) +} +export function stop() { + //wheels.stop() +} +export class Sensors { + constructor() { + this.sw = 0; + } + async switch_sensors(to_value) { + if (to_value == this.sw) { + return; + } + this.sw = to_value; + gpio.write(Sensors.S_SW, to_value); + await sleep(5); + } + async read(sensor) { + switch (sensor) { + case 'W_FR': + this.switch_sensors(0); + return adc.read(Sensors.S_1); + case 'W_FL': + this.switch_sensors(0); + return adc.read(Sensors.S_2); + case 'W_BL': + this.switch_sensors(0); + return adc.read(Sensors.S_3); + case 'W_BR': + this.switch_sensors(0); + return adc.read(Sensors.S_4); + case 'L_FR': + this.switch_sensors(1); + return adc.read(Sensors.S_1); + case 'L_FL': + this.switch_sensors(1); + return adc.read(Sensors.S_2); + case 'L_BL': + this.switch_sensors(1); + return adc.read(Sensors.S_3); + case 'L_BR': + this.switch_sensors(1); + return adc.read(Sensors.S_4); + default: + return 0; + } + } +} +Sensors.S_1 = 4; +Sensors.S_2 = 5; +Sensors.S_3 = 6; +Sensors.S_4 = 7; +Sensors.S_SW = 8; +Sensors.S_PWR = 47; +export class Pen { + constructor(pin) { + this.servo = new Servo(pin, 1, 0); + } + /** + * Set the pen servo position. + * @param value The position to set the servo to, from 0 to 1023. + */ + move(value) { + this.servo.write(value); + } +} +Pen.UP = 512 + 180; +Pen.DOWN = 512 - 180; +Pen.MIDDLE = 512; +Pen.UNLOAD = 0; +export class Strip { + constructor() { + this.strip = new SmartLed(48, 9, LED_WS2812); + } + set(index, color) { + this.strip.set(index, color); + } + fill(color) { + for (let i = 0; i < 9; i++) { + this.strip.set(i, color); + } + } + show() { + this.strip.show(); + } + clear() { + this.strip.clear(); + } +} diff --git a/docs/robutekLibrary/Robutek-library/build/libs/servo.js b/docs/robutekLibrary/Robutek-library/build/libs/servo.js new file mode 100644 index 00000000..3300633a --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/build/libs/servo.js @@ -0,0 +1,28 @@ +import * as ledc from "ledc"; +/** + * A class for controlling a servo motor. + */ +export class Servo { + /** + * Create a new servo object. + * @param pin The pin the servo is connected to. + * @param timer The timer to use for PWM. + * @param channel The channel to use for PWM. + */ + constructor(pin, timer, channel) { + this.channel = channel; + ledc.configureTimer(timer, 50, 12); // 50Hz is the frequency of a servo, 12 is the resolution of the timer + ledc.configureChannel(channel, pin, timer, 1023); // 1023 is the resolution of a servo + } + /** + * Set the servo position. + * @param value The position to set the servo to, from 0-1023. + */ + write(value) { + // map the value from 0-1023 to 0.5-2.5ms + const ms = ((value / 1023) * 2) + 0.5; // 0.5-2.5ms is the range of a servo + // convert to a duty cycle + const duty = (ms / 20) * 1023; // 20ms is the period of a servo + ledc.setDuty(this.channel, duty); // set the duty cycle to the servo + } +} diff --git a/docs/robutekLibrary/Robutek-library/src/index.ts b/docs/robutekLibrary/Robutek-library/src/index.ts new file mode 100644 index 00000000..09ea8a5e --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/src/index.ts @@ -0,0 +1,42 @@ +import { stdin } from "stdio"; +import * as gpio from "gpio"; +import * as colors from "./libs/colors.js" + + + + +import * as Robot from "./libs/robot.js" + +Robot.init() + +const pen = new Robot.Pen(38); + +const sensors = new Robot.Sensors(); + +const strip = new Robot.Strip(); + +Robot.Pins.ENC1_1 +Robot.Pins.M1_1 +Robot.Pins.Status_LED + +strip.set(0, colors.rainbow(123, 5)); +strip.fill({ r: 0, g: 0, b: 0 }); +strip.clear(); +strip.show(); + + +pen.move(Robot.Pen.UP); +pen.move(Robot.Pen.DOWN); +pen.move(Robot.Pen.MIDDLE); +pen.move(Robot.Pen.UNLOAD); +// pen.move(angle); + + +sensors.read('W_FR').then(console.log) +// await sensors.read('W_FR') + +/* + * Naming: + * WFR = Wheel Front Right (in front of right wheel) + * LBL = Line Back Left (next to pen) + */ diff --git a/docs/robutekLibrary/Robutek-library/src/libs/colors.ts b/docs/robutekLibrary/Robutek-library/src/libs/colors.ts new file mode 100644 index 00000000..de76a121 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/src/libs/colors.ts @@ -0,0 +1,81 @@ +/** + * Barva je jednoduchá trojice červené, zelené, a modré složky + * - R: červená (rozsah 0-255) + * - G: zelená (rozsah 0-255) + * - B: modrá (rozsah 0-255) + */ +interface Rgb { + r: number; + g: number; + b: number; +} + + +/** + * Alternativní způsob, jak vyjádřit barvu, je HSL: + * - Hue: odstín (rozsah 0-360) + * - Saturation: sytost barev (rozsah 0-1) + * - Lightness: světlost (rozsah 0-1) + */ +interface Hsl { + h: number; + s: number; + l: number; +} + +/** + * Mezi jednotlivými reprezentacemi lze převádět + * @param hsl {Number} Hue (0-360), Saturation (0-1), Lightness (0-1) + * @returns {Rgb} Red (0-255), Green (0-255), Blue (0-255) + */ +export function hsl_to_rbg( hsl: Hsl ) : Rgb { + const chroma = ( 1 - Math.abs( 2 * hsl.l - 1 ) * hsl.s ); + const hue = hsl.h / 60; + const x = chroma * ( 1 - Math.abs( ( hue % 2 ) - 1 ) ); + + let color : Rgb = { r: 0, g: 0, b: 0 }; + if( hue > 0 && hue < 1 ){ + color = { r: chroma, g: x, b: 0 }; + } else if( hue >= 1 && hue < 2 ){ + color = { r: x, g: chroma, b: 0 }; + } else if( hue >= 2 && hue < 3 ){ + color = { r: 0, g: chroma, b: x }; + } else if( hue >= 3 && hue < 4 ){ + color = { r: 0, g: x, b: chroma }; + } else if( hue >= 4 && hue < 5 ){ + color = { r: x, g: 0, b: chroma }; + } else { + color = { r: chroma, g: 0, b: x }; + } + const correction = hsl.l - chroma / 2; + color.r = ( color.r + correction ) * 255; + color.g = ( color.g + correction ) * 255; + color.b = ( color.b + correction ) * 255; + + return color; +} + +/** + * Funkce rainbow zafixuje sytost a světlost, a prochází barvami + * @param hue (0-360) + * @param brightness (0-100) - 50 je defaultní hodnota + * @returns {Rgb} + */ +export function rainbow( hue: number, brightness: number = 50) : Rgb { + hue = Math.min( hue, 360 ); // Zajistíme, že zadaná hodnota není mimo rozsah + // fix range to 0-100 + let brightness_mapped = Math.min(Math.max(brightness, 0), 100); + return hsl_to_rbg( { h: hue, s: 1, l: brightness_mapped / 100 } ); +} + +/* Základní barvy pro LED pásky*/ +export const red = rainbow( 0 ); +export const orange = rainbow( 27 ); +export const yellow = rainbow( 54 ); +export const green = rainbow( 110 ); +export const light_blue = rainbow( 177 ); +export const blue = rainbow( 240 ); +export const purple = rainbow( 285 ); +export const pink = rainbow( 323 ); +export const white : Rgb = { r: 100, g: 100, b: 100 }; +export const off : Rgb = { r: 0, g: 0, b: 0 }; \ No newline at end of file diff --git a/docs/robutekLibrary/Robutek-library/src/libs/motors.ts b/docs/robutekLibrary/Robutek-library/src/libs/motors.ts new file mode 100644 index 00000000..81795e56 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/src/libs/motors.ts @@ -0,0 +1,42 @@ +declare module "motors" { + + type MoveDuration = { + distance?: number; // distance in cm - negative = reverse + speed?: number; // speed in mm/s? + } + + type RotateDuration = { + angle?: number; // angle in degrees + speed?: number; // speed in mm/s? + } + + type MotorConf = { + pos: number; + neg: number; + encA: number; + encB: number; + encTicks: number; + reverse?: boolean; + }; + + class Motor { + constructor(options: { pins: MotorConf, diameter: number }); + setPower(power: number): void; + setRamp(ramp: number): void; + + move(duration?: MoveDuration): Promise; + stop(brq?: boolean): Promise; + getPosition(): number; + } + + class Wheels { + constructor(options: { left: MotorConf, right: MotorConf, diameter: number, width: number }); + setPower(power: number): void; + setRamp(ramp: number): void; + + move(curve: number, duration?: MoveDuration): Promise; + rotate(angle: number): Promise; + stop(brq?: boolean): Promise; + getPosition(): { left: number, right: number }; + } +} \ No newline at end of file diff --git a/docs/robutekLibrary/Robutek-library/src/libs/readline.ts b/docs/robutekLibrary/Robutek-library/src/libs/readline.ts new file mode 100644 index 00000000..4372efd2 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/src/libs/readline.ts @@ -0,0 +1,84 @@ +import { stdout, stdin } from "stdio"; + +/** + * A class for reading standard input line by line. + */ + +export class readline { + private buffer: string = ""; + private promise: Promise | null = null; + private resolve: ((value: string) => void) | null = null; + private reject: ((reason: any) => void) | null = null; + private closed: boolean = false; + private echo: boolean; + + private onGet(str: string) { + if (this.echo) { + stdout.write(str); + } + + if (str == "\n") { + if (this.resolve) { + this.resolve(this.buffer); + } + + this.buffer = ""; + this.promise = null; + this.resolve = null; + this.reject = null; + + return; + } + + this.buffer += str; + + if (!this.closed) { + stdin.get() + .then((data) => this.onGet(data)) + .catch((reason) => { + if (this.reject) { + this.reject(reason); + } + }); + } + + } + + constructor(echo: boolean = false) { + this.echo = echo; + } + + public read(): Promise { + if (this.promise != null) { + return Promise.reject("Already reading"); + } + + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + + stdin.get() + .then((data) => this.onGet(data)) + .catch((reason) => { + if (this.reject) { + this.reject(reason); + } + }); + + return this.promise; + } + + public close() { + this.closed = true; + + if (this.reject) { + this.reject("Stopped"); + } + + this.buffer = ""; + this.promise = null; + this.resolve = null; + this.reject = null; + } +} diff --git a/docs/robutekLibrary/Robutek-library/src/libs/robot.ts b/docs/robutekLibrary/Robutek-library/src/libs/robot.ts new file mode 100644 index 00000000..6622ff4b --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/src/libs/robot.ts @@ -0,0 +1,219 @@ + +import * as adc from "adc"; +import * as gpio from "gpio"; + +import { Servo } from "./servo.js"; + +import { SmartLed, LED_WS2812, Rgb } from "smartled"; + +/* +import * as motors from "motors" + +const leftMotor: motors.MotorConf = { neg: 1, pos: 2, encA: 3, encB: 4, encTicks: 200 } +const rightMotor: motors.MotorConf = { neg: 5, pos: 6, encA: 7, encB: 8, encTicks: 200, reverse: true } + +const wheels = new motors.Wheels({ left: leftMotor, right: rightMotor, diameter: 20, width: 100 }) +*/ + +import * as motors from "./simplemotors.js" + +const LMOT = new motors.Motor(11, 12, 1, 2, 3); +const RMOT = new motors.Motor(45, 13, 1, 4, 5); + + + +export function init() { + adc.configure(Sensors.S_1, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 + adc.configure(Sensors.S_2, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 + adc.configure(Sensors.S_3, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 + adc.configure(Sensors.S_4, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 + + gpio.pinMode(Sensors.S_PWR, gpio.PinMode.OUTPUT); // nastavíme mód pinu podsvícení na output + gpio.pinMode(Sensors.S_SW, gpio.PinMode.OUTPUT); + + gpio.write(Sensors.S_PWR, 1); // zapneme podsvícení robůtka +} + + + +type MoveDuration = { + distance?: number; // distance in cm + speed?: number; // speed in mm/s? +} +export async function move(curve: number, duration?: MoveDuration) { + // curve = -1 to 1 + + // Ensure the curve is between -1 and 1 + if (curve < -1) curve = -1; + if (curve > 1) curve = 1; + + if (duration.speed != undefined) { + // Just a temporary motor driver + + + // Calculate motor speeds + let m1: number; + let m2: number; + + if (curve >= 0) { + // Curve from 0 to 1 (m1 is max, m2 decreases) + m1 = duration.speed; + m2 = duration.speed * (1 - curve); + } else { + // Curve from -1 to 0 (m2 is max, m1 decreases) + m1 = duration.speed * (1 + curve); // curve is negative, so this increases from 0 to speed + m2 = duration.speed; + } + + LMOT.write(m1) + RMOT.write(m2) + + } + + //await wheels.move(curve, duration) +} + + +type RotateDuration = { + angle?: number; // angle in degrees + speed?: number; // speed in mm/s? +} +export function rotate(duration: RotateDuration) { + + LMOT.write(duration.speed); + RMOT.write(duration.speed); + + //wheels.rotate(duration) +} + +export function stop() { + LMOT.write(1); + RMOT.write(1); + + //wheels.stop() +} + + +export type SensorType = 'W_FR' | 'W_FL' | 'W_BL' | 'W_BR' | 'L_FR' | 'L_FL' | 'L_BL' | 'L_BR'; +export class Sensors { + + public static readonly S_1: number = 4; + public static readonly S_2: number = 5; + public static readonly S_3: number = 6; + public static readonly S_4: number = 7; + public static readonly S_SW: number = 8; + public static readonly S_PWR: number = 47; + + private sw: number = 0; + + + private async switch_sensors(to_value: number) { + if (to_value == this.sw) { + return; + } + this.sw = to_value; + gpio.write(Sensors.S_SW, to_value); + await sleep(5); + } + + public async read(sensor: SensorType): Promise { + switch (sensor) { + case 'W_FR': + this.switch_sensors(0); + return adc.read(Sensors.S_1); + + case 'W_FL': + this.switch_sensors(0); + return adc.read(Sensors.S_2); + + case 'W_BL': + this.switch_sensors(0); + return adc.read(Sensors.S_3); + + case 'W_BR': + this.switch_sensors(0); + return adc.read(Sensors.S_4); + + + case 'L_FR': + this.switch_sensors(1); + return adc.read(Sensors.S_1); + + case 'L_FL': + this.switch_sensors(1); + return adc.read(Sensors.S_2); + + case 'L_BL': + this.switch_sensors(1); + return adc.read(Sensors.S_3); + + case 'L_BR': + this.switch_sensors(1); + return adc.read(Sensors.S_4); + + default: + return 0; + + } + } +} + +export class Pen { + private servo: Servo; + + public static readonly UP = 512 + 180; + public static readonly DOWN = 512 - 180; + public static readonly MIDDLE = 512; + public static readonly UNLOAD = 0; + + constructor(pin: number) { + this.servo = new Servo(pin, 1, 0); + } + + /** + * Set the pen servo position. + * @param value The position to set the servo to, from 0 to 1023. + */ + public move(value: number) { + this.servo.write(value); + } +} + +export class Strip { + private strip: SmartLed; + + constructor() { + this.strip = new SmartLed(48, 9, LED_WS2812); + } + + public set(index: number, color: Rgb) { + this.strip.set(index, color); + } + + public fill(color: Rgb) { + for (let i = 0; i < 9; i++) { + this.strip.set(i, color); + } + } + + public show() { + this.strip.show(); + } + + public clear() { + this.strip.clear(); + } +} + +export class Pins { + public static readonly Status_LED: number = 46; + + public static readonly M1_1 = 11; + public static readonly M1_2 = 12; + public static readonly M2_1 = 45; + public static readonly M2_2 = 13; + public static readonly ENC1_1 = 39; + public static readonly ENC1_2 = 40; + public static readonly ENC2_1 = 41; + public static readonly ENC2_2 = 42; +} \ No newline at end of file diff --git a/docs/robutekLibrary/Robutek-library/src/libs/servo.ts b/docs/robutekLibrary/Robutek-library/src/libs/servo.ts new file mode 100644 index 00000000..ac4fe3ee --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/src/libs/servo.ts @@ -0,0 +1,34 @@ +import * as ledc from "ledc"; + +/** + * A class for controlling a servo motor. + */ +export class Servo { + private channel: number; // the PWM channel to use + + /** + * Create a new servo object. + * @param pin The pin the servo is connected to. + * @param timer The timer to use for PWM. + * @param channel The channel to use for PWM. + */ + constructor(pin: number, timer: number, channel: number) { + this.channel = channel; + ledc.configureTimer(timer, 50, 12); // 50Hz is the frequency of a servo, 12 is the resolution of the timer + ledc.configureChannel(channel, pin, timer, 1023); // 1023 is the resolution of a servo + } + + /** + * Set the servo position. + * @param value The position to set the servo to, from 0-1023. + */ + write(value: number) { + // map the value from 0-1023 to 0.5-2.5ms + const ms = ((value / 1023) * 2) + 0.5; // 0.5-2.5ms is the range of a servo + + // convert to a duty cycle + const duty = (ms / 20) * 1023; // 20ms is the period of a servo + + ledc.setDuty(this.channel, duty); // set the duty cycle to the servo + } +} diff --git a/docs/robutekLibrary/Robutek-library/src/libs/simplemotors.ts b/docs/robutekLibrary/Robutek-library/src/libs/simplemotors.ts new file mode 100644 index 00000000..c6f424cd --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/src/libs/simplemotors.ts @@ -0,0 +1,47 @@ +import * as ledc from "ledc"; + +/** + * A class for controlling a servo motor. + */ +export class Motor { + private channel1: number; // the PWM channel to use + private channel2: number; // the PWM channel to use + + /** + * Create a new servo object. + * @param pin1 The pin the servo is connected to. + * @param pin2 The pin the servo is connected to. + * @param timer The timer to use for PWM. + * @param channel1 The channel to use for PWM. + * @param channel2 The channel to use for PWM. + */ + + constructor(pin1: number, pin2: number, timer: number, channel1: number, channel2: number) { + this.channel1 = channel1; + this.channel2 = channel2; + ledc.configureTimer(timer, 50, 12); // 50Hz is the frequency of a servo, 12 is the resolution of the timer + ledc.configureChannel(channel1, pin1, timer, 1023); // 1023 is the resolution of a servo + ledc.configureChannel(channel2, pin2, timer, 1023); // 1023 is the resolution of a servo + } + + /** + * Set the servo position. + * @param value The position to set the servo to, from -1 to 1. + */ + write(value: number) { + console.log(value) + + if (value > 0) { + ledc.setDuty(this.channel1, 1); + ledc.setDuty(this.channel2, (Math.abs(value) * 1023)); + } + else if (value < 0) { + ledc.setDuty(this.channel1, (Math.abs(value) * 1023)); + ledc.setDuty(this.channel2, 1); + } + else { + ledc.setDuty(this.channel1, 1); + ledc.setDuty(this.channel2, 1); + } + } +} diff --git a/docs/robutekLibrary/Robutek-library/tsconfig.json b/docs/robutekLibrary/Robutek-library/tsconfig.json new file mode 100644 index 00000000..8cf82b10 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "es2020", + "lib": ["es2020"], + "moduleResolution": "node", + "sourceMap": false, + "outDir": "build", + "rootDir": "src", + } +} diff --git a/docs/robutekLibrary/index.md b/docs/robutekLibrary/index.md new file mode 100644 index 00000000..6607635b --- /dev/null +++ b/docs/robutekLibrary/index.md @@ -0,0 +1,3 @@ +# Robutek Library + +[ZIP s knihovnou](./Robutek-library.zip) \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 67622372..78a1f6b6 100755 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -87,6 +87,7 @@ plugins: - robot/lekce4/project4 - robot/lekce6/project6 - robot/lekce8/project8 + - robutekLibrary/Robutek-library debug: true - auto-refresh-build-pages: From 44efb52f95d5e39764a9e2a38f09e5803182548d Mon Sep 17 00:00:00 2001 From: c2coder Date: Sun, 7 Jul 2024 22:41:41 +0200 Subject: [PATCH 02/14] Remove build folder and add it to .gitignore --- .gitignore | 4 +- .../Robutek-library/build/index.js | 182 ------------------ .../Robutek-library/build/libs/colors.js | 57 ------ .../build/libs/custom_motors.js | 40 ---- .../Robutek-library/build/libs/motors.js | 0 .../Robutek-library/build/libs/readline.js | 66 ------- .../Robutek-library/build/libs/robot.js | 114 ----------- .../Robutek-library/build/libs/servo.js | 28 --- 8 files changed, 3 insertions(+), 488 deletions(-) delete mode 100755 docs/robutekLibrary/Robutek-library/build/index.js delete mode 100644 docs/robutekLibrary/Robutek-library/build/libs/colors.js delete mode 100644 docs/robutekLibrary/Robutek-library/build/libs/custom_motors.js delete mode 100644 docs/robutekLibrary/Robutek-library/build/libs/motors.js delete mode 100644 docs/robutekLibrary/Robutek-library/build/libs/readline.js delete mode 100644 docs/robutekLibrary/Robutek-library/build/libs/robot.js delete mode 100644 docs/robutekLibrary/Robutek-library/build/libs/servo.js diff --git a/.gitignore b/.gitignore index b276259f..eed94093 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,6 @@ site/ assets-large .resize-hash -node_modules/ \ No newline at end of file +node_modules/ + +build/ \ No newline at end of file diff --git a/docs/robutekLibrary/Robutek-library/build/index.js b/docs/robutekLibrary/Robutek-library/build/index.js deleted file mode 100755 index 22b2a48b..00000000 --- a/docs/robutekLibrary/Robutek-library/build/index.js +++ /dev/null @@ -1,182 +0,0 @@ -import { stdin } from "stdio"; -import * as Robot from "./libs/robot.js"; -import * as gpio from "gpio"; -import * as colors from "./libs/colors.js"; -import * as motors from "./libs/custom_motors.js"; -Robot.init(); -const pen = new Robot.Pen(38); -const sensors = new Robot.Sensors(); -const strip = new Robot.Strip(); -const LMOT = new motors.Motor(11, 12, 1, 2, 3); -const RMOT = new motors.Motor(45, 13, 1, 4, 5); -function mapRange(fromMin, fromMax, toMin, toMax, number) { - return ((number - fromMin) * (toMax - toMin)) / (fromMax - fromMin) + toMin; -} -function clamp(value, min, max) { - return Math.min(Math.max(value, min), max); -} -gpio.pinMode(0, gpio.PinMode.INPUT_PULLUP); // nastavíme pin na input pullup -gpio.pinMode(2, gpio.PinMode.INPUT_PULLUP); // nastavíme pin na input pullup -gpio.on("falling", 0, () => { - pen.move(Robot.Pen.DOWN); -}); -gpio.on("falling", 2, () => { - pen.move(Robot.Pen.UP); -}); -async function readSensors() { - const max_val = 300; - const max_rgb = 5; - let sens_1 = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('W_FR')), 0, max_rgb); - strip.set(1, { r: sens_1, g: sens_1, b: sens_1 }); - let sens_2 = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('W_FL')), 0, max_rgb); - strip.set(2, { r: sens_2, g: sens_2, b: sens_2 }); - let sens_3 = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('W_BL')), 0, max_rgb); - strip.set(3, { r: sens_3, g: sens_3, b: sens_3 }); - let sens_4 = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('W_BR')), 0, max_rgb); - strip.set(4, { r: sens_4, g: sens_4, b: sens_4 }); - let sens_5 = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('L_FR')), 0, max_rgb); - strip.set(5, { r: sens_5, g: sens_5, b: sens_5 }); - let sens_6 = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('L_FL')), 0, max_rgb); - strip.set(6, { r: sens_6, g: sens_6, b: sens_6 }); - let sens_7 = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('L_BL')), 0, max_rgb); - strip.set(7, { r: sens_7, g: sens_7, b: sens_7 }); - let sens_8 = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('L_BR')), 0, max_rgb); - strip.set(8, { r: sens_8, g: sens_8, b: sens_8 }); -} -setInterval(() => { - readSensors(); -}, 500); -let hue = 0; -async function rgb() { - //for (let i = 0; i < 9; i++) { - // let h = hue - // strip.set(i, colors.rainbow((hue + (12 * i)) % 360, 5)); - //} - strip.set(0, colors.rainbow(hue % 360, 5)); - hue += 1; - strip.show(); -} -setInterval(() => { - rgb(); -}, 20); -let closeCon = false; -let speed = 0.5; -function onGet(data) { - switch (data) { - case "1": - speed = 0.1; - break; - case "2": - speed = 0.2; - break; - case "3": - speed = 0.3; - break; - case "4": - speed = 0.4; - break; - case "5": - speed = 0.5; - break; - case "6": - speed = 0.6; - break; - case "7": - speed = 0.7; - break; - case "8": - speed = 0.8; - break; - case "9": - speed = 0.9; - break; - case "0": - speed = 1; - break; - case "w": - Robot.move(0, 10, 10); - LMOT.write(speed); - RMOT.write(speed); - break; - case "s": - LMOT.write(-speed); - RMOT.write(-speed); - break; - case "a": - LMOT.write(-speed); - RMOT.write(speed); - break; - case "d": - LMOT.write(speed); - RMOT.write(-speed); - break; - case " ": - LMOT.write(0); - RMOT.write(0); - break; - case "q": - LMOT.write(0); - RMOT.write(0); - closeCon = true; - break; - default: - break; - } - if (!closeCon) { - stdin.get().then((data) => onGet(data)); // recursive function - } -} -stdin.get().then((data) => onGet(data)); // start -/*import * as motors from "motors" - -const leftMotor:motors.MotorConf = {neg: 1, pos: 2, encA:3, encB:4, encTicks:200} -const rightMotor:motors.MotorConf = {neg: 5, pos: 6, encA:7, encB:8, encTicks:200, reverse:true} - -const wheels = new motors.Wheels({left:leftMotor, right:rightMotor, diameter:20, width:100}) - -wheels.move(0, {distance:10}) - - - -import { SmartLed, LED_WS2812 } from "smartled"; -import * as colors from "./libs/colors.js" -import * as gpio from "gpio"; -import { stdout } from "stdio"; -import * as adc from "adc"; -import * as readline from "./libs/readline.js" -import * as radio from "simpleradio" -import * as motor from "./libs/motors.js" - -const A1 = 11; -const A2 = 12; -const B1 = 45; -const B2 = 11; - - -const motor_1 = new motor.Motor(A1, A2, 1, 2, 3, false); -const motor_2 = new motor.Motor(B1, B2, 1, 4, 5, false); - -radio.begin(8); -radio.on("string", (str:string, info:radio.PacketInfo) => { - let values = str.split(";") - motor_1.write(Number(values[0])) - motor_2.write(Number(values[1])) -}) - - -async function test() { - - motor_1.write(0); - await sleep(1000); - motor_1.write(0.5); - await sleep(1000); - motor_1.write(1); - await sleep(1000); - motor_1.write(0); - await sleep(1000); - motor_1.write(-1); - await sleep(1000); - motor_1.write(0); -} - -test()*/ diff --git a/docs/robutekLibrary/Robutek-library/build/libs/colors.js b/docs/robutekLibrary/Robutek-library/build/libs/colors.js deleted file mode 100644 index acd8f591..00000000 --- a/docs/robutekLibrary/Robutek-library/build/libs/colors.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Mezi jednotlivými reprezentacemi lze převádět - * @param hsl {Number} Hue (0-360), Saturation (0-1), Lightness (0-1) - * @returns {Rgb} Red (0-255), Green (0-255), Blue (0-255) - */ -export function hsl_to_rbg(hsl) { - const chroma = (1 - Math.abs(2 * hsl.l - 1) * hsl.s); - const hue = hsl.h / 60; - const x = chroma * (1 - Math.abs((hue % 2) - 1)); - let color = { r: 0, g: 0, b: 0 }; - if (hue > 0 && hue < 1) { - color = { r: chroma, g: x, b: 0 }; - } - else if (hue >= 1 && hue < 2) { - color = { r: x, g: chroma, b: 0 }; - } - else if (hue >= 2 && hue < 3) { - color = { r: 0, g: chroma, b: x }; - } - else if (hue >= 3 && hue < 4) { - color = { r: 0, g: x, b: chroma }; - } - else if (hue >= 4 && hue < 5) { - color = { r: x, g: 0, b: chroma }; - } - else { - color = { r: chroma, g: 0, b: x }; - } - const correction = hsl.l - chroma / 2; - color.r = (color.r + correction) * 255; - color.g = (color.g + correction) * 255; - color.b = (color.b + correction) * 255; - return color; -} -/** - * Funkce rainbow zafixuje sytost a světlost, a prochází barvami - * @param hue (0-360) - * @param brightness (0-100) - 50 je defaultní hodnota - * @returns {Rgb} - */ -export function rainbow(hue, brightness = 50) { - hue = Math.min(hue, 360); // Zajistíme, že zadaná hodnota není mimo rozsah - // fix range to 0-100 - let brightness_mapped = Math.min(Math.max(brightness, 0), 100); - return hsl_to_rbg({ h: hue, s: 1, l: brightness_mapped / 100 }); -} -/* Základní barvy pro LED pásky*/ -export const red = rainbow(0); -export const orange = rainbow(27); -export const yellow = rainbow(54); -export const green = rainbow(110); -export const light_blue = rainbow(177); -export const blue = rainbow(240); -export const purple = rainbow(285); -export const pink = rainbow(323); -export const white = { r: 100, g: 100, b: 100 }; -export const off = { r: 0, g: 0, b: 0 }; diff --git a/docs/robutekLibrary/Robutek-library/build/libs/custom_motors.js b/docs/robutekLibrary/Robutek-library/build/libs/custom_motors.js deleted file mode 100644 index e5cb22c3..00000000 --- a/docs/robutekLibrary/Robutek-library/build/libs/custom_motors.js +++ /dev/null @@ -1,40 +0,0 @@ -import * as ledc from "ledc"; -/** - * A class for controlling a servo motor. - */ -export class Motor { - /** - * Create a new servo object. - * @param pin1 The pin the servo is connected to. - * @param pin2 The pin the servo is connected to. - * @param timer The timer to use for PWM. - * @param channel1 The channel to use for PWM. - * @param channel2 The channel to use for PWM. - */ - constructor(pin1, pin2, timer, channel1, channel2) { - this.channel1 = channel1; - this.channel2 = channel2; - ledc.configureTimer(timer, 50, 12); // 50Hz is the frequency of a servo, 12 is the resolution of the timer - ledc.configureChannel(channel1, pin1, timer, 1023); // 1023 is the resolution of a servo - ledc.configureChannel(channel2, pin2, timer, 1023); // 1023 is the resolution of a servo - } - /** - * Set the servo position. - * @param value The position to set the servo to, from -1 to 1. - */ - write(value) { - console.log(value); - if (value > 0) { - ledc.setDuty(this.channel1, 1); - ledc.setDuty(this.channel2, (Math.abs(value) * 1023)); - } - else if (value < 0) { - ledc.setDuty(this.channel1, (Math.abs(value) * 1023)); - ledc.setDuty(this.channel2, 1); - } - else { - ledc.setDuty(this.channel1, 1); - ledc.setDuty(this.channel2, 1); - } - } -} diff --git a/docs/robutekLibrary/Robutek-library/build/libs/motors.js b/docs/robutekLibrary/Robutek-library/build/libs/motors.js deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/robutekLibrary/Robutek-library/build/libs/readline.js b/docs/robutekLibrary/Robutek-library/build/libs/readline.js deleted file mode 100644 index 0f6f797e..00000000 --- a/docs/robutekLibrary/Robutek-library/build/libs/readline.js +++ /dev/null @@ -1,66 +0,0 @@ -import { stdout, stdin } from "stdio"; -/** - * A class for reading standard input line by line. - */ -export class readline { - onGet(str) { - if (this.echo) { - stdout.write(str); - } - if (str == "\n") { - if (this.resolve) { - this.resolve(this.buffer); - } - this.buffer = ""; - this.promise = null; - this.resolve = null; - this.reject = null; - return; - } - this.buffer += str; - if (!this.closed) { - stdin.get() - .then((data) => this.onGet(data)) - .catch((reason) => { - if (this.reject) { - this.reject(reason); - } - }); - } - } - constructor(echo = false) { - this.buffer = ""; - this.promise = null; - this.resolve = null; - this.reject = null; - this.closed = false; - this.echo = echo; - } - read() { - if (this.promise != null) { - return Promise.reject("Already reading"); - } - this.promise = new Promise((resolve, reject) => { - this.resolve = resolve; - this.reject = reject; - }); - stdin.get() - .then((data) => this.onGet(data)) - .catch((reason) => { - if (this.reject) { - this.reject(reason); - } - }); - return this.promise; - } - close() { - this.closed = true; - if (this.reject) { - this.reject("Stopped"); - } - this.buffer = ""; - this.promise = null; - this.resolve = null; - this.reject = null; - } -} diff --git a/docs/robutekLibrary/Robutek-library/build/libs/robot.js b/docs/robutekLibrary/Robutek-library/build/libs/robot.js deleted file mode 100644 index 0996c056..00000000 --- a/docs/robutekLibrary/Robutek-library/build/libs/robot.js +++ /dev/null @@ -1,114 +0,0 @@ -/* -import * as motors from "motors" - -const leftMotor: motors.MotorConf = { neg: 1, pos: 2, encA: 3, encB: 4, encTicks: 200 } -const rightMotor: motors.MotorConf = { neg: 5, pos: 6, encA: 7, encB: 8, encTicks: 200, reverse: true } - -const wheels = new motors.Wheels({ left: leftMotor, right: rightMotor, diameter: 20, width: 100 }) -*/ -import * as adc from "adc"; -import * as gpio from "gpio"; -import { Servo } from "./servo.js"; -import { SmartLed, LED_WS2812 } from "smartled"; -export function init() { - adc.configure(Sensors.S_1, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 - adc.configure(Sensors.S_2, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 - adc.configure(Sensors.S_3, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 - adc.configure(Sensors.S_4, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 - gpio.pinMode(Sensors.S_PWR, gpio.PinMode.OUTPUT); // nastavíme mód pinu podsvícení na output - gpio.pinMode(Sensors.S_SW, gpio.PinMode.OUTPUT); - gpio.write(Sensors.S_PWR, 1); // zapneme podsvícení robůtka -} -export function move(curve, distance, time) { - //wheels.move(curve, {distance:distance, time:time}) -} -export function rotate(angle) { - //wheels.rotate(angle) -} -export function stop() { - //wheels.stop() -} -export class Sensors { - constructor() { - this.sw = 0; - } - async switch_sensors(to_value) { - if (to_value == this.sw) { - return; - } - this.sw = to_value; - gpio.write(Sensors.S_SW, to_value); - await sleep(5); - } - async read(sensor) { - switch (sensor) { - case 'W_FR': - this.switch_sensors(0); - return adc.read(Sensors.S_1); - case 'W_FL': - this.switch_sensors(0); - return adc.read(Sensors.S_2); - case 'W_BL': - this.switch_sensors(0); - return adc.read(Sensors.S_3); - case 'W_BR': - this.switch_sensors(0); - return adc.read(Sensors.S_4); - case 'L_FR': - this.switch_sensors(1); - return adc.read(Sensors.S_1); - case 'L_FL': - this.switch_sensors(1); - return adc.read(Sensors.S_2); - case 'L_BL': - this.switch_sensors(1); - return adc.read(Sensors.S_3); - case 'L_BR': - this.switch_sensors(1); - return adc.read(Sensors.S_4); - default: - return 0; - } - } -} -Sensors.S_1 = 4; -Sensors.S_2 = 5; -Sensors.S_3 = 6; -Sensors.S_4 = 7; -Sensors.S_SW = 8; -Sensors.S_PWR = 47; -export class Pen { - constructor(pin) { - this.servo = new Servo(pin, 1, 0); - } - /** - * Set the pen servo position. - * @param value The position to set the servo to, from 0 to 1023. - */ - move(value) { - this.servo.write(value); - } -} -Pen.UP = 512 + 180; -Pen.DOWN = 512 - 180; -Pen.MIDDLE = 512; -Pen.UNLOAD = 0; -export class Strip { - constructor() { - this.strip = new SmartLed(48, 9, LED_WS2812); - } - set(index, color) { - this.strip.set(index, color); - } - fill(color) { - for (let i = 0; i < 9; i++) { - this.strip.set(i, color); - } - } - show() { - this.strip.show(); - } - clear() { - this.strip.clear(); - } -} diff --git a/docs/robutekLibrary/Robutek-library/build/libs/servo.js b/docs/robutekLibrary/Robutek-library/build/libs/servo.js deleted file mode 100644 index 3300633a..00000000 --- a/docs/robutekLibrary/Robutek-library/build/libs/servo.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as ledc from "ledc"; -/** - * A class for controlling a servo motor. - */ -export class Servo { - /** - * Create a new servo object. - * @param pin The pin the servo is connected to. - * @param timer The timer to use for PWM. - * @param channel The channel to use for PWM. - */ - constructor(pin, timer, channel) { - this.channel = channel; - ledc.configureTimer(timer, 50, 12); // 50Hz is the frequency of a servo, 12 is the resolution of the timer - ledc.configureChannel(channel, pin, timer, 1023); // 1023 is the resolution of a servo - } - /** - * Set the servo position. - * @param value The position to set the servo to, from 0-1023. - */ - write(value) { - // map the value from 0-1023 to 0.5-2.5ms - const ms = ((value / 1023) * 2) + 0.5; // 0.5-2.5ms is the range of a servo - // convert to a duty cycle - const duty = (ms / 20) * 1023; // 20ms is the period of a servo - ledc.setDuty(this.channel, duty); // set the duty cycle to the servo - } -} From 76acc12514c831ae5bd64d19cdc4757257db2d50 Mon Sep 17 00:00:00 2001 From: c2coder Date: Sun, 7 Jul 2024 23:08:11 +0200 Subject: [PATCH 03/14] Add example code + fix rotate function --- .../Robutek-library/src/index.ts | 166 +++++++++++++++++- .../Robutek-library/src/libs/robot.ts | 13 +- 2 files changed, 167 insertions(+), 12 deletions(-) diff --git a/docs/robutekLibrary/Robutek-library/src/index.ts b/docs/robutekLibrary/Robutek-library/src/index.ts index 09ea8a5e..662e3afb 100644 --- a/docs/robutekLibrary/Robutek-library/src/index.ts +++ b/docs/robutekLibrary/Robutek-library/src/index.ts @@ -3,8 +3,6 @@ import * as gpio from "gpio"; import * as colors from "./libs/colors.js" - - import * as Robot from "./libs/robot.js" Robot.init() @@ -15,13 +13,14 @@ const sensors = new Robot.Sensors(); const strip = new Robot.Strip(); +/* ALL FUNCITONS Robot.Pins.ENC1_1 Robot.Pins.M1_1 Robot.Pins.Status_LED -strip.set(0, colors.rainbow(123, 5)); -strip.fill({ r: 0, g: 0, b: 0 }); strip.clear(); +strip.fill({ r: 0, g: 0, b: 0 }); +strip.set(0, colors.rainbow(123, 5)); strip.show(); @@ -29,14 +28,165 @@ pen.move(Robot.Pen.UP); pen.move(Robot.Pen.DOWN); pen.move(Robot.Pen.MIDDLE); pen.move(Robot.Pen.UNLOAD); -// pen.move(angle); - +pen.move(angle); sensors.read('W_FR').then(console.log) -// await sensors.read('W_FR') +await sensors.read('W_FR') + /* - * Naming: + * Naming for sensors: * WFR = Wheel Front Right (in front of right wheel) * LBL = Line Back Left (next to pen) */ + + + + + + +function mapRange(fromMin: number, fromMax: number, toMin: number, toMax: number, number: number): number { + return ((number - fromMin) * (toMax - toMin)) / (fromMax - fromMin) + toMin; +} + +function clamp(value: number, min: number, max: number): number { + return Math.min(Math.max(value, min), max); +} + + +gpio.pinMode(0, gpio.PinMode.INPUT_PULLUP); +gpio.pinMode(2, gpio.PinMode.INPUT_PULLUP); + +gpio.on("falling", 0, () => { + pen.move(Robot.Pen.DOWN) +}); + +gpio.on("falling", 2, () => { + pen.move(Robot.Pen.UP) +}); + + +async function readSensors() { + // + + const max_val: number = 300; + const max_rgb: number = 5; + + // read sensor; convert it to a range; display it on the strip + let sens_1: number = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('W_FR')), 0, max_rgb) + strip.set(1, { r: sens_1, g: sens_1, b: sens_1 }); + + let sens_2: number = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('W_FL')), 0, max_rgb) + strip.set(2, { r: sens_2, g: sens_2, b: sens_2 }); + + let sens_3: number = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('W_BL')), 0, max_rgb) + strip.set(3, { r: sens_3, g: sens_3, b: sens_3 }); + + let sens_4: number = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('W_BR')), 0, max_rgb) + strip.set(4, { r: sens_4, g: sens_4, b: sens_4 }); + + let sens_5: number = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('L_FR')), 0, max_rgb) + strip.set(5, { r: sens_5, g: sens_5, b: sens_5 }); + + let sens_6: number = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('L_FL')), 0, max_rgb) + strip.set(6, { r: sens_6, g: sens_6, b: sens_6 }); + + let sens_7: number = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('L_BL')), 0, max_rgb) + strip.set(7, { r: sens_7, g: sens_7, b: sens_7 }); + + let sens_8: number = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('L_BR')), 0, max_rgb) + strip.set(8, { r: sens_8, g: sens_8, b: sens_8 }); +} + + +setInterval(() => { + readSensors(); +}, 500); + + +let hue = 0; +async function rgb() { + //for (let i = 0; i < 9; i++) { + // let h = hue + // strip.set(i, colors.rainbow((hue + (12 * i)) % 360, 5)); + //} + strip.set(0, colors.rainbow(hue % 360, 5)); + hue += 1; + strip.show(); +} + +setInterval(() => { + rgb(); +}, 20); + + +Robot.stop(); + +let closeCon = false +let speed = 0.1; + +function onGet(data: String) { + switch (data) { + case "1": + speed = 0.1; + break; + case "2": + speed = 0.2; + break; + case "3": + speed = 0.3; + break; + case "4": + speed = 0.4; + break; + case "5": + speed = 0.5; + break; + case "6": + speed = 0.6; + break; + case "7": + speed = 0.7; + break; + case "8": + speed = 0.8; + break; + case "9": + speed = 0.9; + break; + case "0": + speed = 1; + break; + + + case "w": + Robot.move(0, { speed: speed }) + break; + case "s": + Robot.move(0, { speed: -speed }) + break; + case "a": + Robot.rotate({ angle: 90, speed: speed }) + break; + case "d": + Robot.rotate({ angle: -90, speed: speed }) + break; + case " ": + Robot.stop(); + break; + case "q": + Robot.stop(); + closeCon = true + break; + + default: + break; + } + + if (!closeCon) { + stdin.get().then((data) => onGet(data)) // recursive function + } +} + + +stdin.get().then((data) => onGet(data)) // start \ No newline at end of file diff --git a/docs/robutekLibrary/Robutek-library/src/libs/robot.ts b/docs/robutekLibrary/Robutek-library/src/libs/robot.ts index 6622ff4b..29cfd075 100644 --- a/docs/robutekLibrary/Robutek-library/src/libs/robot.ts +++ b/docs/robutekLibrary/Robutek-library/src/libs/robot.ts @@ -80,15 +80,20 @@ type RotateDuration = { } export function rotate(duration: RotateDuration) { - LMOT.write(duration.speed); - RMOT.write(duration.speed); + if (duration.angle < 0) { + LMOT.write(duration.speed); + RMOT.write(-duration.speed); + } else { + LMOT.write(-duration.speed); + RMOT.write(duration.speed); + } //wheels.rotate(duration) } export function stop() { - LMOT.write(1); - RMOT.write(1); + LMOT.write(0); + RMOT.write(0); //wheels.stop() } From 85c46147633024a1392379407bae78d324c2063e Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Mon, 8 Jul 2024 11:41:52 +0200 Subject: [PATCH 04/14] Update dts --- .../Robutek-library/@types/keyvalue.d.ts | 61 ++++++++++++++ .../Robutek-library/@types/motor.d.ts | 82 +++++++++++++++++++ .../Robutek-library/src/libs/robot.ts | 12 +-- 3 files changed, 149 insertions(+), 6 deletions(-) create mode 100644 docs/robutekLibrary/Robutek-library/@types/keyvalue.d.ts create mode 100644 docs/robutekLibrary/Robutek-library/@types/motor.d.ts diff --git a/docs/robutekLibrary/Robutek-library/@types/keyvalue.d.ts b/docs/robutekLibrary/Robutek-library/@types/keyvalue.d.ts new file mode 100644 index 00000000..761b09fb --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/@types/keyvalue.d.ts @@ -0,0 +1,61 @@ +declare module "keyvalue" { + class KeyValueNamespace { + /** + * Get saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present + */ + get(key: string): string | number | null; + + /** + * Get string saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present or if not string + */ + getString(key: string): string | null; + + /** + * Get number saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present or if not number + */ + getNumber(key: string): number | null; + + /** + * Set value in KeyValue namespace, overwriting any previous value. + * Call commit() after setting everything, otherwise changes will be lost! + * @param key key, max 15 characters. + * @param value value + */ + set(key: string, value: string | number): void; + + /** + * Erase value from KeyValue namespace. + * Call commit() after setting everything, otherwise changes will be lost! + * @param key key, max 15 characters. + */ + erase(key: string): void; + + /** + * Check existance of a key. + * @param key key, max 15 characters. + * @returns true if exists, false otherwise + */ + exists(key: string): boolean; + + /** + * Save modifications to persistent storage. + */ + commit(): void; + } + + /** + * Open a KeyValue namespace for use. KeyValue is a persitent storage for small + * pieces of data that saves values across restarts. + * + * The namespaces are isolated from each other. + * + * @param namespace Namespace name, max 15 characters. + */ + function open(namespace: string): KeyValueNamespace; +} diff --git a/docs/robutekLibrary/Robutek-library/@types/motor.d.ts b/docs/robutekLibrary/Robutek-library/@types/motor.d.ts new file mode 100644 index 00000000..2ec31392 --- /dev/null +++ b/docs/robutekLibrary/Robutek-library/@types/motor.d.ts @@ -0,0 +1,82 @@ +declare module "motor" { + + type MoveDuration = { + distance?: number; + time?: number; + } + + type MotorPins = { + motA: number; + motB: number; + encA: number; + encB: number; + }; + + type LedcConfig = { + timer: number; + channelA: number; + channelB: number; + }; + + class Motor { + /** + * Construc a new Motor instance + * @param options Motor configuration + * @note Units used in the diameter parameter determines the units used in the other methods + */ + constructor(options: { pins: MotorPins, ledc: LedcConfig, encTicks: number, diameter: number }); + + /** + * Set the speed of the motor + * @param speed Speed in units per second + * @note The units are the same as used in the diameter parameter + */ + setSpeed(speed: number): void; + // setRamp(ramp: number): void; + + /** + * Move the motor + * @param duration Duration of the movement + * @note The units are the same as used in the diameter parameter + * @note If the duration is not provided, the motor will move indefinitely + */ + move(duration?: MoveDuration): Promise; + + /** + * Stop the motor + * @param brake If true, the motor will brake, otherwise it will coast to a stop + */ + stop(brake?: boolean): Promise; + + /** + * Get the position of the motor + * @note The units are the same as used in the diameter parameter + * @returns The position of the motor + */ + getPosition(): number; + + /** + * Close the motor + */ + close(): void; + } + + /* class Wheels { + constructor(options: { + left: MotorPins, + right: MotorPins, + encTicks: number, + diameter: number, + spacing: number + }); + setSpeed(speed: number): void; + // setRamp(ramp: number): void; + + move(curve: number, duration?: MoveDuration): Promise; + // rotate(angle: number): Promise; + stop(brake?: boolean): Promise; + getPosition(): { left: number, right: number }; + + close(): void; + } */ +} diff --git a/docs/robutekLibrary/Robutek-library/src/libs/robot.ts b/docs/robutekLibrary/Robutek-library/src/libs/robot.ts index 29cfd075..44a4edaa 100644 --- a/docs/robutekLibrary/Robutek-library/src/libs/robot.ts +++ b/docs/robutekLibrary/Robutek-library/src/libs/robot.ts @@ -24,11 +24,11 @@ const RMOT = new motors.Motor(45, 13, 1, 4, 5); export function init() { adc.configure(Sensors.S_1, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 - adc.configure(Sensors.S_2, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 - adc.configure(Sensors.S_3, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 + adc.configure(Sensors.S_2, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 + adc.configure(Sensors.S_3, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 adc.configure(Sensors.S_4, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 - gpio.pinMode(Sensors.S_PWR, gpio.PinMode.OUTPUT); // nastavíme mód pinu podsvícení na output + gpio.pinMode(Sensors.S_PWR, gpio.PinMode.OUTPUT); // nastavíme mód pinu podsvícení na output gpio.pinMode(Sensors.S_SW, gpio.PinMode.OUTPUT); gpio.write(Sensors.S_PWR, 1); // zapneme podsvícení robůtka @@ -41,7 +41,7 @@ type MoveDuration = { speed?: number; // speed in mm/s? } export async function move(curve: number, duration?: MoveDuration) { - // curve = -1 to 1 + // curve = -1 to 1 // Ensure the curve is between -1 and 1 if (curve < -1) curve = -1; @@ -54,7 +54,7 @@ export async function move(curve: number, duration?: MoveDuration) { // Calculate motor speeds let m1: number; let m2: number; - + if (curve >= 0) { // Curve from 0 to 1 (m1 is max, m2 decreases) m1 = duration.speed; @@ -64,7 +64,7 @@ export async function move(curve: number, duration?: MoveDuration) { m1 = duration.speed * (1 + curve); // curve is negative, so this increases from 0 to speed m2 = duration.speed; } - + LMOT.write(m1) RMOT.write(m2) From 0ea8dfca7303c5b4cfb83c19b1e4ecadafeb578d Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Mon, 8 Jul 2024 11:56:00 +0200 Subject: [PATCH 05/14] Add minimal code --- .../Robutek-library/src/index.ts | 186 +----------------- 1 file changed, 2 insertions(+), 184 deletions(-) diff --git a/docs/robutekLibrary/Robutek-library/src/index.ts b/docs/robutekLibrary/Robutek-library/src/index.ts index 662e3afb..01a80dca 100644 --- a/docs/robutekLibrary/Robutek-library/src/index.ts +++ b/docs/robutekLibrary/Robutek-library/src/index.ts @@ -1,192 +1,10 @@ -import { stdin } from "stdio"; -import * as gpio from "gpio"; -import * as colors from "./libs/colors.js" - - import * as Robot from "./libs/robot.js" Robot.init() -const pen = new Robot.Pen(38); - -const sensors = new Robot.Sensors(); - const strip = new Robot.Strip(); - -/* ALL FUNCITONS -Robot.Pins.ENC1_1 -Robot.Pins.M1_1 -Robot.Pins.Status_LED - strip.clear(); -strip.fill({ r: 0, g: 0, b: 0 }); -strip.set(0, colors.rainbow(123, 5)); +strip.set(0, { r: 255, g: 0, b: 0 }); strip.show(); - -pen.move(Robot.Pen.UP); -pen.move(Robot.Pen.DOWN); -pen.move(Robot.Pen.MIDDLE); -pen.move(Robot.Pen.UNLOAD); -pen.move(angle); - -sensors.read('W_FR').then(console.log) -await sensors.read('W_FR') - - -/* - * Naming for sensors: - * WFR = Wheel Front Right (in front of right wheel) - * LBL = Line Back Left (next to pen) - */ - - - - - - -function mapRange(fromMin: number, fromMax: number, toMin: number, toMax: number, number: number): number { - return ((number - fromMin) * (toMax - toMin)) / (fromMax - fromMin) + toMin; -} - -function clamp(value: number, min: number, max: number): number { - return Math.min(Math.max(value, min), max); -} - - -gpio.pinMode(0, gpio.PinMode.INPUT_PULLUP); -gpio.pinMode(2, gpio.PinMode.INPUT_PULLUP); - -gpio.on("falling", 0, () => { - pen.move(Robot.Pen.DOWN) -}); - -gpio.on("falling", 2, () => { - pen.move(Robot.Pen.UP) -}); - - -async function readSensors() { - // - - const max_val: number = 300; - const max_rgb: number = 5; - - // read sensor; convert it to a range; display it on the strip - let sens_1: number = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('W_FR')), 0, max_rgb) - strip.set(1, { r: sens_1, g: sens_1, b: sens_1 }); - - let sens_2: number = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('W_FL')), 0, max_rgb) - strip.set(2, { r: sens_2, g: sens_2, b: sens_2 }); - - let sens_3: number = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('W_BL')), 0, max_rgb) - strip.set(3, { r: sens_3, g: sens_3, b: sens_3 }); - - let sens_4: number = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('W_BR')), 0, max_rgb) - strip.set(4, { r: sens_4, g: sens_4, b: sens_4 }); - - let sens_5: number = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('L_FR')), 0, max_rgb) - strip.set(5, { r: sens_5, g: sens_5, b: sens_5 }); - - let sens_6: number = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('L_FL')), 0, max_rgb) - strip.set(6, { r: sens_6, g: sens_6, b: sens_6 }); - - let sens_7: number = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('L_BL')), 0, max_rgb) - strip.set(7, { r: sens_7, g: sens_7, b: sens_7 }); - - let sens_8: number = clamp(mapRange(20, 1023, 0, max_val, await sensors.read('L_BR')), 0, max_rgb) - strip.set(8, { r: sens_8, g: sens_8, b: sens_8 }); -} - - -setInterval(() => { - readSensors(); -}, 500); - - -let hue = 0; -async function rgb() { - //for (let i = 0; i < 9; i++) { - // let h = hue - // strip.set(i, colors.rainbow((hue + (12 * i)) % 360, 5)); - //} - strip.set(0, colors.rainbow(hue % 360, 5)); - hue += 1; - strip.show(); -} - -setInterval(() => { - rgb(); -}, 20); - - -Robot.stop(); - -let closeCon = false -let speed = 0.1; - -function onGet(data: String) { - switch (data) { - case "1": - speed = 0.1; - break; - case "2": - speed = 0.2; - break; - case "3": - speed = 0.3; - break; - case "4": - speed = 0.4; - break; - case "5": - speed = 0.5; - break; - case "6": - speed = 0.6; - break; - case "7": - speed = 0.7; - break; - case "8": - speed = 0.8; - break; - case "9": - speed = 0.9; - break; - case "0": - speed = 1; - break; - - - case "w": - Robot.move(0, { speed: speed }) - break; - case "s": - Robot.move(0, { speed: -speed }) - break; - case "a": - Robot.rotate({ angle: 90, speed: speed }) - break; - case "d": - Robot.rotate({ angle: -90, speed: speed }) - break; - case " ": - Robot.stop(); - break; - case "q": - Robot.stop(); - closeCon = true - break; - - default: - break; - } - - if (!closeCon) { - stdin.get().then((data) => onGet(data)) // recursive function - } -} - - -stdin.get().then((data) => onGet(data)) // start \ No newline at end of file +console.log("Robotický tábor 2024") From 152e2c9913bebce9caa916c792032469dc7e41f6 Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Mon, 8 Jul 2024 13:03:44 +0200 Subject: [PATCH 06/14] Update robutek lib --- updates_robutek.sh | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100755 updates_robutek.sh diff --git a/updates_robutek.sh b/updates_robutek.sh new file mode 100755 index 00000000..13aa30e7 --- /dev/null +++ b/updates_robutek.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Tool for updating robutek.ts files in all repository +# Default location of robutek.ts is in docs/robutekLibrary/Robutek-library/src/libs/robutek.ts + +ROBUTEK_FILE="robutek.ts" +DEFAULT_ROBOTEK_PATH="docs/robutekLibrary/Robutek-library/src/libs/"$ROBUTEK_FILE + +# Check if exist DEFAULT_ROBOTEK_PATH +if [ ! -f "$DEFAULT_ROBOTEK_PATH" ]; then + echo "File $DEFAULT_ROBOTEK_PATH does not exist." + exit 1 +fi + + +echo "Updating robutek.ts in all repositories" +# Find ROBOTEK_FILE in all repositories an replace it with DEFAULT_ROBOTEK_PATH +for d in */ ; do + if [ -d "$d" ]; then + find $d -type f -name $ROBUTEK_FILE -exec echo "Updating $ROBUTEK_FILE in $d" \; -exec cp $DEFAULT_ROBOTEK_PATH $d$ROBUTEK_FILE \; + fi +done \ No newline at end of file From ea98f105fc14556ddbd6dbd343ff158fa5031edf Mon Sep 17 00:00:00 2001 From: c2coder Date: Mon, 8 Jul 2024 13:19:26 +0200 Subject: [PATCH 07/14] Update library --- .../Robutek-library/src/index.ts | 19 +++- .../Robutek-library/src/libs/motors.ts | 42 -------- .../Robutek-library/src/libs/robot.ts | 102 ++++++++---------- 3 files changed, 58 insertions(+), 105 deletions(-) delete mode 100644 docs/robutekLibrary/Robutek-library/src/libs/motors.ts diff --git a/docs/robutekLibrary/Robutek-library/src/index.ts b/docs/robutekLibrary/Robutek-library/src/index.ts index 01a80dca..5c69acf1 100644 --- a/docs/robutekLibrary/Robutek-library/src/index.ts +++ b/docs/robutekLibrary/Robutek-library/src/index.ts @@ -2,9 +2,20 @@ import * as Robot from "./libs/robot.js" Robot.init() -const strip = new Robot.Strip(); -strip.clear(); -strip.set(0, { r: 255, g: 0, b: 0 }); -strip.show(); +Robot.strip.clear(); +Robot.strip.set(0, { r: 255, g: 0, b: 0 }); +Robot.strip.show(); + +/* +async function main() { + await Robot.RightMot.move({time:Infinity}) + console.log("move"); +} +main(); + +setInterval(() => { + console.log(Robot.RightMot.getPosition()); +}, 100); +*/ console.log("Robotický tábor 2024") diff --git a/docs/robutekLibrary/Robutek-library/src/libs/motors.ts b/docs/robutekLibrary/Robutek-library/src/libs/motors.ts deleted file mode 100644 index 81795e56..00000000 --- a/docs/robutekLibrary/Robutek-library/src/libs/motors.ts +++ /dev/null @@ -1,42 +0,0 @@ -declare module "motors" { - - type MoveDuration = { - distance?: number; // distance in cm - negative = reverse - speed?: number; // speed in mm/s? - } - - type RotateDuration = { - angle?: number; // angle in degrees - speed?: number; // speed in mm/s? - } - - type MotorConf = { - pos: number; - neg: number; - encA: number; - encB: number; - encTicks: number; - reverse?: boolean; - }; - - class Motor { - constructor(options: { pins: MotorConf, diameter: number }); - setPower(power: number): void; - setRamp(ramp: number): void; - - move(duration?: MoveDuration): Promise; - stop(brq?: boolean): Promise; - getPosition(): number; - } - - class Wheels { - constructor(options: { left: MotorConf, right: MotorConf, diameter: number, width: number }); - setPower(power: number): void; - setRamp(ramp: number): void; - - move(curve: number, duration?: MoveDuration): Promise; - rotate(angle: number): Promise; - stop(brq?: boolean): Promise; - getPosition(): { left: number, right: number }; - } -} \ No newline at end of file diff --git a/docs/robutekLibrary/Robutek-library/src/libs/robot.ts b/docs/robutekLibrary/Robutek-library/src/libs/robot.ts index 44a4edaa..1f0a7f9a 100644 --- a/docs/robutekLibrary/Robutek-library/src/libs/robot.ts +++ b/docs/robutekLibrary/Robutek-library/src/libs/robot.ts @@ -6,32 +6,38 @@ import { Servo } from "./servo.js"; import { SmartLed, LED_WS2812, Rgb } from "smartled"; -/* -import * as motors from "motors" +import * as motors from "motor" -const leftMotor: motors.MotorConf = { neg: 1, pos: 2, encA: 3, encB: 4, encTicks: 200 } -const rightMotor: motors.MotorConf = { neg: 5, pos: 6, encA: 7, encB: 8, encTicks: 200, reverse: true } +const leftMotorPins: motors.MotorPins = { motA: 11, motB: 12, encA: 39, encB: 40 } +const rightMotorPins: motors.MotorPins = { motA: 45, motB: 13, encA: 41, encB: 42 } -const wheels = new motors.Wheels({ left: leftMotor, right: rightMotor, diameter: 20, width: 100 }) -*/ +const leftMotorLedc: motors.LedcConfig = { timer: 1, channelA: 0, channelB: 1 } +const rightMotorLedc: motors.LedcConfig = { timer: 1, channelA: 2, channelB: 3 } + +export const LeftMot = new motors.Motor({pins:leftMotorPins, ledc:leftMotorLedc, encTicks:400, diameter:10}); +export const RightMot = new motors.Motor({pins:rightMotorPins, ledc:rightMotorLedc, encTicks:400, diameter:10}); -import * as motors from "./simplemotors.js" +//const wheels = new motors.Wheels({ left: leftMotor, right: rightMotor, diameter: 20, width: 100 }) -const LMOT = new motors.Motor(11, 12, 1, 2, 3); -const RMOT = new motors.Motor(45, 13, 1, 4, 5); +/* + +import * as simplemotors from "./simplemotors.js" +const LMOT = new simplemotors.Motor(11, 12, 1, 2, 3); +const RMOT = new simplemotors.Motor(45, 13, 1, 4, 5); +*/ export function init() { - adc.configure(Sensors.S_1, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 - adc.configure(Sensors.S_2, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 - adc.configure(Sensors.S_3, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 - adc.configure(Sensors.S_4, adc.Attenuation.Db0); // pin senzoru nakonfigurujeme s útlumem nastaveným na 0 + adc.configure(LineSensors.S_1, adc.Attenuation.Db0); + adc.configure(LineSensors.S_2, adc.Attenuation.Db0); + adc.configure(LineSensors.S_3, adc.Attenuation.Db0); + adc.configure(LineSensors.S_4, adc.Attenuation.Db0); - gpio.pinMode(Sensors.S_PWR, gpio.PinMode.OUTPUT); // nastavíme mód pinu podsvícení na output - gpio.pinMode(Sensors.S_SW, gpio.PinMode.OUTPUT); + gpio.pinMode(LineSensors.S_PWR, gpio.PinMode.OUTPUT); + gpio.pinMode(LineSensors.S_SW, gpio.PinMode.OUTPUT); - gpio.write(Sensors.S_PWR, 1); // zapneme podsvícení robůtka + gpio.write(LineSensors.S_PWR, 1); } @@ -40,6 +46,7 @@ type MoveDuration = { distance?: number; // distance in cm speed?: number; // speed in mm/s? } +/* export async function move(curve: number, duration?: MoveDuration) { // curve = -1 to 1 @@ -79,7 +86,7 @@ type RotateDuration = { speed?: number; // speed in mm/s? } export function rotate(duration: RotateDuration) { - + if (duration.angle < 0) { LMOT.write(duration.speed); RMOT.write(-duration.speed); @@ -87,21 +94,22 @@ export function rotate(duration: RotateDuration) { LMOT.write(-duration.speed); RMOT.write(duration.speed); } - + //wheels.rotate(duration) } export function stop() { LMOT.write(0); RMOT.write(0); - + //wheels.stop() } +*/ export type SensorType = 'W_FR' | 'W_FL' | 'W_BL' | 'W_BR' | 'L_FR' | 'L_FL' | 'L_BL' | 'L_BR'; -export class Sensors { - +export class LineSensors { + public static readonly S_1: number = 4; public static readonly S_2: number = 5; public static readonly S_3: number = 6; @@ -117,7 +125,7 @@ export class Sensors { return; } this.sw = to_value; - gpio.write(Sensors.S_SW, to_value); + gpio.write(LineSensors.S_SW, to_value); await sleep(5); } @@ -125,36 +133,36 @@ export class Sensors { switch (sensor) { case 'W_FR': this.switch_sensors(0); - return adc.read(Sensors.S_1); + return adc.read(LineSensors.S_1); case 'W_FL': this.switch_sensors(0); - return adc.read(Sensors.S_2); + return adc.read(LineSensors.S_2); case 'W_BL': this.switch_sensors(0); - return adc.read(Sensors.S_3); + return adc.read(LineSensors.S_3); case 'W_BR': this.switch_sensors(0); - return adc.read(Sensors.S_4); + return adc.read(LineSensors.S_4); case 'L_FR': this.switch_sensors(1); - return adc.read(Sensors.S_1); + return adc.read(LineSensors.S_1); case 'L_FL': this.switch_sensors(1); - return adc.read(Sensors.S_2); + return adc.read(LineSensors.S_2); case 'L_BL': this.switch_sensors(1); - return adc.read(Sensors.S_3); + return adc.read(LineSensors.S_3); case 'L_BR': this.switch_sensors(1); - return adc.read(Sensors.S_4); + return adc.read(LineSensors.S_4); default: return 0; @@ -184,39 +192,15 @@ export class Pen { } } -export class Strip { - private strip: SmartLed; - - constructor() { - this.strip = new SmartLed(48, 9, LED_WS2812); - } - - public set(index: number, color: Rgb) { - this.strip.set(index, color); - } - - public fill(color: Rgb) { - for (let i = 0; i < 9; i++) { - this.strip.set(i, color); - } - } - - public show() { - this.strip.show(); - } - - public clear() { - this.strip.clear(); - } -} +export const strip = new SmartLed(48, 9, LED_WS2812); export class Pins { public static readonly Status_LED: number = 46; - public static readonly M1_1 = 11; - public static readonly M1_2 = 12; - public static readonly M2_1 = 45; - public static readonly M2_2 = 13; + public static readonly Motor1_A = 11; + public static readonly Motor1_B = 12; + public static readonly Motor2_A = 45; + public static readonly Motor2_B = 13; public static readonly ENC1_1 = 39; public static readonly ENC1_2 = 40; public static readonly ENC2_1 = 41; From ef9aa728eaa526d1ff450137decb3983876bed9a Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Mon, 8 Jul 2024 13:33:29 +0200 Subject: [PATCH 08/14] Rename to Robutek --- .../Robutek-library/src/index.ts | 21 ++--- .../src/libs/{robot.ts => robutek.ts} | 77 +------------------ updates_robutek.sh | 18 ++--- 3 files changed, 17 insertions(+), 99 deletions(-) rename docs/robutekLibrary/Robutek-library/src/libs/{robot.ts => robutek.ts} (65%) diff --git a/docs/robutekLibrary/Robutek-library/src/index.ts b/docs/robutekLibrary/Robutek-library/src/index.ts index 5c69acf1..f66cccdf 100644 --- a/docs/robutekLibrary/Robutek-library/src/index.ts +++ b/docs/robutekLibrary/Robutek-library/src/index.ts @@ -1,21 +1,10 @@ -import * as Robot from "./libs/robot.js" +import * as Robutek from "./libs/robutek.js" -Robot.init() +Robutek.init() -Robot.strip.clear(); -Robot.strip.set(0, { r: 255, g: 0, b: 0 }); -Robot.strip.show(); +Robutek.strip.clear(); +Robutek.strip.set(0, { r: 255, g: 0, b: 0 }); +Robutek.strip.show(); -/* -async function main() { - await Robot.RightMot.move({time:Infinity}) - console.log("move"); -} -main(); - -setInterval(() => { - console.log(Robot.RightMot.getPosition()); -}, 100); -*/ console.log("Robotický tábor 2024") diff --git a/docs/robutekLibrary/Robutek-library/src/libs/robot.ts b/docs/robutekLibrary/Robutek-library/src/libs/robutek.ts similarity index 65% rename from docs/robutekLibrary/Robutek-library/src/libs/robot.ts rename to docs/robutekLibrary/Robutek-library/src/libs/robutek.ts index 1f0a7f9a..2b72e543 100644 --- a/docs/robutekLibrary/Robutek-library/src/libs/robot.ts +++ b/docs/robutekLibrary/Robutek-library/src/libs/robutek.ts @@ -4,7 +4,7 @@ import * as gpio from "gpio"; import { Servo } from "./servo.js"; -import { SmartLed, LED_WS2812, Rgb } from "smartled"; +import { SmartLed, LED_WS2812 } from "smartled"; import * as motors from "motor" @@ -14,19 +14,9 @@ const rightMotorPins: motors.MotorPins = { motA: 45, motB: 13, encA: 41, encB: 4 const leftMotorLedc: motors.LedcConfig = { timer: 1, channelA: 0, channelB: 1 } const rightMotorLedc: motors.LedcConfig = { timer: 1, channelA: 2, channelB: 3 } -export const LeftMot = new motors.Motor({pins:leftMotorPins, ledc:leftMotorLedc, encTicks:400, diameter:10}); -export const RightMot = new motors.Motor({pins:rightMotorPins, ledc:rightMotorLedc, encTicks:400, diameter:10}); +export const LeftMot = new motors.Motor({ pins: leftMotorPins, ledc: leftMotorLedc, encTicks: 400, diameter: 10 }); +export const RightMot = new motors.Motor({ pins: rightMotorPins, ledc: rightMotorLedc, encTicks: 400, diameter: 10 }); -//const wheels = new motors.Wheels({ left: leftMotor, right: rightMotor, diameter: 20, width: 100 }) - -/* - -import * as simplemotors from "./simplemotors.js" - -const LMOT = new simplemotors.Motor(11, 12, 1, 2, 3); -const RMOT = new simplemotors.Motor(45, 13, 1, 4, 5); - -*/ export function init() { adc.configure(LineSensors.S_1, adc.Attenuation.Db0); @@ -46,70 +36,11 @@ type MoveDuration = { distance?: number; // distance in cm speed?: number; // speed in mm/s? } -/* -export async function move(curve: number, duration?: MoveDuration) { - // curve = -1 to 1 - - // Ensure the curve is between -1 and 1 - if (curve < -1) curve = -1; - if (curve > 1) curve = 1; - - if (duration.speed != undefined) { - // Just a temporary motor driver - - - // Calculate motor speeds - let m1: number; - let m2: number; - - if (curve >= 0) { - // Curve from 0 to 1 (m1 is max, m2 decreases) - m1 = duration.speed; - m2 = duration.speed * (1 - curve); - } else { - // Curve from -1 to 0 (m2 is max, m1 decreases) - m1 = duration.speed * (1 + curve); // curve is negative, so this increases from 0 to speed - m2 = duration.speed; - } - - LMOT.write(m1) - RMOT.write(m2) - - } - - //await wheels.move(curve, duration) -} - - -type RotateDuration = { - angle?: number; // angle in degrees - speed?: number; // speed in mm/s? -} -export function rotate(duration: RotateDuration) { - - if (duration.angle < 0) { - LMOT.write(duration.speed); - RMOT.write(-duration.speed); - } else { - LMOT.write(-duration.speed); - RMOT.write(duration.speed); - } - - //wheels.rotate(duration) -} - -export function stop() { - LMOT.write(0); - RMOT.write(0); - - //wheels.stop() -} -*/ export type SensorType = 'W_FR' | 'W_FL' | 'W_BL' | 'W_BR' | 'L_FR' | 'L_FL' | 'L_BL' | 'L_BR'; export class LineSensors { - + public static readonly S_1: number = 4; public static readonly S_2: number = 5; public static readonly S_3: number = 6; diff --git a/updates_robutek.sh b/updates_robutek.sh index 13aa30e7..b9f84675 100755 --- a/updates_robutek.sh +++ b/updates_robutek.sh @@ -1,22 +1,20 @@ #!/bin/bash -# Tool for updating robutek.ts files in all repository +# Tool for updating robutek.ts from lib to all folders from running location # Default location of robutek.ts is in docs/robutekLibrary/Robutek-library/src/libs/robutek.ts ROBUTEK_FILE="robutek.ts" -DEFAULT_ROBOTEK_PATH="docs/robutekLibrary/Robutek-library/src/libs/"$ROBUTEK_FILE +DEFAULT_ROBOTEK_PATH="docs/robutekLibrary/Robutek-library/src/libs/$ROBUTEK_FILE" -# Check if exist DEFAULT_ROBOTEK_PATH +# Check if the default Robutek file exists if [ ! -f "$DEFAULT_ROBOTEK_PATH" ]; then echo "File $DEFAULT_ROBOTEK_PATH does not exist." exit 1 fi +echo "Updating robutek.ts in all repositories - replacing all files with the new one" -echo "Updating robutek.ts in all repositories" -# Find ROBOTEK_FILE in all repositories an replace it with DEFAULT_ROBOTEK_PATH -for d in */ ; do - if [ -d "$d" ]; then - find $d -type f -name $ROBUTEK_FILE -exec echo "Updating $ROBUTEK_FILE in $d" \; -exec cp $DEFAULT_ROBOTEK_PATH $d$ROBUTEK_FILE \; - fi -done \ No newline at end of file +# Find and replace all instances of robutek.ts +find . -type f -name "$ROBUTEK_FILE" -exec cp "$DEFAULT_ROBOTEK_PATH" {} \; + +echo "Update completed." From 6f465a717ce2ae6738aca0f6c4d79b1598ee530a Mon Sep 17 00:00:00 2001 From: c2coder Date: Mon, 8 Jul 2024 13:38:17 +0200 Subject: [PATCH 09/14] Update example project --- docs/robot/lekce1/example1/@types/adc.d.ts | 12 +- docs/robot/lekce1/example1/@types/gpio.d.ts | 16 +- docs/robot/lekce1/example1/@types/gridui.d.ts | 321 ++++++++++++++++++ .../lekce1/example1/@types/keyvalue.d.ts | 61 ++++ docs/robot/lekce1/example1/@types/motor.d.ts | 82 +++++ .../lekce1/example1/@types/pulseCounter.d.ts | 64 ++++ .../lekce1/example1/@types/timestamp.d.ts | 11 + docs/robot/lekce1/example1/@types/wifi.d.ts | 6 + docs/robot/lekce1/example1/src/index.ts | 21 +- docs/robot/lekce1/example1/src/libs/colors.ts | 160 ++++----- .../lekce1/example1/src/libs/readline.ts | 168 ++++----- .../robot/lekce1/example1/src/libs/robutek.ts | 139 ++++++++ docs/robot/lekce1/example1/src/libs/servo.ts | 68 ++-- .../lekce1/example1/src/libs/simplemotors.ts | 47 +++ docs/robot/lekce1/example1/tsconfig.json | 22 +- .../Robutek-library/src/index.ts | 12 +- .../Robutek-library/src/libs/robutek.ts | 2 +- 17 files changed, 978 insertions(+), 234 deletions(-) create mode 100644 docs/robot/lekce1/example1/@types/gridui.d.ts create mode 100644 docs/robot/lekce1/example1/@types/keyvalue.d.ts create mode 100644 docs/robot/lekce1/example1/@types/motor.d.ts create mode 100644 docs/robot/lekce1/example1/@types/pulseCounter.d.ts create mode 100644 docs/robot/lekce1/example1/@types/timestamp.d.ts create mode 100644 docs/robot/lekce1/example1/@types/wifi.d.ts create mode 100644 docs/robot/lekce1/example1/src/libs/robutek.ts create mode 100644 docs/robot/lekce1/example1/src/libs/simplemotors.ts diff --git a/docs/robot/lekce1/example1/@types/adc.d.ts b/docs/robot/lekce1/example1/@types/adc.d.ts index d77e9d94..871de201 100644 --- a/docs/robot/lekce1/example1/@types/adc.d.ts +++ b/docs/robot/lekce1/example1/@types/adc.d.ts @@ -1,9 +1,19 @@ declare module "adc" { + type Atten = number; + + const Attenuation: { + readonly Db0: Atten; + readonly Db2_5: Atten; + readonly Db6: Atten; + readonly Db11: Atten; + }; + + /** * Enable ADC on the given pin. * @param pin The pin to enable ADC on. */ - function configure(pin: number): void; + function configure(pin: number, attenuation?: Atten): void; /** * Read the value of the given pin. diff --git a/docs/robot/lekce1/example1/@types/gpio.d.ts b/docs/robot/lekce1/example1/@types/gpio.d.ts index 9c74419e..09c167db 100644 --- a/docs/robot/lekce1/example1/@types/gpio.d.ts +++ b/docs/robot/lekce1/example1/@types/gpio.d.ts @@ -1,12 +1,16 @@ declare module "gpio" { const PinMode: { - DISABLE: number, - OUTPUT: number, - INPUT: number, - INPUT_PULLUP: number, - INPUT_PULLDOWN: number, + readonly DISABLE: number, + readonly OUTPUT: number, + readonly INPUT: number, + readonly INPUT_PULLUP: number, + readonly INPUT_PULLDOWN: number, }; + interface EventInfo { + timestamp: Timestamp; + } + /** * Configure the given pin. * @param pin The pin to configure. @@ -34,7 +38,7 @@ declare module "gpio" { * @param pin The pin to handle the event for. * @param callback The callback to call when the event occurs. */ - function on(event: "rising" | "falling" | "change", pin: number, callback: () => void): void; + function on(event: "rising" | "falling" | "change", pin: number, callback: (info: EventInfo) => void): void; /** * Remove event handler for the given pin. diff --git a/docs/robot/lekce1/example1/@types/gridui.d.ts b/docs/robot/lekce1/example1/@types/gridui.d.ts new file mode 100644 index 00000000..8ffaba74 --- /dev/null +++ b/docs/robot/lekce1/example1/@types/gridui.d.ts @@ -0,0 +1,321 @@ +declare module "gridui" { + namespace widget { + interface Base { + readonly uuid: number + widgetX: number + widgetY: number + widgetW: number + widgetH: number + widgetTab: number + css(key: string): string + setCss(key: string, value: string): void + } + + interface Arm extends Base { + readonly x: number + readonly y: number + } + + interface Bar extends Base { + color: string + fontSize: number + min: number + max: number + value: number + showValue: boolean + } + + interface Button extends Base { + text: string + fontSize: number + color: string + background: string + align: string + valign: string + disabled: boolean + readonly pressed: boolean + } + + interface Camera extends Base { + rotation: number + clip: boolean + } + + interface Checkbox extends Base { + fontSize: number + checked: boolean + color: string + text: string + } + + interface Circle extends Base { + color: string + fontSize: number + min: number + max: number + lineWidth: number + valueStart: number + value: number + showValue: boolean + } + + interface Input extends Base { + text: string + color: string + type: string + disabled: boolean + } + + interface Joystick extends Base { + color: string + keys: string + text: string + readonly x: number + readonly y: number + } + + interface Led extends Base { + color: string + on: boolean + } + + interface Orientation extends Base { + color: string + readonly yaw: number + readonly pitch: number + readonly roll: number + readonly joystickX: number + readonly joystickY: number + } + + interface Select extends Base { + color: string + background: string + disabled: boolean + options: string + selectedIndex: number + } + + interface Slider extends Base { + color: string + fontSize: number + min: number + max: number + value: number + precision: number + showValue: boolean + } + + interface SpinEdit extends Base { + fontSize: number + color: string + value: number + step: number + precision: number + } + + interface Switcher extends Base { + fontSize: number + color: string + value: number + min: number + max: number + } + + interface Text extends Base { + text: string + fontSize: number + color: string + background: string + align: string + valign: string + prefix: string + suffix: string + } + } + + namespace builder { + interface Base { + css(key: string, value: string): this + finish(): this + } + + interface Arm extends Base { + info(info: Record): Arm + + onGrab(callback: (arm: widget.Arm) => void): Arm + onPositionChanged(callback: (arm: widget.Arm) => void): Arm + } + + interface Bar extends Base { + color(color: string): Bar + fontSize(fontSize: number): Bar + min(min: number): Bar + max(max: number): Bar + value(value: number): Bar + showValue(showValue: boolean): Bar + } + + interface Button extends Base { + text(text: string): Button + fontSize(fontSize: number): Button + color(color: string): Button + background(background: string): Button + align(align: string): Button + valign(valign: string): Button + disabled(disabled: boolean): Button + + onPress(callback: (button: widget.Button) => void): Button + onRelease(callback: (button: widget.Button) => void): Button + } + + interface Camera extends Base { + rotation(rotation: number): Camera + clip(clip: boolean): Camera + tags(tags: any /* TODO: fix type */): Camera + } + + interface Checkbox extends Base { + fontSize(fontSize: number): Checkbox + checked(checked: boolean): Checkbox + color(color: string): Checkbox + text(text: string): Checkbox + + onChanged(callback: (checkbox: widget.Checkbox) => void): Checkbox + } + + interface Circle extends Base { + color(color: string): Circle + fontSize(fontSize: number): Circle + min(min: number): Circle + max(max: number): Circle + lineWidth(lineWidth: number): Circle + valueStart(valueStart: number): Circle + value(value: number): Circle + showValue(showValue: boolean): Circle + } + + interface Input extends Base { + text(text: string): Input + color(color: string): Input + type(type: string): Input + disabled(disabled: boolean): Input + + onChanged(callback: (input: widget.Input) => void): Input + } + + interface Joystick extends Base { + color(color: string): Joystick + keys(keys: string): Joystick + text(text: string): Joystick + + onClick(callback: (joystick: widget.Joystick) => void): Joystick + onPositionChanged(callback: (joystick: widget.Joystick) => void): Joystick + } + + interface Led extends Base { + color(color: string): Led + on(on: boolean): Led + } + + interface Orientation extends Base { + color(color: string): Orientation + + onPositionChanged(callback: (orientation: widget.Orientation) => void): Orientation + } + + interface Select extends Base { + color(color: string): Select + background(background: string): Select + disabled(disabled: boolean): Select + options(options: string): Select + selectedIndex(selectedIndex: number): Select + + onChanged(callback: (select: widget.Select) => void): Select + } + + interface Slider extends Base { + color(color: string): Slider + fontSize(fontSize: number): Slider + min(min: number): Slider + max(max: number): Slider + value(value: number): Slider + precision(precision: number): Slider + showValue(showValue: boolean): Slider + + onChanged(callback: (slider: widget.Slider) => void): Slider + } + + interface SpinEdit extends Base { + fontSize(fontSize: number): SpinEdit + color(color: string): SpinEdit + value(value: number): SpinEdit + step(step: number): SpinEdit + precision(precision: number): SpinEdit + + onChanged(callback: (spinEdit: widget.SpinEdit) => void): SpinEdit + } + + interface Switcher extends Base { + fontSize(fontSize: number): Switcher + color(color: string): Switcher + value(value: number): Switcher + min(min: number): Switcher + max(max: number): Switcher + + onChanged(callback: (switcher: widget.Switcher) => void): Switcher + } + + interface Text extends Base { + text(text: string): Text + fontSize(fontSize: number): Text + color(color: string): Text + background(background: string): Text + align(align: string): Text + valign(valign: string): Text + prefix(prefix: string): Text + suffix(suffix: string): Text + } + + interface Widget extends Base {} + } + + class Builder { + arm(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Arm + bar(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Bar + button(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Button + camera(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Camera + checkbox(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Checkbox + circle(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Circle + input(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Input + joystick(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Joystick + led(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Led + orientation(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Orientation + select(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Select + slider(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Slider + spinEdit(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.SpinEdit + switcher(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Switcher + text(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Text + widget(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Widget + } + + /** + * Initialize GridUI. + * @param ownerName name of the owner, must match the name entered in RBController app. + * @param deviceName name of this device, visible in the RBController app. + * @param builderCallback callback, which receives the builder instance that can be used to create widgets. + */ + function begin(ownerName: string, deviceName: string, builderCallback: (builder: Builder) => void): void + + /** + * Stop GridUI. + */ + function end(): void + + /** + * Returns included GridUI version as number, to be compared with hex representation of the version. + * + * For example, for version 5.1.0, do: `gridui.version() >= 0x050100` + */ + function version(): number +} diff --git a/docs/robot/lekce1/example1/@types/keyvalue.d.ts b/docs/robot/lekce1/example1/@types/keyvalue.d.ts new file mode 100644 index 00000000..761b09fb --- /dev/null +++ b/docs/robot/lekce1/example1/@types/keyvalue.d.ts @@ -0,0 +1,61 @@ +declare module "keyvalue" { + class KeyValueNamespace { + /** + * Get saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present + */ + get(key: string): string | number | null; + + /** + * Get string saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present or if not string + */ + getString(key: string): string | null; + + /** + * Get number saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present or if not number + */ + getNumber(key: string): number | null; + + /** + * Set value in KeyValue namespace, overwriting any previous value. + * Call commit() after setting everything, otherwise changes will be lost! + * @param key key, max 15 characters. + * @param value value + */ + set(key: string, value: string | number): void; + + /** + * Erase value from KeyValue namespace. + * Call commit() after setting everything, otherwise changes will be lost! + * @param key key, max 15 characters. + */ + erase(key: string): void; + + /** + * Check existance of a key. + * @param key key, max 15 characters. + * @returns true if exists, false otherwise + */ + exists(key: string): boolean; + + /** + * Save modifications to persistent storage. + */ + commit(): void; + } + + /** + * Open a KeyValue namespace for use. KeyValue is a persitent storage for small + * pieces of data that saves values across restarts. + * + * The namespaces are isolated from each other. + * + * @param namespace Namespace name, max 15 characters. + */ + function open(namespace: string): KeyValueNamespace; +} diff --git a/docs/robot/lekce1/example1/@types/motor.d.ts b/docs/robot/lekce1/example1/@types/motor.d.ts new file mode 100644 index 00000000..2ec31392 --- /dev/null +++ b/docs/robot/lekce1/example1/@types/motor.d.ts @@ -0,0 +1,82 @@ +declare module "motor" { + + type MoveDuration = { + distance?: number; + time?: number; + } + + type MotorPins = { + motA: number; + motB: number; + encA: number; + encB: number; + }; + + type LedcConfig = { + timer: number; + channelA: number; + channelB: number; + }; + + class Motor { + /** + * Construc a new Motor instance + * @param options Motor configuration + * @note Units used in the diameter parameter determines the units used in the other methods + */ + constructor(options: { pins: MotorPins, ledc: LedcConfig, encTicks: number, diameter: number }); + + /** + * Set the speed of the motor + * @param speed Speed in units per second + * @note The units are the same as used in the diameter parameter + */ + setSpeed(speed: number): void; + // setRamp(ramp: number): void; + + /** + * Move the motor + * @param duration Duration of the movement + * @note The units are the same as used in the diameter parameter + * @note If the duration is not provided, the motor will move indefinitely + */ + move(duration?: MoveDuration): Promise; + + /** + * Stop the motor + * @param brake If true, the motor will brake, otherwise it will coast to a stop + */ + stop(brake?: boolean): Promise; + + /** + * Get the position of the motor + * @note The units are the same as used in the diameter parameter + * @returns The position of the motor + */ + getPosition(): number; + + /** + * Close the motor + */ + close(): void; + } + + /* class Wheels { + constructor(options: { + left: MotorPins, + right: MotorPins, + encTicks: number, + diameter: number, + spacing: number + }); + setSpeed(speed: number): void; + // setRamp(ramp: number): void; + + move(curve: number, duration?: MoveDuration): Promise; + // rotate(angle: number): Promise; + stop(brake?: boolean): Promise; + getPosition(): { left: number, right: number }; + + close(): void; + } */ +} diff --git a/docs/robot/lekce1/example1/@types/pulseCounter.d.ts b/docs/robot/lekce1/example1/@types/pulseCounter.d.ts new file mode 100644 index 00000000..76ad8edd --- /dev/null +++ b/docs/robot/lekce1/example1/@types/pulseCounter.d.ts @@ -0,0 +1,64 @@ +declare module "pulseCounter" { + type LevelAction = number; + type EdgeAction = number; + + const LevelAction: { + Keep: LevelAction; + Inverse: LevelAction; + Hold: LevelAction; + }; + + const EdgeAction: { + Hold: EdgeAction; + Increase: EdgeAction; + Decrease: EdgeAction; + }; + + type LevelMode = { + low: LevelAction, + high: LevelAction, + } + + type EdgeMode = { + pos: EdgeAction, + neg: EdgeAction, + } + + class PulseCounter { + /** + * Create a new pulse counter instance. + * @param options The options for the pulse counter. + */ + constructor(options: { + pinLevel: number, + pinEdge: number, + levelMode: LevelMode, + edgeMode: EdgeMode, + }) + + /** + * Read the current value of the pulse counter. + */ + read(): number; + + /** + * Reset the pulse counter to 0. + */ + clear(): void; + + /** + * Start the pulse counter. + */ + start(): void; + + /** + * Stop the pulse counter. + */ + stop(): void; + + /** + * Close the pulse counter. + */ + close(): void; + } +} diff --git a/docs/robot/lekce1/example1/@types/timestamp.d.ts b/docs/robot/lekce1/example1/@types/timestamp.d.ts new file mode 100644 index 00000000..bd9ab6c8 --- /dev/null +++ b/docs/robot/lekce1/example1/@types/timestamp.d.ts @@ -0,0 +1,11 @@ +declare interface Timestamp { + /** + * Convert the timestamp to milliseconds. + */ + millis(): number; + + /** + * Get the lower 10^3 part of the timestamp in microseconds. + */ + micros(): number; +} diff --git a/docs/robot/lekce1/example1/@types/wifi.d.ts b/docs/robot/lekce1/example1/@types/wifi.d.ts new file mode 100644 index 00000000..7e532194 --- /dev/null +++ b/docs/robot/lekce1/example1/@types/wifi.d.ts @@ -0,0 +1,6 @@ +declare module "wifi" { + /** + * Return current IPv4 of the device, or null if WiFi is disabled or not connected. + */ + function currentIp(): string | null; +} diff --git a/docs/robot/lekce1/example1/src/index.ts b/docs/robot/lekce1/example1/src/index.ts index b34b971a..203ed5d5 100644 --- a/docs/robot/lekce1/example1/src/index.ts +++ b/docs/robot/lekce1/example1/src/index.ts @@ -1,11 +1,10 @@ -import { SmartLed, LED_WS2812 } from "smartled"; -import * as colors from "./libs/colors.js" - -const ledStrip = new SmartLed(48, 1, LED_WS2812); // připojí pásek na pin 48, s 1 ledkou a typem WS2812 - -ledStrip.set(0, colors.green); // nastaví barvu LED na desce na zelenou, LED na pásku začínají od 1 -ledStrip.show(); // zobrazí nastavení na LED - -setInterval(() => { // pravidelně vyvolává událost - console.log("Robotický tábor 2024, zdraví Jirka Vácha!"); // vypíše text: Robotický tábor 2024, zdraví Jirka Vácha! -}, 1000); // čas opakování se udává v milisekundách (1000 ms je 1 sekunda) +import * as Robutek from "./libs/robutek.js" + +Robutek.init() + +Robutek.ledStrip.clear(); +Robutek.ledStrip.set(0, { r: 255, g: 0, b: 0 }); +Robutek.ledStrip.show(); + + +console.log("Robotický tábor 2024") diff --git a/docs/robot/lekce1/example1/src/libs/colors.ts b/docs/robot/lekce1/example1/src/libs/colors.ts index 3f06631f..de76a121 100644 --- a/docs/robot/lekce1/example1/src/libs/colors.ts +++ b/docs/robot/lekce1/example1/src/libs/colors.ts @@ -1,81 +1,81 @@ -/** - * Barva je jednoduchá trojice červené, zelené, a modré složky - * - R: červená (rozsah 0-255) - * - G: zelená (rozsah 0-255) - * - B: modrá (rozsah 0-255) - */ -interface Rgb { - r: number; - g: number; - b: number; -} - - -/** - * Alternativní způsob, jak vyjádřit barvu, je HSL: - * - Hue: odstín (rozsah 0-360) - * - Saturation: sytost barev (rozsah 0-1) - * - Lightness: světlost (rozsah 0-1) - */ -interface Hsl { - h: number; - s: number; - l: number; -} - -/** - * Mezi jednotlivými reprezentacemi lze převádět - * @param hsl {Number} Hue (0-360), Saturation (0-1), Lightness (0-1) - * @returns {Rgb} Red (0-255), Green (0-255), Blue (0-255) - */ -export function hsl_to_rbg( hsl: Hsl ) : Rgb { - const chroma = ( 1 - Math.abs( 2 * hsl.l - 1 ) * hsl.s ); - const hue = hsl.h / 60; - const x = chroma * ( 1 - Math.abs( ( hue % 2 ) - 1 ) ); - - let color : Rgb = { r: 0, g: 0, b: 0 }; - if( hue > 0 && hue < 1 ){ - color = { r: chroma, g: x, b: 0 }; - } else if( hue >= 1 && hue < 2 ){ - color = { r: x, g: chroma, b: 0 }; - } else if( hue >= 2 && hue < 3 ){ - color = { r: 0, g: chroma, b: x }; - } else if( hue >= 3 && hue < 4 ){ - color = { r: 0, g: x, b: chroma }; - } else if( hue >= 4 && hue < 5 ){ - color = { r: x, g: 0, b: chroma }; - } else { - color = { r: chroma, g: 0, b: x }; - } - const correction = hsl.l - chroma / 2; - color.r = ( color.r + correction ) * 255; - color.g = ( color.g + correction ) * 255; - color.b = ( color.b + correction ) * 255; - - return color; -} - -/** - * Funkce rainbow zafixuje sytost a světlost, a prochází barvami - * @param hue (0-360) - * @param brightness (0-100) - 50 je defaultní hodnota - * @returns {Rgb} - */ -export function rainbow( hue: number, brightness: number = 50) : Rgb { - hue = Math.min( hue, 360 ); // Zajistíme, že zadaná hodnota není mimo rozsah - // fix range to 0-100 - let brightness_mapped = Math.min(Math.max(brightness, 0), 100); - return hsl_to_rbg( { h: hue, s: 1, l: brightness_mapped / 100 } ); -} - -/* Základní barvy pro LED pásky*/ -export const red = rainbow( 0 ); -export const orange = rainbow( 27 ); -export const yellow = rainbow( 54 ); -export const green = rainbow( 110 ); -export const light_blue = rainbow( 177 ); -export const blue = rainbow( 240 ); -export const purple = rainbow( 285 ); -export const pink = rainbow( 323 ); -export const white : Rgb = { r: 100, g: 100, b: 100 }; +/** + * Barva je jednoduchá trojice červené, zelené, a modré složky + * - R: červená (rozsah 0-255) + * - G: zelená (rozsah 0-255) + * - B: modrá (rozsah 0-255) + */ +interface Rgb { + r: number; + g: number; + b: number; +} + + +/** + * Alternativní způsob, jak vyjádřit barvu, je HSL: + * - Hue: odstín (rozsah 0-360) + * - Saturation: sytost barev (rozsah 0-1) + * - Lightness: světlost (rozsah 0-1) + */ +interface Hsl { + h: number; + s: number; + l: number; +} + +/** + * Mezi jednotlivými reprezentacemi lze převádět + * @param hsl {Number} Hue (0-360), Saturation (0-1), Lightness (0-1) + * @returns {Rgb} Red (0-255), Green (0-255), Blue (0-255) + */ +export function hsl_to_rbg( hsl: Hsl ) : Rgb { + const chroma = ( 1 - Math.abs( 2 * hsl.l - 1 ) * hsl.s ); + const hue = hsl.h / 60; + const x = chroma * ( 1 - Math.abs( ( hue % 2 ) - 1 ) ); + + let color : Rgb = { r: 0, g: 0, b: 0 }; + if( hue > 0 && hue < 1 ){ + color = { r: chroma, g: x, b: 0 }; + } else if( hue >= 1 && hue < 2 ){ + color = { r: x, g: chroma, b: 0 }; + } else if( hue >= 2 && hue < 3 ){ + color = { r: 0, g: chroma, b: x }; + } else if( hue >= 3 && hue < 4 ){ + color = { r: 0, g: x, b: chroma }; + } else if( hue >= 4 && hue < 5 ){ + color = { r: x, g: 0, b: chroma }; + } else { + color = { r: chroma, g: 0, b: x }; + } + const correction = hsl.l - chroma / 2; + color.r = ( color.r + correction ) * 255; + color.g = ( color.g + correction ) * 255; + color.b = ( color.b + correction ) * 255; + + return color; +} + +/** + * Funkce rainbow zafixuje sytost a světlost, a prochází barvami + * @param hue (0-360) + * @param brightness (0-100) - 50 je defaultní hodnota + * @returns {Rgb} + */ +export function rainbow( hue: number, brightness: number = 50) : Rgb { + hue = Math.min( hue, 360 ); // Zajistíme, že zadaná hodnota není mimo rozsah + // fix range to 0-100 + let brightness_mapped = Math.min(Math.max(brightness, 0), 100); + return hsl_to_rbg( { h: hue, s: 1, l: brightness_mapped / 100 } ); +} + +/* Základní barvy pro LED pásky*/ +export const red = rainbow( 0 ); +export const orange = rainbow( 27 ); +export const yellow = rainbow( 54 ); +export const green = rainbow( 110 ); +export const light_blue = rainbow( 177 ); +export const blue = rainbow( 240 ); +export const purple = rainbow( 285 ); +export const pink = rainbow( 323 ); +export const white : Rgb = { r: 100, g: 100, b: 100 }; export const off : Rgb = { r: 0, g: 0, b: 0 }; \ No newline at end of file diff --git a/docs/robot/lekce1/example1/src/libs/readline.ts b/docs/robot/lekce1/example1/src/libs/readline.ts index 0c2bf751..4372efd2 100644 --- a/docs/robot/lekce1/example1/src/libs/readline.ts +++ b/docs/robot/lekce1/example1/src/libs/readline.ts @@ -1,84 +1,84 @@ -import { stdout, stdin } from "stdio"; - -/** - * A class for reading standard input line by line. - */ - -export class readline { - private buffer: string = ""; - private promise: Promise | null = null; - private resolve: ((value: string) => void) | null = null; - private reject: ((reason: any) => void) | null = null; - private closed: boolean = false; - private echo: boolean; - - private onGet(str: string) { - if (this.echo) { - stdout.write(str); - } - - if (str == "\n") { - if (this.resolve) { - this.resolve(this.buffer); - } - - this.buffer = ""; - this.promise = null; - this.resolve = null; - this.reject = null; - - return; - } - - this.buffer += str; - - if (!this.closed) { - stdin.get() - .then((data) => this.onGet(data)) - .catch((reason) => { - if (this.reject) { - this.reject(reason); - } - }); - } - - } - - constructor(echo: boolean = false) { - this.echo = echo; - } - - public read(): Promise { - if (this.promise != null) { - return Promise.reject("Already reading"); - } - - this.promise = new Promise((resolve, reject) => { - this.resolve = resolve; - this.reject = reject; - }); - - stdin.get() - .then((data) => this.onGet(data)) - .catch((reason) => { - if (this.reject) { - this.reject(reason); - } - }); - - return this.promise; - } - - public close() { - this.closed = true; - - if (this.reject) { - this.reject("Stopped"); - } - - this.buffer = ""; - this.promise = null; - this.resolve = null; - this.reject = null; - } -} +import { stdout, stdin } from "stdio"; + +/** + * A class for reading standard input line by line. + */ + +export class readline { + private buffer: string = ""; + private promise: Promise | null = null; + private resolve: ((value: string) => void) | null = null; + private reject: ((reason: any) => void) | null = null; + private closed: boolean = false; + private echo: boolean; + + private onGet(str: string) { + if (this.echo) { + stdout.write(str); + } + + if (str == "\n") { + if (this.resolve) { + this.resolve(this.buffer); + } + + this.buffer = ""; + this.promise = null; + this.resolve = null; + this.reject = null; + + return; + } + + this.buffer += str; + + if (!this.closed) { + stdin.get() + .then((data) => this.onGet(data)) + .catch((reason) => { + if (this.reject) { + this.reject(reason); + } + }); + } + + } + + constructor(echo: boolean = false) { + this.echo = echo; + } + + public read(): Promise { + if (this.promise != null) { + return Promise.reject("Already reading"); + } + + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + + stdin.get() + .then((data) => this.onGet(data)) + .catch((reason) => { + if (this.reject) { + this.reject(reason); + } + }); + + return this.promise; + } + + public close() { + this.closed = true; + + if (this.reject) { + this.reject("Stopped"); + } + + this.buffer = ""; + this.promise = null; + this.resolve = null; + this.reject = null; + } +} diff --git a/docs/robot/lekce1/example1/src/libs/robutek.ts b/docs/robot/lekce1/example1/src/libs/robutek.ts new file mode 100644 index 00000000..a6a934b1 --- /dev/null +++ b/docs/robot/lekce1/example1/src/libs/robutek.ts @@ -0,0 +1,139 @@ + +import * as adc from "adc"; +import * as gpio from "gpio"; + +import { Servo } from "./servo.js"; + +import { SmartLed, LED_WS2812 } from "smartled"; + +import * as motors from "motor" + +const leftMotorPins: motors.MotorPins = { motA: 11, motB: 12, encA: 39, encB: 40 } +const rightMotorPins: motors.MotorPins = { motA: 45, motB: 13, encA: 41, encB: 42 } + +const leftMotorLedc: motors.LedcConfig = { timer: 1, channelA: 0, channelB: 1 } +const rightMotorLedc: motors.LedcConfig = { timer: 1, channelA: 2, channelB: 3 } + +export const LeftMot = new motors.Motor({ pins: leftMotorPins, ledc: leftMotorLedc, encTicks: 400, diameter: 10 }); +export const RightMot = new motors.Motor({ pins: rightMotorPins, ledc: rightMotorLedc, encTicks: 400, diameter: 10 }); + + +export function init() { + adc.configure(LineSensors.S_1, adc.Attenuation.Db0); + adc.configure(LineSensors.S_2, adc.Attenuation.Db0); + adc.configure(LineSensors.S_3, adc.Attenuation.Db0); + adc.configure(LineSensors.S_4, adc.Attenuation.Db0); + + gpio.pinMode(LineSensors.S_PWR, gpio.PinMode.OUTPUT); + gpio.pinMode(LineSensors.S_SW, gpio.PinMode.OUTPUT); + + gpio.write(LineSensors.S_PWR, 1); +} + + + +type MoveDuration = { + distance?: number; // distance in cm + speed?: number; // speed in mm/s? +} + + +export type SensorType = 'W_FR' | 'W_FL' | 'W_BL' | 'W_BR' | 'L_FR' | 'L_FL' | 'L_BL' | 'L_BR'; +export class LineSensors { + + public static readonly S_1: number = 4; + public static readonly S_2: number = 5; + public static readonly S_3: number = 6; + public static readonly S_4: number = 7; + public static readonly S_SW: number = 8; + public static readonly S_PWR: number = 47; + + private sw: number = 0; + + + private async switch_sensors(to_value: number) { + if (to_value == this.sw) { + return; + } + this.sw = to_value; + gpio.write(LineSensors.S_SW, to_value); + await sleep(5); + } + + public async read(sensor: SensorType): Promise { + switch (sensor) { + case 'W_FR': + this.switch_sensors(0); + return adc.read(LineSensors.S_1); + + case 'W_FL': + this.switch_sensors(0); + return adc.read(LineSensors.S_2); + + case 'W_BL': + this.switch_sensors(0); + return adc.read(LineSensors.S_3); + + case 'W_BR': + this.switch_sensors(0); + return adc.read(LineSensors.S_4); + + + case 'L_FR': + this.switch_sensors(1); + return adc.read(LineSensors.S_1); + + case 'L_FL': + this.switch_sensors(1); + return adc.read(LineSensors.S_2); + + case 'L_BL': + this.switch_sensors(1); + return adc.read(LineSensors.S_3); + + case 'L_BR': + this.switch_sensors(1); + return adc.read(LineSensors.S_4); + + default: + return 0; + + } + } +} + +export class Pen { + private servo: Servo; + + public static readonly UP = 512 + 180; + public static readonly DOWN = 512 - 180; + public static readonly MIDDLE = 512; + public static readonly UNLOAD = 0; + + constructor(pin: number) { + this.servo = new Servo(pin, 1, 0); + } + + /** + * Set the pen servo position. + * @param value The position to set the servo to, from 0 to 1023. + */ + public move(value: number) { + this.servo.write(value); + } +} + +export const ledStrip = new SmartLed(48, 9, LED_WS2812); + +export class Pins { + public static readonly Status_LED: number = 46; + + public static readonly Motor1_A = 11; + public static readonly Motor1_B = 12; + public static readonly Motor2_A = 45; + public static readonly Motor2_B = 13; + public static readonly ENC1_1 = 39; + public static readonly ENC1_2 = 40; + public static readonly ENC2_1 = 41; + public static readonly ENC2_2 = 42; +} \ No newline at end of file diff --git a/docs/robot/lekce1/example1/src/libs/servo.ts b/docs/robot/lekce1/example1/src/libs/servo.ts index ba0f7a14..ac4fe3ee 100644 --- a/docs/robot/lekce1/example1/src/libs/servo.ts +++ b/docs/robot/lekce1/example1/src/libs/servo.ts @@ -1,34 +1,34 @@ -import * as ledc from "ledc"; - -/** - * A class for controlling a servo motor. - */ -export class Servo { - private channel: number; // the PWM channel to use - - /** - * Create a new servo object. - * @param pin The pin the servo is connected to. - * @param timer The timer to use for PWM. - * @param channel The channel to use for PWM. - */ - constructor(pin: number, timer: number, channel: number) { - this.channel = channel; - ledc.configureTimer(timer, 50, 12); // 50Hz is the frequency of a servo, 12 is the resolution of the timer - ledc.configureChannel(channel, pin, timer, 1023); // 1023 is the resolution of a servo - } - - /** - * Set the servo position. - * @param value The position to set the servo to, from 0-1023. - */ - write(value: number) { - // map the value from 0-1023 to 0.5-2.5ms - const ms = ((value / 1023) * 2) + 0.5; // 0.5-2.5ms is the range of a servo - - // convert to a duty cycle - const duty = (ms / 20) * 1023; // 20ms is the period of a servo - - ledc.setDuty(this.channel, duty); // set the duty cycle to the servo - } -} +import * as ledc from "ledc"; + +/** + * A class for controlling a servo motor. + */ +export class Servo { + private channel: number; // the PWM channel to use + + /** + * Create a new servo object. + * @param pin The pin the servo is connected to. + * @param timer The timer to use for PWM. + * @param channel The channel to use for PWM. + */ + constructor(pin: number, timer: number, channel: number) { + this.channel = channel; + ledc.configureTimer(timer, 50, 12); // 50Hz is the frequency of a servo, 12 is the resolution of the timer + ledc.configureChannel(channel, pin, timer, 1023); // 1023 is the resolution of a servo + } + + /** + * Set the servo position. + * @param value The position to set the servo to, from 0-1023. + */ + write(value: number) { + // map the value from 0-1023 to 0.5-2.5ms + const ms = ((value / 1023) * 2) + 0.5; // 0.5-2.5ms is the range of a servo + + // convert to a duty cycle + const duty = (ms / 20) * 1023; // 20ms is the period of a servo + + ledc.setDuty(this.channel, duty); // set the duty cycle to the servo + } +} diff --git a/docs/robot/lekce1/example1/src/libs/simplemotors.ts b/docs/robot/lekce1/example1/src/libs/simplemotors.ts new file mode 100644 index 00000000..c6f424cd --- /dev/null +++ b/docs/robot/lekce1/example1/src/libs/simplemotors.ts @@ -0,0 +1,47 @@ +import * as ledc from "ledc"; + +/** + * A class for controlling a servo motor. + */ +export class Motor { + private channel1: number; // the PWM channel to use + private channel2: number; // the PWM channel to use + + /** + * Create a new servo object. + * @param pin1 The pin the servo is connected to. + * @param pin2 The pin the servo is connected to. + * @param timer The timer to use for PWM. + * @param channel1 The channel to use for PWM. + * @param channel2 The channel to use for PWM. + */ + + constructor(pin1: number, pin2: number, timer: number, channel1: number, channel2: number) { + this.channel1 = channel1; + this.channel2 = channel2; + ledc.configureTimer(timer, 50, 12); // 50Hz is the frequency of a servo, 12 is the resolution of the timer + ledc.configureChannel(channel1, pin1, timer, 1023); // 1023 is the resolution of a servo + ledc.configureChannel(channel2, pin2, timer, 1023); // 1023 is the resolution of a servo + } + + /** + * Set the servo position. + * @param value The position to set the servo to, from -1 to 1. + */ + write(value: number) { + console.log(value) + + if (value > 0) { + ledc.setDuty(this.channel1, 1); + ledc.setDuty(this.channel2, (Math.abs(value) * 1023)); + } + else if (value < 0) { + ledc.setDuty(this.channel1, (Math.abs(value) * 1023)); + ledc.setDuty(this.channel2, 1); + } + else { + ledc.setDuty(this.channel1, 1); + ledc.setDuty(this.channel2, 1); + } + } +} diff --git a/docs/robot/lekce1/example1/tsconfig.json b/docs/robot/lekce1/example1/tsconfig.json index 98564f26..8cf82b10 100644 --- a/docs/robot/lekce1/example1/tsconfig.json +++ b/docs/robot/lekce1/example1/tsconfig.json @@ -1,11 +1,11 @@ -{ - "compilerOptions": { - "target": "es2020", - "module": "es2020", - "lib": ["es2020"], - "moduleResolution": "node", - "sourceMap": false, - "outDir": "build", - "rootDir": "src", - } -} +{ + "compilerOptions": { + "target": "es2020", + "module": "es2020", + "lib": ["es2020"], + "moduleResolution": "node", + "sourceMap": false, + "outDir": "build", + "rootDir": "src", + } +} diff --git a/docs/robutekLibrary/Robutek-library/src/index.ts b/docs/robutekLibrary/Robutek-library/src/index.ts index f66cccdf..131e6b25 100644 --- a/docs/robutekLibrary/Robutek-library/src/index.ts +++ b/docs/robutekLibrary/Robutek-library/src/index.ts @@ -1,10 +1,10 @@ +import * as colors from "./libs/colors.js" import * as Robutek from "./libs/robutek.js" -Robutek.init() -Robutek.strip.clear(); -Robutek.strip.set(0, { r: 255, g: 0, b: 0 }); -Robutek.strip.show(); +Robutek.ledStrip.set(0, colors.green); // nastaví barvu LED na desce na zelenou, LED na pásku začínají od 1 +Robutek.ledStrip.show(); // zobrazí nastavení na LED - -console.log("Robotický tábor 2024") +setInterval(() => { // pravidelně vyvolává událost + console.log("Robotický tábor 2024, zdraví Jirka Vácha!"); // vypíše text: Robotický tábor 2024, zdraví Jirka Vácha! +}, 1000); // čas opakování se udává v milisekundách (1000 ms je 1 sekunda) diff --git a/docs/robutekLibrary/Robutek-library/src/libs/robutek.ts b/docs/robutekLibrary/Robutek-library/src/libs/robutek.ts index 2b72e543..a6a934b1 100644 --- a/docs/robutekLibrary/Robutek-library/src/libs/robutek.ts +++ b/docs/robutekLibrary/Robutek-library/src/libs/robutek.ts @@ -123,7 +123,7 @@ export class Pen { } } -export const strip = new SmartLed(48, 9, LED_WS2812); +export const ledStrip = new SmartLed(48, 9, LED_WS2812); export class Pins { public static readonly Status_LED: number = 46; From a37f43742b52944fe3a3cd92bd2766d7dffdf1e8 Mon Sep 17 00:00:00 2001 From: c2coder Date: Mon, 8 Jul 2024 13:51:33 +0200 Subject: [PATCH 10/14] Update lib + fix lekce 1 --- docs/robot/lekce1/example1/src/index.ts | 13 +- docs/robot/lekce1/index.md | 14 +- .../@types/adc.d.ts | 12 +- .../@types/basicStream.d.ts | 0 .../@types/fs.d.ts | 0 .../@types/gpio.d.ts | 16 +- .../lekce2/black_project/@types/gridui.d.ts | 321 ++++++++++++++++++ .../@types/i2c.d.ts | 0 .../lekce2/black_project/@types/keyvalue.d.ts | 61 ++++ .../@types/ledc.d.ts | 0 .../@types/misc.d.ts | 0 .../lekce2/black_project/@types/motor.d.ts | 82 +++++ .../@types/path.d.ts | 0 .../@types/platform.d.ts | 0 .../black_project/@types/pulseCounter.d.ts | 64 ++++ .../@types/simpleradio.d.ts | 0 .../@types/smartled.d.ts | 0 .../@types/stdio.d.ts | 0 .../@types/timers.d.ts | 0 .../black_project/@types/timestamp.d.ts | 11 + .../lekce2/black_project/@types/wifi.d.ts | 6 + docs/robot/lekce2/black_project/src/index.ts | 10 + .../src/libs/colors.ts | 160 ++++----- .../src/libs/readline.ts | 168 ++++----- .../lekce2/black_project/src/libs/robutek.ts | 139 ++++++++ .../src/libs/servo.ts | 68 ++-- .../black_project/src/libs/simplemotors.ts | 47 +++ .../tsconfig.json | 22 +- docs/robot/lekce2/blank_project/src/index.ts | 4 - .../Robutek-library/src/libs/robutek.ts | 8 - 30 files changed, 979 insertions(+), 247 deletions(-) rename docs/robot/lekce2/{blank_project => black_project}/@types/adc.d.ts (55%) rename docs/robot/lekce2/{blank_project => black_project}/@types/basicStream.d.ts (100%) rename docs/robot/lekce2/{blank_project => black_project}/@types/fs.d.ts (100%) rename docs/robot/lekce2/{blank_project => black_project}/@types/gpio.d.ts (79%) create mode 100644 docs/robot/lekce2/black_project/@types/gridui.d.ts rename docs/robot/lekce2/{blank_project => black_project}/@types/i2c.d.ts (100%) create mode 100644 docs/robot/lekce2/black_project/@types/keyvalue.d.ts rename docs/robot/lekce2/{blank_project => black_project}/@types/ledc.d.ts (100%) rename docs/robot/lekce2/{blank_project => black_project}/@types/misc.d.ts (100%) create mode 100644 docs/robot/lekce2/black_project/@types/motor.d.ts rename docs/robot/lekce2/{blank_project => black_project}/@types/path.d.ts (100%) rename docs/robot/lekce2/{blank_project => black_project}/@types/platform.d.ts (100%) create mode 100644 docs/robot/lekce2/black_project/@types/pulseCounter.d.ts rename docs/robot/lekce2/{blank_project => black_project}/@types/simpleradio.d.ts (100%) rename docs/robot/lekce2/{blank_project => black_project}/@types/smartled.d.ts (100%) rename docs/robot/lekce2/{blank_project => black_project}/@types/stdio.d.ts (100%) rename docs/robot/lekce2/{blank_project => black_project}/@types/timers.d.ts (100%) create mode 100644 docs/robot/lekce2/black_project/@types/timestamp.d.ts create mode 100644 docs/robot/lekce2/black_project/@types/wifi.d.ts create mode 100644 docs/robot/lekce2/black_project/src/index.ts rename docs/robot/lekce2/{blank_project => black_project}/src/libs/colors.ts (94%) rename docs/robot/lekce2/{blank_project => black_project}/src/libs/readline.ts (95%) create mode 100644 docs/robot/lekce2/black_project/src/libs/robutek.ts rename docs/robot/lekce2/{blank_project => black_project}/src/libs/servo.ts (96%) create mode 100644 docs/robot/lekce2/black_project/src/libs/simplemotors.ts rename docs/robot/lekce2/{blank_project => black_project}/tsconfig.json (95%) delete mode 100644 docs/robot/lekce2/blank_project/src/index.ts diff --git a/docs/robot/lekce1/example1/src/index.ts b/docs/robot/lekce1/example1/src/index.ts index 203ed5d5..cacfc134 100644 --- a/docs/robot/lekce1/example1/src/index.ts +++ b/docs/robot/lekce1/example1/src/index.ts @@ -1,10 +1,11 @@ import * as Robutek from "./libs/robutek.js" -Robutek.init() +Robutek.init() // Zprovozní Robůtka -Robutek.ledStrip.clear(); -Robutek.ledStrip.set(0, { r: 255, g: 0, b: 0 }); -Robutek.ledStrip.show(); +Robutek.ledStrip.clear(); // Zhasne LEDku na Robůtkovi, jenom pro jistotu, kdyby už předtím svítila +Robutek.ledStrip.set(0, { r: 255, g: 0, b: 0 }); // Nastaví první LEDku na červenou barvu, LEDky začínají na indexu 0 +Robutek.ledStrip.show(); // Rozsvítí LEDku s červenou, kterou jsme si nastavili - -console.log("Robotický tábor 2024") +setInterval(() => { // pravidelně vyvolává událost + console.log("Robotický tábor 2024, zdraví Jirka Vácha!"); // vypíše text: Robotický tábor 2024, zdraví Jirka Vácha! +}, 1000); // čas opakování se udává v milisekundách (1000 ms je 1 sekunda) \ No newline at end of file diff --git a/docs/robot/lekce1/index.md b/docs/robot/lekce1/index.md index 63804b99..b7c96522 100644 --- a/docs/robot/lekce1/index.md +++ b/docs/robot/lekce1/index.md @@ -194,7 +194,7 @@ Ve zdrojovém kódu jsou komentáře (`// tohle je komentář`), které nám pop ??? note "Řešení" ```ts ... - ledStrip.set(0, colors.red); // nastaví barvu LED na ESP32 na červenou + Robutek.ledStrip.set(0, colors.red); // nastaví barvu LED na ESP32 na červenou ... ``` @@ -210,15 +210,3 @@ Ve zdrojovém kódu jsou komentáře (`// tohle je komentář`), které nám pop - `pink` - `white` - `off` - -5. Upravímes si číselné proměnné na pojmenované konstanty. - - ??? note "Pojmenované konstanty" - ```ts - ... - const LED_PIN = 48; - const LED_COUNT = 1; - - const ledStrip = new SmartLed(LED_PIN, LED_COUNT, LED_WS2812); // připojí pásek na pin 48, s 1 ledkou a typem WS2812 - ... - ``` diff --git a/docs/robot/lekce2/blank_project/@types/adc.d.ts b/docs/robot/lekce2/black_project/@types/adc.d.ts similarity index 55% rename from docs/robot/lekce2/blank_project/@types/adc.d.ts rename to docs/robot/lekce2/black_project/@types/adc.d.ts index d77e9d94..871de201 100644 --- a/docs/robot/lekce2/blank_project/@types/adc.d.ts +++ b/docs/robot/lekce2/black_project/@types/adc.d.ts @@ -1,9 +1,19 @@ declare module "adc" { + type Atten = number; + + const Attenuation: { + readonly Db0: Atten; + readonly Db2_5: Atten; + readonly Db6: Atten; + readonly Db11: Atten; + }; + + /** * Enable ADC on the given pin. * @param pin The pin to enable ADC on. */ - function configure(pin: number): void; + function configure(pin: number, attenuation?: Atten): void; /** * Read the value of the given pin. diff --git a/docs/robot/lekce2/blank_project/@types/basicStream.d.ts b/docs/robot/lekce2/black_project/@types/basicStream.d.ts similarity index 100% rename from docs/robot/lekce2/blank_project/@types/basicStream.d.ts rename to docs/robot/lekce2/black_project/@types/basicStream.d.ts diff --git a/docs/robot/lekce2/blank_project/@types/fs.d.ts b/docs/robot/lekce2/black_project/@types/fs.d.ts similarity index 100% rename from docs/robot/lekce2/blank_project/@types/fs.d.ts rename to docs/robot/lekce2/black_project/@types/fs.d.ts diff --git a/docs/robot/lekce2/blank_project/@types/gpio.d.ts b/docs/robot/lekce2/black_project/@types/gpio.d.ts similarity index 79% rename from docs/robot/lekce2/blank_project/@types/gpio.d.ts rename to docs/robot/lekce2/black_project/@types/gpio.d.ts index 9c74419e..09c167db 100644 --- a/docs/robot/lekce2/blank_project/@types/gpio.d.ts +++ b/docs/robot/lekce2/black_project/@types/gpio.d.ts @@ -1,12 +1,16 @@ declare module "gpio" { const PinMode: { - DISABLE: number, - OUTPUT: number, - INPUT: number, - INPUT_PULLUP: number, - INPUT_PULLDOWN: number, + readonly DISABLE: number, + readonly OUTPUT: number, + readonly INPUT: number, + readonly INPUT_PULLUP: number, + readonly INPUT_PULLDOWN: number, }; + interface EventInfo { + timestamp: Timestamp; + } + /** * Configure the given pin. * @param pin The pin to configure. @@ -34,7 +38,7 @@ declare module "gpio" { * @param pin The pin to handle the event for. * @param callback The callback to call when the event occurs. */ - function on(event: "rising" | "falling" | "change", pin: number, callback: () => void): void; + function on(event: "rising" | "falling" | "change", pin: number, callback: (info: EventInfo) => void): void; /** * Remove event handler for the given pin. diff --git a/docs/robot/lekce2/black_project/@types/gridui.d.ts b/docs/robot/lekce2/black_project/@types/gridui.d.ts new file mode 100644 index 00000000..8ffaba74 --- /dev/null +++ b/docs/robot/lekce2/black_project/@types/gridui.d.ts @@ -0,0 +1,321 @@ +declare module "gridui" { + namespace widget { + interface Base { + readonly uuid: number + widgetX: number + widgetY: number + widgetW: number + widgetH: number + widgetTab: number + css(key: string): string + setCss(key: string, value: string): void + } + + interface Arm extends Base { + readonly x: number + readonly y: number + } + + interface Bar extends Base { + color: string + fontSize: number + min: number + max: number + value: number + showValue: boolean + } + + interface Button extends Base { + text: string + fontSize: number + color: string + background: string + align: string + valign: string + disabled: boolean + readonly pressed: boolean + } + + interface Camera extends Base { + rotation: number + clip: boolean + } + + interface Checkbox extends Base { + fontSize: number + checked: boolean + color: string + text: string + } + + interface Circle extends Base { + color: string + fontSize: number + min: number + max: number + lineWidth: number + valueStart: number + value: number + showValue: boolean + } + + interface Input extends Base { + text: string + color: string + type: string + disabled: boolean + } + + interface Joystick extends Base { + color: string + keys: string + text: string + readonly x: number + readonly y: number + } + + interface Led extends Base { + color: string + on: boolean + } + + interface Orientation extends Base { + color: string + readonly yaw: number + readonly pitch: number + readonly roll: number + readonly joystickX: number + readonly joystickY: number + } + + interface Select extends Base { + color: string + background: string + disabled: boolean + options: string + selectedIndex: number + } + + interface Slider extends Base { + color: string + fontSize: number + min: number + max: number + value: number + precision: number + showValue: boolean + } + + interface SpinEdit extends Base { + fontSize: number + color: string + value: number + step: number + precision: number + } + + interface Switcher extends Base { + fontSize: number + color: string + value: number + min: number + max: number + } + + interface Text extends Base { + text: string + fontSize: number + color: string + background: string + align: string + valign: string + prefix: string + suffix: string + } + } + + namespace builder { + interface Base { + css(key: string, value: string): this + finish(): this + } + + interface Arm extends Base { + info(info: Record): Arm + + onGrab(callback: (arm: widget.Arm) => void): Arm + onPositionChanged(callback: (arm: widget.Arm) => void): Arm + } + + interface Bar extends Base { + color(color: string): Bar + fontSize(fontSize: number): Bar + min(min: number): Bar + max(max: number): Bar + value(value: number): Bar + showValue(showValue: boolean): Bar + } + + interface Button extends Base { + text(text: string): Button + fontSize(fontSize: number): Button + color(color: string): Button + background(background: string): Button + align(align: string): Button + valign(valign: string): Button + disabled(disabled: boolean): Button + + onPress(callback: (button: widget.Button) => void): Button + onRelease(callback: (button: widget.Button) => void): Button + } + + interface Camera extends Base { + rotation(rotation: number): Camera + clip(clip: boolean): Camera + tags(tags: any /* TODO: fix type */): Camera + } + + interface Checkbox extends Base { + fontSize(fontSize: number): Checkbox + checked(checked: boolean): Checkbox + color(color: string): Checkbox + text(text: string): Checkbox + + onChanged(callback: (checkbox: widget.Checkbox) => void): Checkbox + } + + interface Circle extends Base { + color(color: string): Circle + fontSize(fontSize: number): Circle + min(min: number): Circle + max(max: number): Circle + lineWidth(lineWidth: number): Circle + valueStart(valueStart: number): Circle + value(value: number): Circle + showValue(showValue: boolean): Circle + } + + interface Input extends Base { + text(text: string): Input + color(color: string): Input + type(type: string): Input + disabled(disabled: boolean): Input + + onChanged(callback: (input: widget.Input) => void): Input + } + + interface Joystick extends Base { + color(color: string): Joystick + keys(keys: string): Joystick + text(text: string): Joystick + + onClick(callback: (joystick: widget.Joystick) => void): Joystick + onPositionChanged(callback: (joystick: widget.Joystick) => void): Joystick + } + + interface Led extends Base { + color(color: string): Led + on(on: boolean): Led + } + + interface Orientation extends Base { + color(color: string): Orientation + + onPositionChanged(callback: (orientation: widget.Orientation) => void): Orientation + } + + interface Select extends Base { + color(color: string): Select + background(background: string): Select + disabled(disabled: boolean): Select + options(options: string): Select + selectedIndex(selectedIndex: number): Select + + onChanged(callback: (select: widget.Select) => void): Select + } + + interface Slider extends Base { + color(color: string): Slider + fontSize(fontSize: number): Slider + min(min: number): Slider + max(max: number): Slider + value(value: number): Slider + precision(precision: number): Slider + showValue(showValue: boolean): Slider + + onChanged(callback: (slider: widget.Slider) => void): Slider + } + + interface SpinEdit extends Base { + fontSize(fontSize: number): SpinEdit + color(color: string): SpinEdit + value(value: number): SpinEdit + step(step: number): SpinEdit + precision(precision: number): SpinEdit + + onChanged(callback: (spinEdit: widget.SpinEdit) => void): SpinEdit + } + + interface Switcher extends Base { + fontSize(fontSize: number): Switcher + color(color: string): Switcher + value(value: number): Switcher + min(min: number): Switcher + max(max: number): Switcher + + onChanged(callback: (switcher: widget.Switcher) => void): Switcher + } + + interface Text extends Base { + text(text: string): Text + fontSize(fontSize: number): Text + color(color: string): Text + background(background: string): Text + align(align: string): Text + valign(valign: string): Text + prefix(prefix: string): Text + suffix(suffix: string): Text + } + + interface Widget extends Base {} + } + + class Builder { + arm(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Arm + bar(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Bar + button(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Button + camera(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Camera + checkbox(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Checkbox + circle(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Circle + input(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Input + joystick(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Joystick + led(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Led + orientation(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Orientation + select(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Select + slider(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Slider + spinEdit(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.SpinEdit + switcher(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Switcher + text(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Text + widget(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Widget + } + + /** + * Initialize GridUI. + * @param ownerName name of the owner, must match the name entered in RBController app. + * @param deviceName name of this device, visible in the RBController app. + * @param builderCallback callback, which receives the builder instance that can be used to create widgets. + */ + function begin(ownerName: string, deviceName: string, builderCallback: (builder: Builder) => void): void + + /** + * Stop GridUI. + */ + function end(): void + + /** + * Returns included GridUI version as number, to be compared with hex representation of the version. + * + * For example, for version 5.1.0, do: `gridui.version() >= 0x050100` + */ + function version(): number +} diff --git a/docs/robot/lekce2/blank_project/@types/i2c.d.ts b/docs/robot/lekce2/black_project/@types/i2c.d.ts similarity index 100% rename from docs/robot/lekce2/blank_project/@types/i2c.d.ts rename to docs/robot/lekce2/black_project/@types/i2c.d.ts diff --git a/docs/robot/lekce2/black_project/@types/keyvalue.d.ts b/docs/robot/lekce2/black_project/@types/keyvalue.d.ts new file mode 100644 index 00000000..761b09fb --- /dev/null +++ b/docs/robot/lekce2/black_project/@types/keyvalue.d.ts @@ -0,0 +1,61 @@ +declare module "keyvalue" { + class KeyValueNamespace { + /** + * Get saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present + */ + get(key: string): string | number | null; + + /** + * Get string saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present or if not string + */ + getString(key: string): string | null; + + /** + * Get number saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present or if not number + */ + getNumber(key: string): number | null; + + /** + * Set value in KeyValue namespace, overwriting any previous value. + * Call commit() after setting everything, otherwise changes will be lost! + * @param key key, max 15 characters. + * @param value value + */ + set(key: string, value: string | number): void; + + /** + * Erase value from KeyValue namespace. + * Call commit() after setting everything, otherwise changes will be lost! + * @param key key, max 15 characters. + */ + erase(key: string): void; + + /** + * Check existance of a key. + * @param key key, max 15 characters. + * @returns true if exists, false otherwise + */ + exists(key: string): boolean; + + /** + * Save modifications to persistent storage. + */ + commit(): void; + } + + /** + * Open a KeyValue namespace for use. KeyValue is a persitent storage for small + * pieces of data that saves values across restarts. + * + * The namespaces are isolated from each other. + * + * @param namespace Namespace name, max 15 characters. + */ + function open(namespace: string): KeyValueNamespace; +} diff --git a/docs/robot/lekce2/blank_project/@types/ledc.d.ts b/docs/robot/lekce2/black_project/@types/ledc.d.ts similarity index 100% rename from docs/robot/lekce2/blank_project/@types/ledc.d.ts rename to docs/robot/lekce2/black_project/@types/ledc.d.ts diff --git a/docs/robot/lekce2/blank_project/@types/misc.d.ts b/docs/robot/lekce2/black_project/@types/misc.d.ts similarity index 100% rename from docs/robot/lekce2/blank_project/@types/misc.d.ts rename to docs/robot/lekce2/black_project/@types/misc.d.ts diff --git a/docs/robot/lekce2/black_project/@types/motor.d.ts b/docs/robot/lekce2/black_project/@types/motor.d.ts new file mode 100644 index 00000000..2ec31392 --- /dev/null +++ b/docs/robot/lekce2/black_project/@types/motor.d.ts @@ -0,0 +1,82 @@ +declare module "motor" { + + type MoveDuration = { + distance?: number; + time?: number; + } + + type MotorPins = { + motA: number; + motB: number; + encA: number; + encB: number; + }; + + type LedcConfig = { + timer: number; + channelA: number; + channelB: number; + }; + + class Motor { + /** + * Construc a new Motor instance + * @param options Motor configuration + * @note Units used in the diameter parameter determines the units used in the other methods + */ + constructor(options: { pins: MotorPins, ledc: LedcConfig, encTicks: number, diameter: number }); + + /** + * Set the speed of the motor + * @param speed Speed in units per second + * @note The units are the same as used in the diameter parameter + */ + setSpeed(speed: number): void; + // setRamp(ramp: number): void; + + /** + * Move the motor + * @param duration Duration of the movement + * @note The units are the same as used in the diameter parameter + * @note If the duration is not provided, the motor will move indefinitely + */ + move(duration?: MoveDuration): Promise; + + /** + * Stop the motor + * @param brake If true, the motor will brake, otherwise it will coast to a stop + */ + stop(brake?: boolean): Promise; + + /** + * Get the position of the motor + * @note The units are the same as used in the diameter parameter + * @returns The position of the motor + */ + getPosition(): number; + + /** + * Close the motor + */ + close(): void; + } + + /* class Wheels { + constructor(options: { + left: MotorPins, + right: MotorPins, + encTicks: number, + diameter: number, + spacing: number + }); + setSpeed(speed: number): void; + // setRamp(ramp: number): void; + + move(curve: number, duration?: MoveDuration): Promise; + // rotate(angle: number): Promise; + stop(brake?: boolean): Promise; + getPosition(): { left: number, right: number }; + + close(): void; + } */ +} diff --git a/docs/robot/lekce2/blank_project/@types/path.d.ts b/docs/robot/lekce2/black_project/@types/path.d.ts similarity index 100% rename from docs/robot/lekce2/blank_project/@types/path.d.ts rename to docs/robot/lekce2/black_project/@types/path.d.ts diff --git a/docs/robot/lekce2/blank_project/@types/platform.d.ts b/docs/robot/lekce2/black_project/@types/platform.d.ts similarity index 100% rename from docs/robot/lekce2/blank_project/@types/platform.d.ts rename to docs/robot/lekce2/black_project/@types/platform.d.ts diff --git a/docs/robot/lekce2/black_project/@types/pulseCounter.d.ts b/docs/robot/lekce2/black_project/@types/pulseCounter.d.ts new file mode 100644 index 00000000..76ad8edd --- /dev/null +++ b/docs/robot/lekce2/black_project/@types/pulseCounter.d.ts @@ -0,0 +1,64 @@ +declare module "pulseCounter" { + type LevelAction = number; + type EdgeAction = number; + + const LevelAction: { + Keep: LevelAction; + Inverse: LevelAction; + Hold: LevelAction; + }; + + const EdgeAction: { + Hold: EdgeAction; + Increase: EdgeAction; + Decrease: EdgeAction; + }; + + type LevelMode = { + low: LevelAction, + high: LevelAction, + } + + type EdgeMode = { + pos: EdgeAction, + neg: EdgeAction, + } + + class PulseCounter { + /** + * Create a new pulse counter instance. + * @param options The options for the pulse counter. + */ + constructor(options: { + pinLevel: number, + pinEdge: number, + levelMode: LevelMode, + edgeMode: EdgeMode, + }) + + /** + * Read the current value of the pulse counter. + */ + read(): number; + + /** + * Reset the pulse counter to 0. + */ + clear(): void; + + /** + * Start the pulse counter. + */ + start(): void; + + /** + * Stop the pulse counter. + */ + stop(): void; + + /** + * Close the pulse counter. + */ + close(): void; + } +} diff --git a/docs/robot/lekce2/blank_project/@types/simpleradio.d.ts b/docs/robot/lekce2/black_project/@types/simpleradio.d.ts similarity index 100% rename from docs/robot/lekce2/blank_project/@types/simpleradio.d.ts rename to docs/robot/lekce2/black_project/@types/simpleradio.d.ts diff --git a/docs/robot/lekce2/blank_project/@types/smartled.d.ts b/docs/robot/lekce2/black_project/@types/smartled.d.ts similarity index 100% rename from docs/robot/lekce2/blank_project/@types/smartled.d.ts rename to docs/robot/lekce2/black_project/@types/smartled.d.ts diff --git a/docs/robot/lekce2/blank_project/@types/stdio.d.ts b/docs/robot/lekce2/black_project/@types/stdio.d.ts similarity index 100% rename from docs/robot/lekce2/blank_project/@types/stdio.d.ts rename to docs/robot/lekce2/black_project/@types/stdio.d.ts diff --git a/docs/robot/lekce2/blank_project/@types/timers.d.ts b/docs/robot/lekce2/black_project/@types/timers.d.ts similarity index 100% rename from docs/robot/lekce2/blank_project/@types/timers.d.ts rename to docs/robot/lekce2/black_project/@types/timers.d.ts diff --git a/docs/robot/lekce2/black_project/@types/timestamp.d.ts b/docs/robot/lekce2/black_project/@types/timestamp.d.ts new file mode 100644 index 00000000..bd9ab6c8 --- /dev/null +++ b/docs/robot/lekce2/black_project/@types/timestamp.d.ts @@ -0,0 +1,11 @@ +declare interface Timestamp { + /** + * Convert the timestamp to milliseconds. + */ + millis(): number; + + /** + * Get the lower 10^3 part of the timestamp in microseconds. + */ + micros(): number; +} diff --git a/docs/robot/lekce2/black_project/@types/wifi.d.ts b/docs/robot/lekce2/black_project/@types/wifi.d.ts new file mode 100644 index 00000000..7e532194 --- /dev/null +++ b/docs/robot/lekce2/black_project/@types/wifi.d.ts @@ -0,0 +1,6 @@ +declare module "wifi" { + /** + * Return current IPv4 of the device, or null if WiFi is disabled or not connected. + */ + function currentIp(): string | null; +} diff --git a/docs/robot/lekce2/black_project/src/index.ts b/docs/robot/lekce2/black_project/src/index.ts new file mode 100644 index 00000000..131e6b25 --- /dev/null +++ b/docs/robot/lekce2/black_project/src/index.ts @@ -0,0 +1,10 @@ +import * as colors from "./libs/colors.js" +import * as Robutek from "./libs/robutek.js" + + +Robutek.ledStrip.set(0, colors.green); // nastaví barvu LED na desce na zelenou, LED na pásku začínají od 1 +Robutek.ledStrip.show(); // zobrazí nastavení na LED + +setInterval(() => { // pravidelně vyvolává událost + console.log("Robotický tábor 2024, zdraví Jirka Vácha!"); // vypíše text: Robotický tábor 2024, zdraví Jirka Vácha! +}, 1000); // čas opakování se udává v milisekundách (1000 ms je 1 sekunda) diff --git a/docs/robot/lekce2/blank_project/src/libs/colors.ts b/docs/robot/lekce2/black_project/src/libs/colors.ts similarity index 94% rename from docs/robot/lekce2/blank_project/src/libs/colors.ts rename to docs/robot/lekce2/black_project/src/libs/colors.ts index 950adfbe..de76a121 100644 --- a/docs/robot/lekce2/blank_project/src/libs/colors.ts +++ b/docs/robot/lekce2/black_project/src/libs/colors.ts @@ -1,81 +1,81 @@ -/** - * Barva je jednoduchá trojice červené, zelené, a modré složky - * - R: červená (rozsah 0-255) - * - G: zelená (rozsah 0-255) - * - B: modrá (rozsah 0-255) - */ -interface Rgb { - r: number; - g: number; - b: number; -} - - -/** - * Alternativní způsob, jak vyjádřit barvu, je HSL: - * - Hue: odstín (rozsah 0-360) - * - Saturation: sytost barev (rozsah 0-1) - * - Lightness: světlost (rozsah 0-1) - */ -interface Hsl { - h: number; - s: number; - l: number; -} - -/** - * Mezi jednotlivými reprezentacemi lze převádět - * @param hsl {Number} Hue (0-360), Saturation (0-1), Lightness (0-1) - * @returns {Rgb} Red (0-255), Green (0-255), Blue (0-255) - */ -export function hsl_to_rbg( hsl: Hsl ) : Rgb { - const chroma = ( 1 - Math.abs( 2 * hsl.l - 1 ) * hsl.s ); - const hue = hsl.h / 60; - const x = chroma * ( 1 - Math.abs( ( hue % 2 ) - 1 ) ); - - let color : Rgb = { r: 0, g: 0, b: 0 }; - if( hue > 0 && hue < 1 ){ - color = { r: chroma, g: x, b: 0 }; - } else if( hue >= 1 && hue < 2 ){ - color = { r: x, g: chroma, b: 0 }; - } else if( hue >= 2 && hue < 3 ){ - color = { r: 0, g: chroma, b: x }; - } else if( hue >= 3 && hue < 4 ){ - color = { r: 0, g: x, b: chroma }; - } else if( hue >= 4 && hue < 5 ){ - color = { r: x, g: 0, b: chroma }; - } else { - color = { r: chroma, g: 0, b: x }; - } - const correction = hsl.l - chroma / 2; - color.r = ( color.r + correction ) * 255; - color.g = ( color.g + correction ) * 255; - color.b = ( color.b + correction ) * 255; - - return color; -} - -/** - * Funkce rainbow zafixuje sytost a světlost, a prochází barvami - * @param hue (0-360) - * @param brightness (0-100) - 50 je defaultní hodnota - * @returns {Rgb} - */ -export function rainbow( hue: number, brightness: number = 20) : Rgb { - hue = Math.min( hue, 360 ); // Zajistíme, že zadaná hodnota není mimo rozsah - // fix range to 0-100 - let brightness_mapped = Math.min(Math.max(brightness, 0), 100); - return hsl_to_rbg( { h: hue, s: 1, l: brightness_mapped / 100 } ); -} - -/* Základní barvy pro LED pásky*/ -export const red = rainbow( 0 ); -export const orange = rainbow( 27 ); -export const yellow = rainbow( 54 ); -export const green = rainbow( 110 ); -export const light_blue = rainbow( 177 ); -export const blue = rainbow( 240 ); -export const purple = rainbow( 285 ); -export const pink = rainbow( 323 ); -export const white : Rgb = { r: 100, g: 100, b: 100 }; +/** + * Barva je jednoduchá trojice červené, zelené, a modré složky + * - R: červená (rozsah 0-255) + * - G: zelená (rozsah 0-255) + * - B: modrá (rozsah 0-255) + */ +interface Rgb { + r: number; + g: number; + b: number; +} + + +/** + * Alternativní způsob, jak vyjádřit barvu, je HSL: + * - Hue: odstín (rozsah 0-360) + * - Saturation: sytost barev (rozsah 0-1) + * - Lightness: světlost (rozsah 0-1) + */ +interface Hsl { + h: number; + s: number; + l: number; +} + +/** + * Mezi jednotlivými reprezentacemi lze převádět + * @param hsl {Number} Hue (0-360), Saturation (0-1), Lightness (0-1) + * @returns {Rgb} Red (0-255), Green (0-255), Blue (0-255) + */ +export function hsl_to_rbg( hsl: Hsl ) : Rgb { + const chroma = ( 1 - Math.abs( 2 * hsl.l - 1 ) * hsl.s ); + const hue = hsl.h / 60; + const x = chroma * ( 1 - Math.abs( ( hue % 2 ) - 1 ) ); + + let color : Rgb = { r: 0, g: 0, b: 0 }; + if( hue > 0 && hue < 1 ){ + color = { r: chroma, g: x, b: 0 }; + } else if( hue >= 1 && hue < 2 ){ + color = { r: x, g: chroma, b: 0 }; + } else if( hue >= 2 && hue < 3 ){ + color = { r: 0, g: chroma, b: x }; + } else if( hue >= 3 && hue < 4 ){ + color = { r: 0, g: x, b: chroma }; + } else if( hue >= 4 && hue < 5 ){ + color = { r: x, g: 0, b: chroma }; + } else { + color = { r: chroma, g: 0, b: x }; + } + const correction = hsl.l - chroma / 2; + color.r = ( color.r + correction ) * 255; + color.g = ( color.g + correction ) * 255; + color.b = ( color.b + correction ) * 255; + + return color; +} + +/** + * Funkce rainbow zafixuje sytost a světlost, a prochází barvami + * @param hue (0-360) + * @param brightness (0-100) - 50 je defaultní hodnota + * @returns {Rgb} + */ +export function rainbow( hue: number, brightness: number = 50) : Rgb { + hue = Math.min( hue, 360 ); // Zajistíme, že zadaná hodnota není mimo rozsah + // fix range to 0-100 + let brightness_mapped = Math.min(Math.max(brightness, 0), 100); + return hsl_to_rbg( { h: hue, s: 1, l: brightness_mapped / 100 } ); +} + +/* Základní barvy pro LED pásky*/ +export const red = rainbow( 0 ); +export const orange = rainbow( 27 ); +export const yellow = rainbow( 54 ); +export const green = rainbow( 110 ); +export const light_blue = rainbow( 177 ); +export const blue = rainbow( 240 ); +export const purple = rainbow( 285 ); +export const pink = rainbow( 323 ); +export const white : Rgb = { r: 100, g: 100, b: 100 }; export const off : Rgb = { r: 0, g: 0, b: 0 }; \ No newline at end of file diff --git a/docs/robot/lekce2/blank_project/src/libs/readline.ts b/docs/robot/lekce2/black_project/src/libs/readline.ts similarity index 95% rename from docs/robot/lekce2/blank_project/src/libs/readline.ts rename to docs/robot/lekce2/black_project/src/libs/readline.ts index 0c2bf751..4372efd2 100644 --- a/docs/robot/lekce2/blank_project/src/libs/readline.ts +++ b/docs/robot/lekce2/black_project/src/libs/readline.ts @@ -1,84 +1,84 @@ -import { stdout, stdin } from "stdio"; - -/** - * A class for reading standard input line by line. - */ - -export class readline { - private buffer: string = ""; - private promise: Promise | null = null; - private resolve: ((value: string) => void) | null = null; - private reject: ((reason: any) => void) | null = null; - private closed: boolean = false; - private echo: boolean; - - private onGet(str: string) { - if (this.echo) { - stdout.write(str); - } - - if (str == "\n") { - if (this.resolve) { - this.resolve(this.buffer); - } - - this.buffer = ""; - this.promise = null; - this.resolve = null; - this.reject = null; - - return; - } - - this.buffer += str; - - if (!this.closed) { - stdin.get() - .then((data) => this.onGet(data)) - .catch((reason) => { - if (this.reject) { - this.reject(reason); - } - }); - } - - } - - constructor(echo: boolean = false) { - this.echo = echo; - } - - public read(): Promise { - if (this.promise != null) { - return Promise.reject("Already reading"); - } - - this.promise = new Promise((resolve, reject) => { - this.resolve = resolve; - this.reject = reject; - }); - - stdin.get() - .then((data) => this.onGet(data)) - .catch((reason) => { - if (this.reject) { - this.reject(reason); - } - }); - - return this.promise; - } - - public close() { - this.closed = true; - - if (this.reject) { - this.reject("Stopped"); - } - - this.buffer = ""; - this.promise = null; - this.resolve = null; - this.reject = null; - } -} +import { stdout, stdin } from "stdio"; + +/** + * A class for reading standard input line by line. + */ + +export class readline { + private buffer: string = ""; + private promise: Promise | null = null; + private resolve: ((value: string) => void) | null = null; + private reject: ((reason: any) => void) | null = null; + private closed: boolean = false; + private echo: boolean; + + private onGet(str: string) { + if (this.echo) { + stdout.write(str); + } + + if (str == "\n") { + if (this.resolve) { + this.resolve(this.buffer); + } + + this.buffer = ""; + this.promise = null; + this.resolve = null; + this.reject = null; + + return; + } + + this.buffer += str; + + if (!this.closed) { + stdin.get() + .then((data) => this.onGet(data)) + .catch((reason) => { + if (this.reject) { + this.reject(reason); + } + }); + } + + } + + constructor(echo: boolean = false) { + this.echo = echo; + } + + public read(): Promise { + if (this.promise != null) { + return Promise.reject("Already reading"); + } + + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + + stdin.get() + .then((data) => this.onGet(data)) + .catch((reason) => { + if (this.reject) { + this.reject(reason); + } + }); + + return this.promise; + } + + public close() { + this.closed = true; + + if (this.reject) { + this.reject("Stopped"); + } + + this.buffer = ""; + this.promise = null; + this.resolve = null; + this.reject = null; + } +} diff --git a/docs/robot/lekce2/black_project/src/libs/robutek.ts b/docs/robot/lekce2/black_project/src/libs/robutek.ts new file mode 100644 index 00000000..a6a934b1 --- /dev/null +++ b/docs/robot/lekce2/black_project/src/libs/robutek.ts @@ -0,0 +1,139 @@ + +import * as adc from "adc"; +import * as gpio from "gpio"; + +import { Servo } from "./servo.js"; + +import { SmartLed, LED_WS2812 } from "smartled"; + +import * as motors from "motor" + +const leftMotorPins: motors.MotorPins = { motA: 11, motB: 12, encA: 39, encB: 40 } +const rightMotorPins: motors.MotorPins = { motA: 45, motB: 13, encA: 41, encB: 42 } + +const leftMotorLedc: motors.LedcConfig = { timer: 1, channelA: 0, channelB: 1 } +const rightMotorLedc: motors.LedcConfig = { timer: 1, channelA: 2, channelB: 3 } + +export const LeftMot = new motors.Motor({ pins: leftMotorPins, ledc: leftMotorLedc, encTicks: 400, diameter: 10 }); +export const RightMot = new motors.Motor({ pins: rightMotorPins, ledc: rightMotorLedc, encTicks: 400, diameter: 10 }); + + +export function init() { + adc.configure(LineSensors.S_1, adc.Attenuation.Db0); + adc.configure(LineSensors.S_2, adc.Attenuation.Db0); + adc.configure(LineSensors.S_3, adc.Attenuation.Db0); + adc.configure(LineSensors.S_4, adc.Attenuation.Db0); + + gpio.pinMode(LineSensors.S_PWR, gpio.PinMode.OUTPUT); + gpio.pinMode(LineSensors.S_SW, gpio.PinMode.OUTPUT); + + gpio.write(LineSensors.S_PWR, 1); +} + + + +type MoveDuration = { + distance?: number; // distance in cm + speed?: number; // speed in mm/s? +} + + +export type SensorType = 'W_FR' | 'W_FL' | 'W_BL' | 'W_BR' | 'L_FR' | 'L_FL' | 'L_BL' | 'L_BR'; +export class LineSensors { + + public static readonly S_1: number = 4; + public static readonly S_2: number = 5; + public static readonly S_3: number = 6; + public static readonly S_4: number = 7; + public static readonly S_SW: number = 8; + public static readonly S_PWR: number = 47; + + private sw: number = 0; + + + private async switch_sensors(to_value: number) { + if (to_value == this.sw) { + return; + } + this.sw = to_value; + gpio.write(LineSensors.S_SW, to_value); + await sleep(5); + } + + public async read(sensor: SensorType): Promise { + switch (sensor) { + case 'W_FR': + this.switch_sensors(0); + return adc.read(LineSensors.S_1); + + case 'W_FL': + this.switch_sensors(0); + return adc.read(LineSensors.S_2); + + case 'W_BL': + this.switch_sensors(0); + return adc.read(LineSensors.S_3); + + case 'W_BR': + this.switch_sensors(0); + return adc.read(LineSensors.S_4); + + + case 'L_FR': + this.switch_sensors(1); + return adc.read(LineSensors.S_1); + + case 'L_FL': + this.switch_sensors(1); + return adc.read(LineSensors.S_2); + + case 'L_BL': + this.switch_sensors(1); + return adc.read(LineSensors.S_3); + + case 'L_BR': + this.switch_sensors(1); + return adc.read(LineSensors.S_4); + + default: + return 0; + + } + } +} + +export class Pen { + private servo: Servo; + + public static readonly UP = 512 + 180; + public static readonly DOWN = 512 - 180; + public static readonly MIDDLE = 512; + public static readonly UNLOAD = 0; + + constructor(pin: number) { + this.servo = new Servo(pin, 1, 0); + } + + /** + * Set the pen servo position. + * @param value The position to set the servo to, from 0 to 1023. + */ + public move(value: number) { + this.servo.write(value); + } +} + +export const ledStrip = new SmartLed(48, 9, LED_WS2812); + +export class Pins { + public static readonly Status_LED: number = 46; + + public static readonly Motor1_A = 11; + public static readonly Motor1_B = 12; + public static readonly Motor2_A = 45; + public static readonly Motor2_B = 13; + public static readonly ENC1_1 = 39; + public static readonly ENC1_2 = 40; + public static readonly ENC2_1 = 41; + public static readonly ENC2_2 = 42; +} \ No newline at end of file diff --git a/docs/robot/lekce2/blank_project/src/libs/servo.ts b/docs/robot/lekce2/black_project/src/libs/servo.ts similarity index 96% rename from docs/robot/lekce2/blank_project/src/libs/servo.ts rename to docs/robot/lekce2/black_project/src/libs/servo.ts index ba0f7a14..ac4fe3ee 100644 --- a/docs/robot/lekce2/blank_project/src/libs/servo.ts +++ b/docs/robot/lekce2/black_project/src/libs/servo.ts @@ -1,34 +1,34 @@ -import * as ledc from "ledc"; - -/** - * A class for controlling a servo motor. - */ -export class Servo { - private channel: number; // the PWM channel to use - - /** - * Create a new servo object. - * @param pin The pin the servo is connected to. - * @param timer The timer to use for PWM. - * @param channel The channel to use for PWM. - */ - constructor(pin: number, timer: number, channel: number) { - this.channel = channel; - ledc.configureTimer(timer, 50, 12); // 50Hz is the frequency of a servo, 12 is the resolution of the timer - ledc.configureChannel(channel, pin, timer, 1023); // 1023 is the resolution of a servo - } - - /** - * Set the servo position. - * @param value The position to set the servo to, from 0-1023. - */ - write(value: number) { - // map the value from 0-1023 to 0.5-2.5ms - const ms = ((value / 1023) * 2) + 0.5; // 0.5-2.5ms is the range of a servo - - // convert to a duty cycle - const duty = (ms / 20) * 1023; // 20ms is the period of a servo - - ledc.setDuty(this.channel, duty); // set the duty cycle to the servo - } -} +import * as ledc from "ledc"; + +/** + * A class for controlling a servo motor. + */ +export class Servo { + private channel: number; // the PWM channel to use + + /** + * Create a new servo object. + * @param pin The pin the servo is connected to. + * @param timer The timer to use for PWM. + * @param channel The channel to use for PWM. + */ + constructor(pin: number, timer: number, channel: number) { + this.channel = channel; + ledc.configureTimer(timer, 50, 12); // 50Hz is the frequency of a servo, 12 is the resolution of the timer + ledc.configureChannel(channel, pin, timer, 1023); // 1023 is the resolution of a servo + } + + /** + * Set the servo position. + * @param value The position to set the servo to, from 0-1023. + */ + write(value: number) { + // map the value from 0-1023 to 0.5-2.5ms + const ms = ((value / 1023) * 2) + 0.5; // 0.5-2.5ms is the range of a servo + + // convert to a duty cycle + const duty = (ms / 20) * 1023; // 20ms is the period of a servo + + ledc.setDuty(this.channel, duty); // set the duty cycle to the servo + } +} diff --git a/docs/robot/lekce2/black_project/src/libs/simplemotors.ts b/docs/robot/lekce2/black_project/src/libs/simplemotors.ts new file mode 100644 index 00000000..c6f424cd --- /dev/null +++ b/docs/robot/lekce2/black_project/src/libs/simplemotors.ts @@ -0,0 +1,47 @@ +import * as ledc from "ledc"; + +/** + * A class for controlling a servo motor. + */ +export class Motor { + private channel1: number; // the PWM channel to use + private channel2: number; // the PWM channel to use + + /** + * Create a new servo object. + * @param pin1 The pin the servo is connected to. + * @param pin2 The pin the servo is connected to. + * @param timer The timer to use for PWM. + * @param channel1 The channel to use for PWM. + * @param channel2 The channel to use for PWM. + */ + + constructor(pin1: number, pin2: number, timer: number, channel1: number, channel2: number) { + this.channel1 = channel1; + this.channel2 = channel2; + ledc.configureTimer(timer, 50, 12); // 50Hz is the frequency of a servo, 12 is the resolution of the timer + ledc.configureChannel(channel1, pin1, timer, 1023); // 1023 is the resolution of a servo + ledc.configureChannel(channel2, pin2, timer, 1023); // 1023 is the resolution of a servo + } + + /** + * Set the servo position. + * @param value The position to set the servo to, from -1 to 1. + */ + write(value: number) { + console.log(value) + + if (value > 0) { + ledc.setDuty(this.channel1, 1); + ledc.setDuty(this.channel2, (Math.abs(value) * 1023)); + } + else if (value < 0) { + ledc.setDuty(this.channel1, (Math.abs(value) * 1023)); + ledc.setDuty(this.channel2, 1); + } + else { + ledc.setDuty(this.channel1, 1); + ledc.setDuty(this.channel2, 1); + } + } +} diff --git a/docs/robot/lekce2/blank_project/tsconfig.json b/docs/robot/lekce2/black_project/tsconfig.json similarity index 95% rename from docs/robot/lekce2/blank_project/tsconfig.json rename to docs/robot/lekce2/black_project/tsconfig.json index 98564f26..8cf82b10 100644 --- a/docs/robot/lekce2/blank_project/tsconfig.json +++ b/docs/robot/lekce2/black_project/tsconfig.json @@ -1,11 +1,11 @@ -{ - "compilerOptions": { - "target": "es2020", - "module": "es2020", - "lib": ["es2020"], - "moduleResolution": "node", - "sourceMap": false, - "outDir": "build", - "rootDir": "src", - } -} +{ + "compilerOptions": { + "target": "es2020", + "module": "es2020", + "lib": ["es2020"], + "moduleResolution": "node", + "sourceMap": false, + "outDir": "build", + "rootDir": "src", + } +} diff --git a/docs/robot/lekce2/blank_project/src/index.ts b/docs/robot/lekce2/blank_project/src/index.ts deleted file mode 100644 index b4175ce0..00000000 --- a/docs/robot/lekce2/blank_project/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { SmartLed, LED_WS2812 } from "smartled"; -import * as colors from "./libs/colors.js" -import * as gpio from "gpio"; - diff --git a/docs/robutekLibrary/Robutek-library/src/libs/robutek.ts b/docs/robutekLibrary/Robutek-library/src/libs/robutek.ts index a6a934b1..8e05f2bb 100644 --- a/docs/robutekLibrary/Robutek-library/src/libs/robutek.ts +++ b/docs/robutekLibrary/Robutek-library/src/libs/robutek.ts @@ -30,14 +30,6 @@ export function init() { gpio.write(LineSensors.S_PWR, 1); } - - -type MoveDuration = { - distance?: number; // distance in cm - speed?: number; // speed in mm/s? -} - - export type SensorType = 'W_FR' | 'W_FL' | 'W_BL' | 'W_BR' | 'L_FR' | 'L_FL' | 'L_BL' | 'L_BR'; export class LineSensors { From e10b66dc13b106b136196c13a09b10c3655c2710 Mon Sep 17 00:00:00 2001 From: c2coder Date: Mon, 8 Jul 2024 14:29:46 +0200 Subject: [PATCH 11/14] Fix lekce 1-4 --- .../robot/lekce1/example1/src/libs/robutek.ts | 21 +- docs/robot/lekce2/black_project/src/index.ts | 10 - .../@types/adc.d.ts | 0 .../@types/basicStream.d.ts | 0 .../@types/fs.d.ts | 0 .../@types/gpio.d.ts | 0 .../@types/gridui.d.ts | 0 .../@types/i2c.d.ts | 0 .../@types/keyvalue.d.ts | 0 .../@types/ledc.d.ts | 0 .../@types/misc.d.ts | 0 .../@types/motor.d.ts | 0 .../@types/path.d.ts | 0 .../@types/platform.d.ts | 0 .../@types/pulseCounter.d.ts | 0 .../@types/simpleradio.d.ts | 0 .../@types/smartled.d.ts | 0 .../@types/stdio.d.ts | 0 .../@types/timers.d.ts | 0 .../@types/timestamp.d.ts | 0 .../@types/wifi.d.ts | 0 docs/robot/lekce2/blank_project/src/index.ts | 3 + .../src/libs/colors.ts | 0 .../src/libs/readline.ts | 0 .../src/libs/robutek.ts | 21 +- .../src/libs/servo.ts | 0 .../src/libs/simplemotors.ts | 0 .../tsconfig.json | 0 docs/robot/lekce2/index.md | 33 +- docs/robot/lekce3/index.md | 60 ++-- docs/robot/lekce3/project3/@types/adc.d.ts | 12 +- docs/robot/lekce3/project3/@types/gpio.d.ts | 16 +- docs/robot/lekce3/project3/@types/gridui.d.ts | 321 ++++++++++++++++++ .../lekce3/project3/@types/keyvalue.d.ts | 61 ++++ docs/robot/lekce3/project3/@types/motor.d.ts | 82 +++++ .../lekce3/project3/@types/pulseCounter.d.ts | 64 ++++ .../lekce3/project3/@types/timestamp.d.ts | 11 + docs/robot/lekce3/project3/@types/wifi.d.ts | 6 + docs/robot/lekce3/project3/src/index.ts | 6 +- docs/robot/lekce3/project3/src/libs/colors.ts | 160 ++++----- .../lekce3/project3/src/libs/readline.ts | 84 +++++ .../robot/lekce3/project3/src/libs/robutek.ts | 134 ++++++++ docs/robot/lekce3/project3/src/libs/servo.ts | 34 ++ .../lekce3/project3/src/libs/simplemotors.ts | 47 +++ docs/robot/lekce3/project3/tsconfig.json | 22 +- docs/robot/lekce4/project4/@types/adc.d.ts | 12 +- docs/robot/lekce4/project4/@types/gpio.d.ts | 16 +- docs/robot/lekce4/project4/@types/gridui.d.ts | 321 ++++++++++++++++++ .../lekce4/project4/@types/keyvalue.d.ts | 61 ++++ docs/robot/lekce4/project4/@types/motor.d.ts | 82 +++++ .../lekce4/project4/@types/pulseCounter.d.ts | 64 ++++ .../lekce4/project4/@types/timestamp.d.ts | 11 + docs/robot/lekce4/project4/@types/wifi.d.ts | 6 + docs/robot/lekce4/project4/src/index.ts | 9 +- docs/robot/lekce4/project4/src/libs/colors.ts | 160 ++++----- .../lekce4/project4/src/libs/readline.ts | 84 +++++ .../robot/lekce4/project4/src/libs/robutek.ts | 134 ++++++++ docs/robot/lekce4/project4/src/libs/servo.ts | 34 ++ .../lekce4/project4/src/libs/simplemotors.ts | 47 +++ docs/robot/lekce4/project4/tsconfig.json | 22 +- docs/robot/lekce5/turtle.md | 20 +- .../Robutek-library/src/index.ts | 4 + .../Robutek-library/src/libs/robutek.ts | 13 +- mkdocs.yml | 1 + 64 files changed, 1997 insertions(+), 312 deletions(-) delete mode 100644 docs/robot/lekce2/black_project/src/index.ts rename docs/robot/lekce2/{black_project => blank_project}/@types/adc.d.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/@types/basicStream.d.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/@types/fs.d.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/@types/gpio.d.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/@types/gridui.d.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/@types/i2c.d.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/@types/keyvalue.d.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/@types/ledc.d.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/@types/misc.d.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/@types/motor.d.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/@types/path.d.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/@types/platform.d.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/@types/pulseCounter.d.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/@types/simpleradio.d.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/@types/smartled.d.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/@types/stdio.d.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/@types/timers.d.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/@types/timestamp.d.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/@types/wifi.d.ts (100%) create mode 100644 docs/robot/lekce2/blank_project/src/index.ts rename docs/robot/lekce2/{black_project => blank_project}/src/libs/colors.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/src/libs/readline.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/src/libs/robutek.ts (90%) rename docs/robot/lekce2/{black_project => blank_project}/src/libs/servo.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/src/libs/simplemotors.ts (100%) rename docs/robot/lekce2/{black_project => blank_project}/tsconfig.json (100%) create mode 100644 docs/robot/lekce3/project3/@types/gridui.d.ts create mode 100644 docs/robot/lekce3/project3/@types/keyvalue.d.ts create mode 100644 docs/robot/lekce3/project3/@types/motor.d.ts create mode 100644 docs/robot/lekce3/project3/@types/pulseCounter.d.ts create mode 100644 docs/robot/lekce3/project3/@types/timestamp.d.ts create mode 100644 docs/robot/lekce3/project3/@types/wifi.d.ts create mode 100644 docs/robot/lekce3/project3/src/libs/readline.ts create mode 100644 docs/robot/lekce3/project3/src/libs/robutek.ts create mode 100644 docs/robot/lekce3/project3/src/libs/servo.ts create mode 100644 docs/robot/lekce3/project3/src/libs/simplemotors.ts create mode 100644 docs/robot/lekce4/project4/@types/gridui.d.ts create mode 100644 docs/robot/lekce4/project4/@types/keyvalue.d.ts create mode 100644 docs/robot/lekce4/project4/@types/motor.d.ts create mode 100644 docs/robot/lekce4/project4/@types/pulseCounter.d.ts create mode 100644 docs/robot/lekce4/project4/@types/timestamp.d.ts create mode 100644 docs/robot/lekce4/project4/@types/wifi.d.ts create mode 100644 docs/robot/lekce4/project4/src/libs/readline.ts create mode 100644 docs/robot/lekce4/project4/src/libs/robutek.ts create mode 100644 docs/robot/lekce4/project4/src/libs/servo.ts create mode 100644 docs/robot/lekce4/project4/src/libs/simplemotors.ts diff --git a/docs/robot/lekce1/example1/src/libs/robutek.ts b/docs/robot/lekce1/example1/src/libs/robutek.ts index a6a934b1..5a143f46 100644 --- a/docs/robot/lekce1/example1/src/libs/robutek.ts +++ b/docs/robot/lekce1/example1/src/libs/robutek.ts @@ -7,15 +7,18 @@ import { Servo } from "./servo.js"; import { SmartLed, LED_WS2812 } from "smartled"; import * as motors from "motor" +import * as ledc from "ledc"; + +ledc.configureTimer(0, 64000, 10); const leftMotorPins: motors.MotorPins = { motA: 11, motB: 12, encA: 39, encB: 40 } -const rightMotorPins: motors.MotorPins = { motA: 45, motB: 13, encA: 41, encB: 42 } +const rightMotorPins: motors.MotorPins = { motA: 45, motB: 13, encA: 42, encB: 41 } -const leftMotorLedc: motors.LedcConfig = { timer: 1, channelA: 0, channelB: 1 } -const rightMotorLedc: motors.LedcConfig = { timer: 1, channelA: 2, channelB: 3 } +const leftMotorLedc: motors.LedcConfig = { timer: 0, channelA: 0, channelB: 1 } +const rightMotorLedc: motors.LedcConfig = { timer: 0, channelA: 2, channelB: 3 } -export const LeftMot = new motors.Motor({ pins: leftMotorPins, ledc: leftMotorLedc, encTicks: 400, diameter: 10 }); -export const RightMot = new motors.Motor({ pins: rightMotorPins, ledc: rightMotorLedc, encTicks: 400, diameter: 10 }); +export const LeftMot = new motors.Motor({ pins: leftMotorPins, ledc: leftMotorLedc, encTicks: 406, diameter: 34 }); +export const RightMot = new motors.Motor({ pins: rightMotorPins, ledc: rightMotorLedc, encTicks: 406, diameter: 34 }); export function init() { @@ -30,14 +33,6 @@ export function init() { gpio.write(LineSensors.S_PWR, 1); } - - -type MoveDuration = { - distance?: number; // distance in cm - speed?: number; // speed in mm/s? -} - - export type SensorType = 'W_FR' | 'W_FL' | 'W_BL' | 'W_BR' | 'L_FR' | 'L_FL' | 'L_BL' | 'L_BR'; export class LineSensors { diff --git a/docs/robot/lekce2/black_project/src/index.ts b/docs/robot/lekce2/black_project/src/index.ts deleted file mode 100644 index 131e6b25..00000000 --- a/docs/robot/lekce2/black_project/src/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as colors from "./libs/colors.js" -import * as Robutek from "./libs/robutek.js" - - -Robutek.ledStrip.set(0, colors.green); // nastaví barvu LED na desce na zelenou, LED na pásku začínají od 1 -Robutek.ledStrip.show(); // zobrazí nastavení na LED - -setInterval(() => { // pravidelně vyvolává událost - console.log("Robotický tábor 2024, zdraví Jirka Vácha!"); // vypíše text: Robotický tábor 2024, zdraví Jirka Vácha! -}, 1000); // čas opakování se udává v milisekundách (1000 ms je 1 sekunda) diff --git a/docs/robot/lekce2/black_project/@types/adc.d.ts b/docs/robot/lekce2/blank_project/@types/adc.d.ts similarity index 100% rename from docs/robot/lekce2/black_project/@types/adc.d.ts rename to docs/robot/lekce2/blank_project/@types/adc.d.ts diff --git a/docs/robot/lekce2/black_project/@types/basicStream.d.ts b/docs/robot/lekce2/blank_project/@types/basicStream.d.ts similarity index 100% rename from docs/robot/lekce2/black_project/@types/basicStream.d.ts rename to docs/robot/lekce2/blank_project/@types/basicStream.d.ts diff --git a/docs/robot/lekce2/black_project/@types/fs.d.ts b/docs/robot/lekce2/blank_project/@types/fs.d.ts similarity index 100% rename from docs/robot/lekce2/black_project/@types/fs.d.ts rename to docs/robot/lekce2/blank_project/@types/fs.d.ts diff --git a/docs/robot/lekce2/black_project/@types/gpio.d.ts b/docs/robot/lekce2/blank_project/@types/gpio.d.ts similarity index 100% rename from docs/robot/lekce2/black_project/@types/gpio.d.ts rename to docs/robot/lekce2/blank_project/@types/gpio.d.ts diff --git a/docs/robot/lekce2/black_project/@types/gridui.d.ts b/docs/robot/lekce2/blank_project/@types/gridui.d.ts similarity index 100% rename from docs/robot/lekce2/black_project/@types/gridui.d.ts rename to docs/robot/lekce2/blank_project/@types/gridui.d.ts diff --git a/docs/robot/lekce2/black_project/@types/i2c.d.ts b/docs/robot/lekce2/blank_project/@types/i2c.d.ts similarity index 100% rename from docs/robot/lekce2/black_project/@types/i2c.d.ts rename to docs/robot/lekce2/blank_project/@types/i2c.d.ts diff --git a/docs/robot/lekce2/black_project/@types/keyvalue.d.ts b/docs/robot/lekce2/blank_project/@types/keyvalue.d.ts similarity index 100% rename from docs/robot/lekce2/black_project/@types/keyvalue.d.ts rename to docs/robot/lekce2/blank_project/@types/keyvalue.d.ts diff --git a/docs/robot/lekce2/black_project/@types/ledc.d.ts b/docs/robot/lekce2/blank_project/@types/ledc.d.ts similarity index 100% rename from docs/robot/lekce2/black_project/@types/ledc.d.ts rename to docs/robot/lekce2/blank_project/@types/ledc.d.ts diff --git a/docs/robot/lekce2/black_project/@types/misc.d.ts b/docs/robot/lekce2/blank_project/@types/misc.d.ts similarity index 100% rename from docs/robot/lekce2/black_project/@types/misc.d.ts rename to docs/robot/lekce2/blank_project/@types/misc.d.ts diff --git a/docs/robot/lekce2/black_project/@types/motor.d.ts b/docs/robot/lekce2/blank_project/@types/motor.d.ts similarity index 100% rename from docs/robot/lekce2/black_project/@types/motor.d.ts rename to docs/robot/lekce2/blank_project/@types/motor.d.ts diff --git a/docs/robot/lekce2/black_project/@types/path.d.ts b/docs/robot/lekce2/blank_project/@types/path.d.ts similarity index 100% rename from docs/robot/lekce2/black_project/@types/path.d.ts rename to docs/robot/lekce2/blank_project/@types/path.d.ts diff --git a/docs/robot/lekce2/black_project/@types/platform.d.ts b/docs/robot/lekce2/blank_project/@types/platform.d.ts similarity index 100% rename from docs/robot/lekce2/black_project/@types/platform.d.ts rename to docs/robot/lekce2/blank_project/@types/platform.d.ts diff --git a/docs/robot/lekce2/black_project/@types/pulseCounter.d.ts b/docs/robot/lekce2/blank_project/@types/pulseCounter.d.ts similarity index 100% rename from docs/robot/lekce2/black_project/@types/pulseCounter.d.ts rename to docs/robot/lekce2/blank_project/@types/pulseCounter.d.ts diff --git a/docs/robot/lekce2/black_project/@types/simpleradio.d.ts b/docs/robot/lekce2/blank_project/@types/simpleradio.d.ts similarity index 100% rename from docs/robot/lekce2/black_project/@types/simpleradio.d.ts rename to docs/robot/lekce2/blank_project/@types/simpleradio.d.ts diff --git a/docs/robot/lekce2/black_project/@types/smartled.d.ts b/docs/robot/lekce2/blank_project/@types/smartled.d.ts similarity index 100% rename from docs/robot/lekce2/black_project/@types/smartled.d.ts rename to docs/robot/lekce2/blank_project/@types/smartled.d.ts diff --git a/docs/robot/lekce2/black_project/@types/stdio.d.ts b/docs/robot/lekce2/blank_project/@types/stdio.d.ts similarity index 100% rename from docs/robot/lekce2/black_project/@types/stdio.d.ts rename to docs/robot/lekce2/blank_project/@types/stdio.d.ts diff --git a/docs/robot/lekce2/black_project/@types/timers.d.ts b/docs/robot/lekce2/blank_project/@types/timers.d.ts similarity index 100% rename from docs/robot/lekce2/black_project/@types/timers.d.ts rename to docs/robot/lekce2/blank_project/@types/timers.d.ts diff --git a/docs/robot/lekce2/black_project/@types/timestamp.d.ts b/docs/robot/lekce2/blank_project/@types/timestamp.d.ts similarity index 100% rename from docs/robot/lekce2/black_project/@types/timestamp.d.ts rename to docs/robot/lekce2/blank_project/@types/timestamp.d.ts diff --git a/docs/robot/lekce2/black_project/@types/wifi.d.ts b/docs/robot/lekce2/blank_project/@types/wifi.d.ts similarity index 100% rename from docs/robot/lekce2/black_project/@types/wifi.d.ts rename to docs/robot/lekce2/blank_project/@types/wifi.d.ts diff --git a/docs/robot/lekce2/blank_project/src/index.ts b/docs/robot/lekce2/blank_project/src/index.ts new file mode 100644 index 00000000..ccb85e82 --- /dev/null +++ b/docs/robot/lekce2/blank_project/src/index.ts @@ -0,0 +1,3 @@ +import * as Robutek from "./libs/robutek.js" +import * as colors from "./libs/colors.js" +import * as gpio from "gpio" \ No newline at end of file diff --git a/docs/robot/lekce2/black_project/src/libs/colors.ts b/docs/robot/lekce2/blank_project/src/libs/colors.ts similarity index 100% rename from docs/robot/lekce2/black_project/src/libs/colors.ts rename to docs/robot/lekce2/blank_project/src/libs/colors.ts diff --git a/docs/robot/lekce2/black_project/src/libs/readline.ts b/docs/robot/lekce2/blank_project/src/libs/readline.ts similarity index 100% rename from docs/robot/lekce2/black_project/src/libs/readline.ts rename to docs/robot/lekce2/blank_project/src/libs/readline.ts diff --git a/docs/robot/lekce2/black_project/src/libs/robutek.ts b/docs/robot/lekce2/blank_project/src/libs/robutek.ts similarity index 90% rename from docs/robot/lekce2/black_project/src/libs/robutek.ts rename to docs/robot/lekce2/blank_project/src/libs/robutek.ts index a6a934b1..5a143f46 100644 --- a/docs/robot/lekce2/black_project/src/libs/robutek.ts +++ b/docs/robot/lekce2/blank_project/src/libs/robutek.ts @@ -7,15 +7,18 @@ import { Servo } from "./servo.js"; import { SmartLed, LED_WS2812 } from "smartled"; import * as motors from "motor" +import * as ledc from "ledc"; + +ledc.configureTimer(0, 64000, 10); const leftMotorPins: motors.MotorPins = { motA: 11, motB: 12, encA: 39, encB: 40 } -const rightMotorPins: motors.MotorPins = { motA: 45, motB: 13, encA: 41, encB: 42 } +const rightMotorPins: motors.MotorPins = { motA: 45, motB: 13, encA: 42, encB: 41 } -const leftMotorLedc: motors.LedcConfig = { timer: 1, channelA: 0, channelB: 1 } -const rightMotorLedc: motors.LedcConfig = { timer: 1, channelA: 2, channelB: 3 } +const leftMotorLedc: motors.LedcConfig = { timer: 0, channelA: 0, channelB: 1 } +const rightMotorLedc: motors.LedcConfig = { timer: 0, channelA: 2, channelB: 3 } -export const LeftMot = new motors.Motor({ pins: leftMotorPins, ledc: leftMotorLedc, encTicks: 400, diameter: 10 }); -export const RightMot = new motors.Motor({ pins: rightMotorPins, ledc: rightMotorLedc, encTicks: 400, diameter: 10 }); +export const LeftMot = new motors.Motor({ pins: leftMotorPins, ledc: leftMotorLedc, encTicks: 406, diameter: 34 }); +export const RightMot = new motors.Motor({ pins: rightMotorPins, ledc: rightMotorLedc, encTicks: 406, diameter: 34 }); export function init() { @@ -30,14 +33,6 @@ export function init() { gpio.write(LineSensors.S_PWR, 1); } - - -type MoveDuration = { - distance?: number; // distance in cm - speed?: number; // speed in mm/s? -} - - export type SensorType = 'W_FR' | 'W_FL' | 'W_BL' | 'W_BR' | 'L_FR' | 'L_FL' | 'L_BL' | 'L_BR'; export class LineSensors { diff --git a/docs/robot/lekce2/black_project/src/libs/servo.ts b/docs/robot/lekce2/blank_project/src/libs/servo.ts similarity index 100% rename from docs/robot/lekce2/black_project/src/libs/servo.ts rename to docs/robot/lekce2/blank_project/src/libs/servo.ts diff --git a/docs/robot/lekce2/black_project/src/libs/simplemotors.ts b/docs/robot/lekce2/blank_project/src/libs/simplemotors.ts similarity index 100% rename from docs/robot/lekce2/black_project/src/libs/simplemotors.ts rename to docs/robot/lekce2/blank_project/src/libs/simplemotors.ts diff --git a/docs/robot/lekce2/black_project/tsconfig.json b/docs/robot/lekce2/blank_project/tsconfig.json similarity index 100% rename from docs/robot/lekce2/black_project/tsconfig.json rename to docs/robot/lekce2/blank_project/tsconfig.json diff --git a/docs/robot/lekce2/index.md b/docs/robot/lekce2/index.md index 00e4d10f..5bad6a3d 100644 --- a/docs/robot/lekce2/index.md +++ b/docs/robot/lekce2/index.md @@ -12,24 +12,19 @@ nejzákladnější příkazy: i velice jednoduchý program už může mít vidit ## Zadání A -Nejdříve si zopakujeme předchozí lekci, a rozsvítíme RGB LED na ESP32 (`GPIO 48`) jednou barvou (například červenou). +Nejdříve si zopakujeme předchozí lekci, a rozsvítíme RGB LED na Robůtkovi (`GPIO 48`) jednou barvou (například červenou). Na začátku tohoto úkolu si stáhneme nový [zip](./blank_project.zip) soubor obsahující prázdný projekt. Po stažení složku rozbalíme a otevřeme ve Visual Studio Code. V souboru `index.ts` jsou připraveny `import` příkazy: ty nám umožní využívat funkcionalitu z různých souborů, např. jednoduše ovládat LEDku, nebo využívat nadefinované barvy. -Abychom mohli LED u procesoru ovládat, musíme ji získat příkazem `#!ts const led = new SmartLed(...)`, a do závorky napíšeme číslo PINu, počet LED světel (zatím je to 1), a typ světla: `LED_WS2812`. Barvu LED nastavíme pomocí `led.set(0, colors.nějaká_barva)` a zobrazíme pomocí `led.show()`. +Barvu LED nastavíme pomocí `Robutek.ledStrip.set(0, colors.nějaká_barva)` a zobrazíme pomocí `Robutek.ledStrip.show()`. ??? note "Řešení" ```ts - import { SmartLed, LED_WS2812 } from "smartled"; + import * as Robutek from "./libs/robutek.js" import * as colors from "./libs/colors.js" - const LED_PIN = 48; - const LED_COUNT = 1; - - const ledStrip = new SmartLed(LED_PIN, LED_COUNT, LED_WS2812); // připojí pásek na pin 48, s 1 ledkou a typem WS2812 - - ledStrip.set(0, colors.red); // nastaví barvu nulté LED na červenou (RGB 255 0 0) - ledStrip.show(); // zobrazí nastavení na LED + Robutek.ledStrip.set(0, colors.red); // nastaví barvu nulté LED na červenou (RGB 255 0 0) + Robutek.ledStrip.show(); // zobrazí nastavení na LED ``` ## Co je to událost v programování? @@ -61,26 +56,22 @@ Pomocí událostí rozsvítíme při stisknutí tlačítka (GPIO 0) RGB LED na E ??? note "Řešení" ```ts - import * as gpio from "gpio"; - import { SmartLed, LED_WS2812 } from "smartled"; + import * as Robutek from "./libs/robutek.js" import * as colors from "./libs/colors.js" + import * as gpio from "gpio" - const LED_PIN = 48; - const LED_COUNT = 1; const BTN_RIGHT = 0; - const ledStrip = new SmartLed(LED_PIN, LED_COUNT, LED_WS2812); // připojí pásek na pin 48, s 1 ledkou a typem WS2812 - gpio.pinMode(BTN_RIGHT, gpio.PinMode.INPUT); // nastaví pin 0 jako vstup gpio.on("falling", BTN_RIGHT, () => { // událost, která proběhne při stisknutí tlačítka připojeného na pin 0 - ledStrip.set(0, colors.red); // nastaví barvu nulté LED na červenou (RGB 255 0 0) - ledStrip.show(); // zobrazí nastavení na LED + Robutek.ledStrip.set(0, colors.red); // nastaví barvu nulté LED na červenou (RGB 255 0 0) + Robutek.ledStrip.show(); // zobrazí nastavení na LED }); gpio.on("rising", BTN_RIGHT, () => { // událost, která proběhne při puštění tlačítka připojeného na pin 0 - ledStrip.set(0, colors.off); // nastaví nultou LED na zhasnutou (RGB 0 0 0) - ledStrip.show(); // zobrazí nastavení na LED + Robutek.ledStrip.set(0, colors.off); // nastaví nultou LED na zhasnutou (RGB 0 0 0) + Robutek.ledStrip.show(); // zobrazí nastavení na LED }); ``` @@ -109,4 +100,4 @@ Při stisknutí tlačítka (GPIO 0) vypíšeme pozdrav. ## Výstupní úkol V2 - Změna barvy -Při stisknutí tlačítka (GPIO 0) rozsvítíme RGB LED na ESP32 (`GPIO 48`) jednou barvou a při puštění barvu změníme na jinou. +Při stisknutí tlačítka (GPIO 0) rozsvítíme RGB LED na Robůtkovi (`GPIO 48`) jednou barvou a při puštění barvu změníme na jinou. diff --git a/docs/robot/lekce3/index.md b/docs/robot/lekce3/index.md index 77a682d4..927e49b8 100644 --- a/docs/robot/lekce3/index.md +++ b/docs/robot/lekce3/index.md @@ -82,11 +82,11 @@ barev na maximum (hodnoty `(255, 255, 255)`). Druhou variantou je použití předdefinovaných barev, které jsou v souboru `colors.ts`. Příklad použití obou variant: ```ts - ledStrip.set(0, colors.off); // Vypne LEDku pomocí předdefinované barvy - ledStrip.set(0, {r: 0, g: 0, b: 0}); // Vypne LEDku pomocí vlastní barvy + Robutek.ledStrip.set(0, colors.off); // Vypne LEDku pomocí předdefinované barvy + Robutek.ledStrip.set(0, {r: 0, g: 0, b: 0}); // Vypne LEDku pomocí vlastní barvy - ledStrip.set(0, colors.green); // Rozsvítí LEDku zeleně pomocí předdefinované barvy - ledStrip.set(0, {r: 0, g: 255, b: 0}); // Rozsvítí LEDku zeleně pomocí vlastní barvy + Robutek.ledStrip.set(0, colors.green); // Rozsvítí LEDku zeleně pomocí předdefinované barvy + Robutek.ledStrip.set(0, {r: 0, g: 255, b: 0}); // Rozsvítí LEDku zeleně pomocí vlastní barvy ``` Pro tuto lekci si stáhneme [zip](./project3.zip), nebo navážeme na předchozí cvičení. Své řešení budeme psát do souboru `index.ts`. @@ -97,24 +97,18 @@ Pomocí jedné proměnné se stavem a podmínky každou sekundu buď rozsvítím ??? note "Řešení" ```ts - import { SmartLed, LED_WS2812 } from "smartled"; + import * as Robutek from "./libs/robutek.js" import * as colors from "./libs/colors.js" - - const LED_PIN = 48; - const LED_COUNT = 1; - - const ledStrip = new SmartLed(LED_PIN, LED_COUNT, LED_WS2812); // připojí pásek na pin 48, s 1 ledkou a typem WS2812 - let on: boolean = false; // LED je vypnutá setInterval(() => { if (on) { // Pokud je LED zapnutá - ledStrip.set(0, colors.off); // Vypneme LED - ledStrip.show(); // Zobrazíme změny + Robutek.ledStrip.set(0, colors.off); // Vypneme LED + Robutek.ledStrip.show(); // Zobrazíme změny on = false; } else { - ledStrip.set(0, colors.green); // Rozsvítíme LED zelenou barvou - ledStrip.show(); // Zobrazíme změny + Robutek.ledStrip.set(0, colors.green); // Rozsvítíme LED zelenou barvou + Robutek.ledStrip.show(); // Zobrazíme změny on = true } }, 1000); @@ -128,19 +122,14 @@ opět nastavit na `0`. ??? note "Řešení" ```ts - import { SmartLed, LED_WS2812 } from "smartled"; + import * as Robutek from "./libs/robutek.js" import * as colors from "./libs/colors.js" - const LED_PIN = 48; - const LED_COUNT = 1; - - const ledStrip = new SmartLed(LED_PIN, LED_COUNT, LED_WS2812); // připojí pásek na pin 48, s 1 ledkou a typem WS2812 - let shade = 0; // Držíme si stav s aktuálním odstínem setInterval(() => { - ledStrip.set(0, colors.rainbow(shade)); // Nastavíme LED na aktuální odstín - ledStrip.show(); // Zobrazíme vybranou barvu + Robutek.ledStrip.set(0, colors.rainbow(shade)); // Nastavíme LED na aktuální odstín + Robutek.ledStrip.show(); // Zobrazíme vybranou barvu shade = shade + 1; // Zvedneme odstín (lze i shade += 1) if (shade > 360) { shade = 0; @@ -152,36 +141,35 @@ opět nastavit na `0`. Tentokrát budeme reagovat na stisk tlačítka. Do desky si zapojíme pásku 8 inteligentních ledek, a vybranou barvou je budeme rozsvěcet. + Po stisku tlačítka zhasneme aktuální LEDku, a rozsvítíme tu další. Pokud při stisku tlačítka svítí poslední LED, zhasneme ji, a rozsvítíme opět první LED. +!!! note "Led pásek je připojený za inteligentní ledku na desce, takže inde pásku začíná na 1" + ??? note "Řešení" ```ts - import { SmartLed, Rgb, LED_WS2812 } from "smartled"; + import * as Robutek from "./libs/robutek.js" import * as colors from "./libs/colors.js" import * as gpio from "gpio"; - const LED_PIN = 21; - const LED_COUNT = 8; - const BTN_PIN = 0; gpio.pinMode(BTN_PIN, gpio.PinMode.INPUT_PULLUP); // Nastavíme tlačítko - const ledStrip = new SmartLed(LED_PIN, LED_COUNT, LED_WS2812); // připojí pásek na pin 21, s 8 ledkami a typem WS2812 - let index : number = 0; + let index : number = 1; let color : Rgb = colors.light_blue; // Vybereme si barvu - ledStrip.set(0, color); // Nastavíme LED na aktuální odstín - ledStrip.show(); // Zobrazíme změny + Robutek.ledStrip.set(0, color); // Nastavíme LED na aktuální odstín + Robutek.ledStrip.show(); // Zobrazíme změny gpio.on("falling", BTN_PIN, () => { - ledStrip.set(index, colors.off); // Vypneme předchozí LED + Robutek.ledStrip.set(index, colors.off); // Vypneme předchozí LED index = index + 1; // Zvedneme index (lze i index += 1) - if(index > 7){ // Pokud jsme mimo rozsah pásku, vrátíme se na začátek - index = 0; + if(index > 8){ // Pokud jsme mimo rozsah pásku, vrátíme se na začátek + index = 1; } - ledStrip.set(index, color); // Nastavíme aktuální LED - ledStrip.show(); // Zobrazíme změny + Robutek.ledStrip.set(index, color); // Nastavíme aktuální LED + Robutek.ledStrip.show(); // Zobrazíme změny }); ``` diff --git a/docs/robot/lekce3/project3/@types/adc.d.ts b/docs/robot/lekce3/project3/@types/adc.d.ts index d77e9d94..871de201 100644 --- a/docs/robot/lekce3/project3/@types/adc.d.ts +++ b/docs/robot/lekce3/project3/@types/adc.d.ts @@ -1,9 +1,19 @@ declare module "adc" { + type Atten = number; + + const Attenuation: { + readonly Db0: Atten; + readonly Db2_5: Atten; + readonly Db6: Atten; + readonly Db11: Atten; + }; + + /** * Enable ADC on the given pin. * @param pin The pin to enable ADC on. */ - function configure(pin: number): void; + function configure(pin: number, attenuation?: Atten): void; /** * Read the value of the given pin. diff --git a/docs/robot/lekce3/project3/@types/gpio.d.ts b/docs/robot/lekce3/project3/@types/gpio.d.ts index 9c74419e..09c167db 100644 --- a/docs/robot/lekce3/project3/@types/gpio.d.ts +++ b/docs/robot/lekce3/project3/@types/gpio.d.ts @@ -1,12 +1,16 @@ declare module "gpio" { const PinMode: { - DISABLE: number, - OUTPUT: number, - INPUT: number, - INPUT_PULLUP: number, - INPUT_PULLDOWN: number, + readonly DISABLE: number, + readonly OUTPUT: number, + readonly INPUT: number, + readonly INPUT_PULLUP: number, + readonly INPUT_PULLDOWN: number, }; + interface EventInfo { + timestamp: Timestamp; + } + /** * Configure the given pin. * @param pin The pin to configure. @@ -34,7 +38,7 @@ declare module "gpio" { * @param pin The pin to handle the event for. * @param callback The callback to call when the event occurs. */ - function on(event: "rising" | "falling" | "change", pin: number, callback: () => void): void; + function on(event: "rising" | "falling" | "change", pin: number, callback: (info: EventInfo) => void): void; /** * Remove event handler for the given pin. diff --git a/docs/robot/lekce3/project3/@types/gridui.d.ts b/docs/robot/lekce3/project3/@types/gridui.d.ts new file mode 100644 index 00000000..8ffaba74 --- /dev/null +++ b/docs/robot/lekce3/project3/@types/gridui.d.ts @@ -0,0 +1,321 @@ +declare module "gridui" { + namespace widget { + interface Base { + readonly uuid: number + widgetX: number + widgetY: number + widgetW: number + widgetH: number + widgetTab: number + css(key: string): string + setCss(key: string, value: string): void + } + + interface Arm extends Base { + readonly x: number + readonly y: number + } + + interface Bar extends Base { + color: string + fontSize: number + min: number + max: number + value: number + showValue: boolean + } + + interface Button extends Base { + text: string + fontSize: number + color: string + background: string + align: string + valign: string + disabled: boolean + readonly pressed: boolean + } + + interface Camera extends Base { + rotation: number + clip: boolean + } + + interface Checkbox extends Base { + fontSize: number + checked: boolean + color: string + text: string + } + + interface Circle extends Base { + color: string + fontSize: number + min: number + max: number + lineWidth: number + valueStart: number + value: number + showValue: boolean + } + + interface Input extends Base { + text: string + color: string + type: string + disabled: boolean + } + + interface Joystick extends Base { + color: string + keys: string + text: string + readonly x: number + readonly y: number + } + + interface Led extends Base { + color: string + on: boolean + } + + interface Orientation extends Base { + color: string + readonly yaw: number + readonly pitch: number + readonly roll: number + readonly joystickX: number + readonly joystickY: number + } + + interface Select extends Base { + color: string + background: string + disabled: boolean + options: string + selectedIndex: number + } + + interface Slider extends Base { + color: string + fontSize: number + min: number + max: number + value: number + precision: number + showValue: boolean + } + + interface SpinEdit extends Base { + fontSize: number + color: string + value: number + step: number + precision: number + } + + interface Switcher extends Base { + fontSize: number + color: string + value: number + min: number + max: number + } + + interface Text extends Base { + text: string + fontSize: number + color: string + background: string + align: string + valign: string + prefix: string + suffix: string + } + } + + namespace builder { + interface Base { + css(key: string, value: string): this + finish(): this + } + + interface Arm extends Base { + info(info: Record): Arm + + onGrab(callback: (arm: widget.Arm) => void): Arm + onPositionChanged(callback: (arm: widget.Arm) => void): Arm + } + + interface Bar extends Base { + color(color: string): Bar + fontSize(fontSize: number): Bar + min(min: number): Bar + max(max: number): Bar + value(value: number): Bar + showValue(showValue: boolean): Bar + } + + interface Button extends Base { + text(text: string): Button + fontSize(fontSize: number): Button + color(color: string): Button + background(background: string): Button + align(align: string): Button + valign(valign: string): Button + disabled(disabled: boolean): Button + + onPress(callback: (button: widget.Button) => void): Button + onRelease(callback: (button: widget.Button) => void): Button + } + + interface Camera extends Base { + rotation(rotation: number): Camera + clip(clip: boolean): Camera + tags(tags: any /* TODO: fix type */): Camera + } + + interface Checkbox extends Base { + fontSize(fontSize: number): Checkbox + checked(checked: boolean): Checkbox + color(color: string): Checkbox + text(text: string): Checkbox + + onChanged(callback: (checkbox: widget.Checkbox) => void): Checkbox + } + + interface Circle extends Base { + color(color: string): Circle + fontSize(fontSize: number): Circle + min(min: number): Circle + max(max: number): Circle + lineWidth(lineWidth: number): Circle + valueStart(valueStart: number): Circle + value(value: number): Circle + showValue(showValue: boolean): Circle + } + + interface Input extends Base { + text(text: string): Input + color(color: string): Input + type(type: string): Input + disabled(disabled: boolean): Input + + onChanged(callback: (input: widget.Input) => void): Input + } + + interface Joystick extends Base { + color(color: string): Joystick + keys(keys: string): Joystick + text(text: string): Joystick + + onClick(callback: (joystick: widget.Joystick) => void): Joystick + onPositionChanged(callback: (joystick: widget.Joystick) => void): Joystick + } + + interface Led extends Base { + color(color: string): Led + on(on: boolean): Led + } + + interface Orientation extends Base { + color(color: string): Orientation + + onPositionChanged(callback: (orientation: widget.Orientation) => void): Orientation + } + + interface Select extends Base { + color(color: string): Select + background(background: string): Select + disabled(disabled: boolean): Select + options(options: string): Select + selectedIndex(selectedIndex: number): Select + + onChanged(callback: (select: widget.Select) => void): Select + } + + interface Slider extends Base { + color(color: string): Slider + fontSize(fontSize: number): Slider + min(min: number): Slider + max(max: number): Slider + value(value: number): Slider + precision(precision: number): Slider + showValue(showValue: boolean): Slider + + onChanged(callback: (slider: widget.Slider) => void): Slider + } + + interface SpinEdit extends Base { + fontSize(fontSize: number): SpinEdit + color(color: string): SpinEdit + value(value: number): SpinEdit + step(step: number): SpinEdit + precision(precision: number): SpinEdit + + onChanged(callback: (spinEdit: widget.SpinEdit) => void): SpinEdit + } + + interface Switcher extends Base { + fontSize(fontSize: number): Switcher + color(color: string): Switcher + value(value: number): Switcher + min(min: number): Switcher + max(max: number): Switcher + + onChanged(callback: (switcher: widget.Switcher) => void): Switcher + } + + interface Text extends Base { + text(text: string): Text + fontSize(fontSize: number): Text + color(color: string): Text + background(background: string): Text + align(align: string): Text + valign(valign: string): Text + prefix(prefix: string): Text + suffix(suffix: string): Text + } + + interface Widget extends Base {} + } + + class Builder { + arm(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Arm + bar(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Bar + button(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Button + camera(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Camera + checkbox(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Checkbox + circle(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Circle + input(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Input + joystick(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Joystick + led(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Led + orientation(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Orientation + select(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Select + slider(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Slider + spinEdit(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.SpinEdit + switcher(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Switcher + text(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Text + widget(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Widget + } + + /** + * Initialize GridUI. + * @param ownerName name of the owner, must match the name entered in RBController app. + * @param deviceName name of this device, visible in the RBController app. + * @param builderCallback callback, which receives the builder instance that can be used to create widgets. + */ + function begin(ownerName: string, deviceName: string, builderCallback: (builder: Builder) => void): void + + /** + * Stop GridUI. + */ + function end(): void + + /** + * Returns included GridUI version as number, to be compared with hex representation of the version. + * + * For example, for version 5.1.0, do: `gridui.version() >= 0x050100` + */ + function version(): number +} diff --git a/docs/robot/lekce3/project3/@types/keyvalue.d.ts b/docs/robot/lekce3/project3/@types/keyvalue.d.ts new file mode 100644 index 00000000..761b09fb --- /dev/null +++ b/docs/robot/lekce3/project3/@types/keyvalue.d.ts @@ -0,0 +1,61 @@ +declare module "keyvalue" { + class KeyValueNamespace { + /** + * Get saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present + */ + get(key: string): string | number | null; + + /** + * Get string saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present or if not string + */ + getString(key: string): string | null; + + /** + * Get number saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present or if not number + */ + getNumber(key: string): number | null; + + /** + * Set value in KeyValue namespace, overwriting any previous value. + * Call commit() after setting everything, otherwise changes will be lost! + * @param key key, max 15 characters. + * @param value value + */ + set(key: string, value: string | number): void; + + /** + * Erase value from KeyValue namespace. + * Call commit() after setting everything, otherwise changes will be lost! + * @param key key, max 15 characters. + */ + erase(key: string): void; + + /** + * Check existance of a key. + * @param key key, max 15 characters. + * @returns true if exists, false otherwise + */ + exists(key: string): boolean; + + /** + * Save modifications to persistent storage. + */ + commit(): void; + } + + /** + * Open a KeyValue namespace for use. KeyValue is a persitent storage for small + * pieces of data that saves values across restarts. + * + * The namespaces are isolated from each other. + * + * @param namespace Namespace name, max 15 characters. + */ + function open(namespace: string): KeyValueNamespace; +} diff --git a/docs/robot/lekce3/project3/@types/motor.d.ts b/docs/robot/lekce3/project3/@types/motor.d.ts new file mode 100644 index 00000000..2ec31392 --- /dev/null +++ b/docs/robot/lekce3/project3/@types/motor.d.ts @@ -0,0 +1,82 @@ +declare module "motor" { + + type MoveDuration = { + distance?: number; + time?: number; + } + + type MotorPins = { + motA: number; + motB: number; + encA: number; + encB: number; + }; + + type LedcConfig = { + timer: number; + channelA: number; + channelB: number; + }; + + class Motor { + /** + * Construc a new Motor instance + * @param options Motor configuration + * @note Units used in the diameter parameter determines the units used in the other methods + */ + constructor(options: { pins: MotorPins, ledc: LedcConfig, encTicks: number, diameter: number }); + + /** + * Set the speed of the motor + * @param speed Speed in units per second + * @note The units are the same as used in the diameter parameter + */ + setSpeed(speed: number): void; + // setRamp(ramp: number): void; + + /** + * Move the motor + * @param duration Duration of the movement + * @note The units are the same as used in the diameter parameter + * @note If the duration is not provided, the motor will move indefinitely + */ + move(duration?: MoveDuration): Promise; + + /** + * Stop the motor + * @param brake If true, the motor will brake, otherwise it will coast to a stop + */ + stop(brake?: boolean): Promise; + + /** + * Get the position of the motor + * @note The units are the same as used in the diameter parameter + * @returns The position of the motor + */ + getPosition(): number; + + /** + * Close the motor + */ + close(): void; + } + + /* class Wheels { + constructor(options: { + left: MotorPins, + right: MotorPins, + encTicks: number, + diameter: number, + spacing: number + }); + setSpeed(speed: number): void; + // setRamp(ramp: number): void; + + move(curve: number, duration?: MoveDuration): Promise; + // rotate(angle: number): Promise; + stop(brake?: boolean): Promise; + getPosition(): { left: number, right: number }; + + close(): void; + } */ +} diff --git a/docs/robot/lekce3/project3/@types/pulseCounter.d.ts b/docs/robot/lekce3/project3/@types/pulseCounter.d.ts new file mode 100644 index 00000000..76ad8edd --- /dev/null +++ b/docs/robot/lekce3/project3/@types/pulseCounter.d.ts @@ -0,0 +1,64 @@ +declare module "pulseCounter" { + type LevelAction = number; + type EdgeAction = number; + + const LevelAction: { + Keep: LevelAction; + Inverse: LevelAction; + Hold: LevelAction; + }; + + const EdgeAction: { + Hold: EdgeAction; + Increase: EdgeAction; + Decrease: EdgeAction; + }; + + type LevelMode = { + low: LevelAction, + high: LevelAction, + } + + type EdgeMode = { + pos: EdgeAction, + neg: EdgeAction, + } + + class PulseCounter { + /** + * Create a new pulse counter instance. + * @param options The options for the pulse counter. + */ + constructor(options: { + pinLevel: number, + pinEdge: number, + levelMode: LevelMode, + edgeMode: EdgeMode, + }) + + /** + * Read the current value of the pulse counter. + */ + read(): number; + + /** + * Reset the pulse counter to 0. + */ + clear(): void; + + /** + * Start the pulse counter. + */ + start(): void; + + /** + * Stop the pulse counter. + */ + stop(): void; + + /** + * Close the pulse counter. + */ + close(): void; + } +} diff --git a/docs/robot/lekce3/project3/@types/timestamp.d.ts b/docs/robot/lekce3/project3/@types/timestamp.d.ts new file mode 100644 index 00000000..bd9ab6c8 --- /dev/null +++ b/docs/robot/lekce3/project3/@types/timestamp.d.ts @@ -0,0 +1,11 @@ +declare interface Timestamp { + /** + * Convert the timestamp to milliseconds. + */ + millis(): number; + + /** + * Get the lower 10^3 part of the timestamp in microseconds. + */ + micros(): number; +} diff --git a/docs/robot/lekce3/project3/@types/wifi.d.ts b/docs/robot/lekce3/project3/@types/wifi.d.ts new file mode 100644 index 00000000..7e532194 --- /dev/null +++ b/docs/robot/lekce3/project3/@types/wifi.d.ts @@ -0,0 +1,6 @@ +declare module "wifi" { + /** + * Return current IPv4 of the device, or null if WiFi is disabled or not connected. + */ + function currentIp(): string | null; +} diff --git a/docs/robot/lekce3/project3/src/index.ts b/docs/robot/lekce3/project3/src/index.ts index fbea5f60..af790de1 100644 --- a/docs/robot/lekce3/project3/src/index.ts +++ b/docs/robot/lekce3/project3/src/index.ts @@ -1,4 +1,4 @@ -import { SmartLed, LED_WS2812 } from "smartled"; -import * as colors from "./libs/colors.js" - +import * as Robutek from "./libs/robutek.js" +import * as colors from "./libs/colors.js" + // Tady si napište své řešení \ No newline at end of file diff --git a/docs/robot/lekce3/project3/src/libs/colors.ts b/docs/robot/lekce3/project3/src/libs/colors.ts index 950adfbe..de76a121 100644 --- a/docs/robot/lekce3/project3/src/libs/colors.ts +++ b/docs/robot/lekce3/project3/src/libs/colors.ts @@ -1,81 +1,81 @@ -/** - * Barva je jednoduchá trojice červené, zelené, a modré složky - * - R: červená (rozsah 0-255) - * - G: zelená (rozsah 0-255) - * - B: modrá (rozsah 0-255) - */ -interface Rgb { - r: number; - g: number; - b: number; -} - - -/** - * Alternativní způsob, jak vyjádřit barvu, je HSL: - * - Hue: odstín (rozsah 0-360) - * - Saturation: sytost barev (rozsah 0-1) - * - Lightness: světlost (rozsah 0-1) - */ -interface Hsl { - h: number; - s: number; - l: number; -} - -/** - * Mezi jednotlivými reprezentacemi lze převádět - * @param hsl {Number} Hue (0-360), Saturation (0-1), Lightness (0-1) - * @returns {Rgb} Red (0-255), Green (0-255), Blue (0-255) - */ -export function hsl_to_rbg( hsl: Hsl ) : Rgb { - const chroma = ( 1 - Math.abs( 2 * hsl.l - 1 ) * hsl.s ); - const hue = hsl.h / 60; - const x = chroma * ( 1 - Math.abs( ( hue % 2 ) - 1 ) ); - - let color : Rgb = { r: 0, g: 0, b: 0 }; - if( hue > 0 && hue < 1 ){ - color = { r: chroma, g: x, b: 0 }; - } else if( hue >= 1 && hue < 2 ){ - color = { r: x, g: chroma, b: 0 }; - } else if( hue >= 2 && hue < 3 ){ - color = { r: 0, g: chroma, b: x }; - } else if( hue >= 3 && hue < 4 ){ - color = { r: 0, g: x, b: chroma }; - } else if( hue >= 4 && hue < 5 ){ - color = { r: x, g: 0, b: chroma }; - } else { - color = { r: chroma, g: 0, b: x }; - } - const correction = hsl.l - chroma / 2; - color.r = ( color.r + correction ) * 255; - color.g = ( color.g + correction ) * 255; - color.b = ( color.b + correction ) * 255; - - return color; -} - -/** - * Funkce rainbow zafixuje sytost a světlost, a prochází barvami - * @param hue (0-360) - * @param brightness (0-100) - 50 je defaultní hodnota - * @returns {Rgb} - */ -export function rainbow( hue: number, brightness: number = 20) : Rgb { - hue = Math.min( hue, 360 ); // Zajistíme, že zadaná hodnota není mimo rozsah - // fix range to 0-100 - let brightness_mapped = Math.min(Math.max(brightness, 0), 100); - return hsl_to_rbg( { h: hue, s: 1, l: brightness_mapped / 100 } ); -} - -/* Základní barvy pro LED pásky*/ -export const red = rainbow( 0 ); -export const orange = rainbow( 27 ); -export const yellow = rainbow( 54 ); -export const green = rainbow( 110 ); -export const light_blue = rainbow( 177 ); -export const blue = rainbow( 240 ); -export const purple = rainbow( 285 ); -export const pink = rainbow( 323 ); -export const white : Rgb = { r: 100, g: 100, b: 100 }; +/** + * Barva je jednoduchá trojice červené, zelené, a modré složky + * - R: červená (rozsah 0-255) + * - G: zelená (rozsah 0-255) + * - B: modrá (rozsah 0-255) + */ +interface Rgb { + r: number; + g: number; + b: number; +} + + +/** + * Alternativní způsob, jak vyjádřit barvu, je HSL: + * - Hue: odstín (rozsah 0-360) + * - Saturation: sytost barev (rozsah 0-1) + * - Lightness: světlost (rozsah 0-1) + */ +interface Hsl { + h: number; + s: number; + l: number; +} + +/** + * Mezi jednotlivými reprezentacemi lze převádět + * @param hsl {Number} Hue (0-360), Saturation (0-1), Lightness (0-1) + * @returns {Rgb} Red (0-255), Green (0-255), Blue (0-255) + */ +export function hsl_to_rbg( hsl: Hsl ) : Rgb { + const chroma = ( 1 - Math.abs( 2 * hsl.l - 1 ) * hsl.s ); + const hue = hsl.h / 60; + const x = chroma * ( 1 - Math.abs( ( hue % 2 ) - 1 ) ); + + let color : Rgb = { r: 0, g: 0, b: 0 }; + if( hue > 0 && hue < 1 ){ + color = { r: chroma, g: x, b: 0 }; + } else if( hue >= 1 && hue < 2 ){ + color = { r: x, g: chroma, b: 0 }; + } else if( hue >= 2 && hue < 3 ){ + color = { r: 0, g: chroma, b: x }; + } else if( hue >= 3 && hue < 4 ){ + color = { r: 0, g: x, b: chroma }; + } else if( hue >= 4 && hue < 5 ){ + color = { r: x, g: 0, b: chroma }; + } else { + color = { r: chroma, g: 0, b: x }; + } + const correction = hsl.l - chroma / 2; + color.r = ( color.r + correction ) * 255; + color.g = ( color.g + correction ) * 255; + color.b = ( color.b + correction ) * 255; + + return color; +} + +/** + * Funkce rainbow zafixuje sytost a světlost, a prochází barvami + * @param hue (0-360) + * @param brightness (0-100) - 50 je defaultní hodnota + * @returns {Rgb} + */ +export function rainbow( hue: number, brightness: number = 50) : Rgb { + hue = Math.min( hue, 360 ); // Zajistíme, že zadaná hodnota není mimo rozsah + // fix range to 0-100 + let brightness_mapped = Math.min(Math.max(brightness, 0), 100); + return hsl_to_rbg( { h: hue, s: 1, l: brightness_mapped / 100 } ); +} + +/* Základní barvy pro LED pásky*/ +export const red = rainbow( 0 ); +export const orange = rainbow( 27 ); +export const yellow = rainbow( 54 ); +export const green = rainbow( 110 ); +export const light_blue = rainbow( 177 ); +export const blue = rainbow( 240 ); +export const purple = rainbow( 285 ); +export const pink = rainbow( 323 ); +export const white : Rgb = { r: 100, g: 100, b: 100 }; export const off : Rgb = { r: 0, g: 0, b: 0 }; \ No newline at end of file diff --git a/docs/robot/lekce3/project3/src/libs/readline.ts b/docs/robot/lekce3/project3/src/libs/readline.ts new file mode 100644 index 00000000..4372efd2 --- /dev/null +++ b/docs/robot/lekce3/project3/src/libs/readline.ts @@ -0,0 +1,84 @@ +import { stdout, stdin } from "stdio"; + +/** + * A class for reading standard input line by line. + */ + +export class readline { + private buffer: string = ""; + private promise: Promise | null = null; + private resolve: ((value: string) => void) | null = null; + private reject: ((reason: any) => void) | null = null; + private closed: boolean = false; + private echo: boolean; + + private onGet(str: string) { + if (this.echo) { + stdout.write(str); + } + + if (str == "\n") { + if (this.resolve) { + this.resolve(this.buffer); + } + + this.buffer = ""; + this.promise = null; + this.resolve = null; + this.reject = null; + + return; + } + + this.buffer += str; + + if (!this.closed) { + stdin.get() + .then((data) => this.onGet(data)) + .catch((reason) => { + if (this.reject) { + this.reject(reason); + } + }); + } + + } + + constructor(echo: boolean = false) { + this.echo = echo; + } + + public read(): Promise { + if (this.promise != null) { + return Promise.reject("Already reading"); + } + + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + + stdin.get() + .then((data) => this.onGet(data)) + .catch((reason) => { + if (this.reject) { + this.reject(reason); + } + }); + + return this.promise; + } + + public close() { + this.closed = true; + + if (this.reject) { + this.reject("Stopped"); + } + + this.buffer = ""; + this.promise = null; + this.resolve = null; + this.reject = null; + } +} diff --git a/docs/robot/lekce3/project3/src/libs/robutek.ts b/docs/robot/lekce3/project3/src/libs/robutek.ts new file mode 100644 index 00000000..5a143f46 --- /dev/null +++ b/docs/robot/lekce3/project3/src/libs/robutek.ts @@ -0,0 +1,134 @@ + +import * as adc from "adc"; +import * as gpio from "gpio"; + +import { Servo } from "./servo.js"; + +import { SmartLed, LED_WS2812 } from "smartled"; + +import * as motors from "motor" +import * as ledc from "ledc"; + +ledc.configureTimer(0, 64000, 10); + +const leftMotorPins: motors.MotorPins = { motA: 11, motB: 12, encA: 39, encB: 40 } +const rightMotorPins: motors.MotorPins = { motA: 45, motB: 13, encA: 42, encB: 41 } + +const leftMotorLedc: motors.LedcConfig = { timer: 0, channelA: 0, channelB: 1 } +const rightMotorLedc: motors.LedcConfig = { timer: 0, channelA: 2, channelB: 3 } + +export const LeftMot = new motors.Motor({ pins: leftMotorPins, ledc: leftMotorLedc, encTicks: 406, diameter: 34 }); +export const RightMot = new motors.Motor({ pins: rightMotorPins, ledc: rightMotorLedc, encTicks: 406, diameter: 34 }); + + +export function init() { + adc.configure(LineSensors.S_1, adc.Attenuation.Db0); + adc.configure(LineSensors.S_2, adc.Attenuation.Db0); + adc.configure(LineSensors.S_3, adc.Attenuation.Db0); + adc.configure(LineSensors.S_4, adc.Attenuation.Db0); + + gpio.pinMode(LineSensors.S_PWR, gpio.PinMode.OUTPUT); + gpio.pinMode(LineSensors.S_SW, gpio.PinMode.OUTPUT); + + gpio.write(LineSensors.S_PWR, 1); +} + +export type SensorType = 'W_FR' | 'W_FL' | 'W_BL' | 'W_BR' | 'L_FR' | 'L_FL' | 'L_BL' | 'L_BR'; +export class LineSensors { + + public static readonly S_1: number = 4; + public static readonly S_2: number = 5; + public static readonly S_3: number = 6; + public static readonly S_4: number = 7; + public static readonly S_SW: number = 8; + public static readonly S_PWR: number = 47; + + private sw: number = 0; + + + private async switch_sensors(to_value: number) { + if (to_value == this.sw) { + return; + } + this.sw = to_value; + gpio.write(LineSensors.S_SW, to_value); + await sleep(5); + } + + public async read(sensor: SensorType): Promise { + switch (sensor) { + case 'W_FR': + this.switch_sensors(0); + return adc.read(LineSensors.S_1); + + case 'W_FL': + this.switch_sensors(0); + return adc.read(LineSensors.S_2); + + case 'W_BL': + this.switch_sensors(0); + return adc.read(LineSensors.S_3); + + case 'W_BR': + this.switch_sensors(0); + return adc.read(LineSensors.S_4); + + + case 'L_FR': + this.switch_sensors(1); + return adc.read(LineSensors.S_1); + + case 'L_FL': + this.switch_sensors(1); + return adc.read(LineSensors.S_2); + + case 'L_BL': + this.switch_sensors(1); + return adc.read(LineSensors.S_3); + + case 'L_BR': + this.switch_sensors(1); + return adc.read(LineSensors.S_4); + + default: + return 0; + + } + } +} + +export class Pen { + private servo: Servo; + + public static readonly UP = 512 + 180; + public static readonly DOWN = 512 - 180; + public static readonly MIDDLE = 512; + public static readonly UNLOAD = 0; + + constructor(pin: number) { + this.servo = new Servo(pin, 1, 0); + } + + /** + * Set the pen servo position. + * @param value The position to set the servo to, from 0 to 1023. + */ + public move(value: number) { + this.servo.write(value); + } +} + +export const ledStrip = new SmartLed(48, 9, LED_WS2812); + +export class Pins { + public static readonly Status_LED: number = 46; + + public static readonly Motor1_A = 11; + public static readonly Motor1_B = 12; + public static readonly Motor2_A = 45; + public static readonly Motor2_B = 13; + public static readonly ENC1_1 = 39; + public static readonly ENC1_2 = 40; + public static readonly ENC2_1 = 41; + public static readonly ENC2_2 = 42; +} \ No newline at end of file diff --git a/docs/robot/lekce3/project3/src/libs/servo.ts b/docs/robot/lekce3/project3/src/libs/servo.ts new file mode 100644 index 00000000..ac4fe3ee --- /dev/null +++ b/docs/robot/lekce3/project3/src/libs/servo.ts @@ -0,0 +1,34 @@ +import * as ledc from "ledc"; + +/** + * A class for controlling a servo motor. + */ +export class Servo { + private channel: number; // the PWM channel to use + + /** + * Create a new servo object. + * @param pin The pin the servo is connected to. + * @param timer The timer to use for PWM. + * @param channel The channel to use for PWM. + */ + constructor(pin: number, timer: number, channel: number) { + this.channel = channel; + ledc.configureTimer(timer, 50, 12); // 50Hz is the frequency of a servo, 12 is the resolution of the timer + ledc.configureChannel(channel, pin, timer, 1023); // 1023 is the resolution of a servo + } + + /** + * Set the servo position. + * @param value The position to set the servo to, from 0-1023. + */ + write(value: number) { + // map the value from 0-1023 to 0.5-2.5ms + const ms = ((value / 1023) * 2) + 0.5; // 0.5-2.5ms is the range of a servo + + // convert to a duty cycle + const duty = (ms / 20) * 1023; // 20ms is the period of a servo + + ledc.setDuty(this.channel, duty); // set the duty cycle to the servo + } +} diff --git a/docs/robot/lekce3/project3/src/libs/simplemotors.ts b/docs/robot/lekce3/project3/src/libs/simplemotors.ts new file mode 100644 index 00000000..c6f424cd --- /dev/null +++ b/docs/robot/lekce3/project3/src/libs/simplemotors.ts @@ -0,0 +1,47 @@ +import * as ledc from "ledc"; + +/** + * A class for controlling a servo motor. + */ +export class Motor { + private channel1: number; // the PWM channel to use + private channel2: number; // the PWM channel to use + + /** + * Create a new servo object. + * @param pin1 The pin the servo is connected to. + * @param pin2 The pin the servo is connected to. + * @param timer The timer to use for PWM. + * @param channel1 The channel to use for PWM. + * @param channel2 The channel to use for PWM. + */ + + constructor(pin1: number, pin2: number, timer: number, channel1: number, channel2: number) { + this.channel1 = channel1; + this.channel2 = channel2; + ledc.configureTimer(timer, 50, 12); // 50Hz is the frequency of a servo, 12 is the resolution of the timer + ledc.configureChannel(channel1, pin1, timer, 1023); // 1023 is the resolution of a servo + ledc.configureChannel(channel2, pin2, timer, 1023); // 1023 is the resolution of a servo + } + + /** + * Set the servo position. + * @param value The position to set the servo to, from -1 to 1. + */ + write(value: number) { + console.log(value) + + if (value > 0) { + ledc.setDuty(this.channel1, 1); + ledc.setDuty(this.channel2, (Math.abs(value) * 1023)); + } + else if (value < 0) { + ledc.setDuty(this.channel1, (Math.abs(value) * 1023)); + ledc.setDuty(this.channel2, 1); + } + else { + ledc.setDuty(this.channel1, 1); + ledc.setDuty(this.channel2, 1); + } + } +} diff --git a/docs/robot/lekce3/project3/tsconfig.json b/docs/robot/lekce3/project3/tsconfig.json index 98564f26..8cf82b10 100644 --- a/docs/robot/lekce3/project3/tsconfig.json +++ b/docs/robot/lekce3/project3/tsconfig.json @@ -1,11 +1,11 @@ -{ - "compilerOptions": { - "target": "es2020", - "module": "es2020", - "lib": ["es2020"], - "moduleResolution": "node", - "sourceMap": false, - "outDir": "build", - "rootDir": "src", - } -} +{ + "compilerOptions": { + "target": "es2020", + "module": "es2020", + "lib": ["es2020"], + "moduleResolution": "node", + "sourceMap": false, + "outDir": "build", + "rootDir": "src", + } +} diff --git a/docs/robot/lekce4/project4/@types/adc.d.ts b/docs/robot/lekce4/project4/@types/adc.d.ts index d77e9d94..871de201 100644 --- a/docs/robot/lekce4/project4/@types/adc.d.ts +++ b/docs/robot/lekce4/project4/@types/adc.d.ts @@ -1,9 +1,19 @@ declare module "adc" { + type Atten = number; + + const Attenuation: { + readonly Db0: Atten; + readonly Db2_5: Atten; + readonly Db6: Atten; + readonly Db11: Atten; + }; + + /** * Enable ADC on the given pin. * @param pin The pin to enable ADC on. */ - function configure(pin: number): void; + function configure(pin: number, attenuation?: Atten): void; /** * Read the value of the given pin. diff --git a/docs/robot/lekce4/project4/@types/gpio.d.ts b/docs/robot/lekce4/project4/@types/gpio.d.ts index 9c74419e..09c167db 100644 --- a/docs/robot/lekce4/project4/@types/gpio.d.ts +++ b/docs/robot/lekce4/project4/@types/gpio.d.ts @@ -1,12 +1,16 @@ declare module "gpio" { const PinMode: { - DISABLE: number, - OUTPUT: number, - INPUT: number, - INPUT_PULLUP: number, - INPUT_PULLDOWN: number, + readonly DISABLE: number, + readonly OUTPUT: number, + readonly INPUT: number, + readonly INPUT_PULLUP: number, + readonly INPUT_PULLDOWN: number, }; + interface EventInfo { + timestamp: Timestamp; + } + /** * Configure the given pin. * @param pin The pin to configure. @@ -34,7 +38,7 @@ declare module "gpio" { * @param pin The pin to handle the event for. * @param callback The callback to call when the event occurs. */ - function on(event: "rising" | "falling" | "change", pin: number, callback: () => void): void; + function on(event: "rising" | "falling" | "change", pin: number, callback: (info: EventInfo) => void): void; /** * Remove event handler for the given pin. diff --git a/docs/robot/lekce4/project4/@types/gridui.d.ts b/docs/robot/lekce4/project4/@types/gridui.d.ts new file mode 100644 index 00000000..8ffaba74 --- /dev/null +++ b/docs/robot/lekce4/project4/@types/gridui.d.ts @@ -0,0 +1,321 @@ +declare module "gridui" { + namespace widget { + interface Base { + readonly uuid: number + widgetX: number + widgetY: number + widgetW: number + widgetH: number + widgetTab: number + css(key: string): string + setCss(key: string, value: string): void + } + + interface Arm extends Base { + readonly x: number + readonly y: number + } + + interface Bar extends Base { + color: string + fontSize: number + min: number + max: number + value: number + showValue: boolean + } + + interface Button extends Base { + text: string + fontSize: number + color: string + background: string + align: string + valign: string + disabled: boolean + readonly pressed: boolean + } + + interface Camera extends Base { + rotation: number + clip: boolean + } + + interface Checkbox extends Base { + fontSize: number + checked: boolean + color: string + text: string + } + + interface Circle extends Base { + color: string + fontSize: number + min: number + max: number + lineWidth: number + valueStart: number + value: number + showValue: boolean + } + + interface Input extends Base { + text: string + color: string + type: string + disabled: boolean + } + + interface Joystick extends Base { + color: string + keys: string + text: string + readonly x: number + readonly y: number + } + + interface Led extends Base { + color: string + on: boolean + } + + interface Orientation extends Base { + color: string + readonly yaw: number + readonly pitch: number + readonly roll: number + readonly joystickX: number + readonly joystickY: number + } + + interface Select extends Base { + color: string + background: string + disabled: boolean + options: string + selectedIndex: number + } + + interface Slider extends Base { + color: string + fontSize: number + min: number + max: number + value: number + precision: number + showValue: boolean + } + + interface SpinEdit extends Base { + fontSize: number + color: string + value: number + step: number + precision: number + } + + interface Switcher extends Base { + fontSize: number + color: string + value: number + min: number + max: number + } + + interface Text extends Base { + text: string + fontSize: number + color: string + background: string + align: string + valign: string + prefix: string + suffix: string + } + } + + namespace builder { + interface Base { + css(key: string, value: string): this + finish(): this + } + + interface Arm extends Base { + info(info: Record): Arm + + onGrab(callback: (arm: widget.Arm) => void): Arm + onPositionChanged(callback: (arm: widget.Arm) => void): Arm + } + + interface Bar extends Base { + color(color: string): Bar + fontSize(fontSize: number): Bar + min(min: number): Bar + max(max: number): Bar + value(value: number): Bar + showValue(showValue: boolean): Bar + } + + interface Button extends Base { + text(text: string): Button + fontSize(fontSize: number): Button + color(color: string): Button + background(background: string): Button + align(align: string): Button + valign(valign: string): Button + disabled(disabled: boolean): Button + + onPress(callback: (button: widget.Button) => void): Button + onRelease(callback: (button: widget.Button) => void): Button + } + + interface Camera extends Base { + rotation(rotation: number): Camera + clip(clip: boolean): Camera + tags(tags: any /* TODO: fix type */): Camera + } + + interface Checkbox extends Base { + fontSize(fontSize: number): Checkbox + checked(checked: boolean): Checkbox + color(color: string): Checkbox + text(text: string): Checkbox + + onChanged(callback: (checkbox: widget.Checkbox) => void): Checkbox + } + + interface Circle extends Base { + color(color: string): Circle + fontSize(fontSize: number): Circle + min(min: number): Circle + max(max: number): Circle + lineWidth(lineWidth: number): Circle + valueStart(valueStart: number): Circle + value(value: number): Circle + showValue(showValue: boolean): Circle + } + + interface Input extends Base { + text(text: string): Input + color(color: string): Input + type(type: string): Input + disabled(disabled: boolean): Input + + onChanged(callback: (input: widget.Input) => void): Input + } + + interface Joystick extends Base { + color(color: string): Joystick + keys(keys: string): Joystick + text(text: string): Joystick + + onClick(callback: (joystick: widget.Joystick) => void): Joystick + onPositionChanged(callback: (joystick: widget.Joystick) => void): Joystick + } + + interface Led extends Base { + color(color: string): Led + on(on: boolean): Led + } + + interface Orientation extends Base { + color(color: string): Orientation + + onPositionChanged(callback: (orientation: widget.Orientation) => void): Orientation + } + + interface Select extends Base { + color(color: string): Select + background(background: string): Select + disabled(disabled: boolean): Select + options(options: string): Select + selectedIndex(selectedIndex: number): Select + + onChanged(callback: (select: widget.Select) => void): Select + } + + interface Slider extends Base { + color(color: string): Slider + fontSize(fontSize: number): Slider + min(min: number): Slider + max(max: number): Slider + value(value: number): Slider + precision(precision: number): Slider + showValue(showValue: boolean): Slider + + onChanged(callback: (slider: widget.Slider) => void): Slider + } + + interface SpinEdit extends Base { + fontSize(fontSize: number): SpinEdit + color(color: string): SpinEdit + value(value: number): SpinEdit + step(step: number): SpinEdit + precision(precision: number): SpinEdit + + onChanged(callback: (spinEdit: widget.SpinEdit) => void): SpinEdit + } + + interface Switcher extends Base { + fontSize(fontSize: number): Switcher + color(color: string): Switcher + value(value: number): Switcher + min(min: number): Switcher + max(max: number): Switcher + + onChanged(callback: (switcher: widget.Switcher) => void): Switcher + } + + interface Text extends Base { + text(text: string): Text + fontSize(fontSize: number): Text + color(color: string): Text + background(background: string): Text + align(align: string): Text + valign(valign: string): Text + prefix(prefix: string): Text + suffix(suffix: string): Text + } + + interface Widget extends Base {} + } + + class Builder { + arm(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Arm + bar(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Bar + button(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Button + camera(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Camera + checkbox(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Checkbox + circle(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Circle + input(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Input + joystick(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Joystick + led(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Led + orientation(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Orientation + select(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Select + slider(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Slider + spinEdit(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.SpinEdit + switcher(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Switcher + text(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Text + widget(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Widget + } + + /** + * Initialize GridUI. + * @param ownerName name of the owner, must match the name entered in RBController app. + * @param deviceName name of this device, visible in the RBController app. + * @param builderCallback callback, which receives the builder instance that can be used to create widgets. + */ + function begin(ownerName: string, deviceName: string, builderCallback: (builder: Builder) => void): void + + /** + * Stop GridUI. + */ + function end(): void + + /** + * Returns included GridUI version as number, to be compared with hex representation of the version. + * + * For example, for version 5.1.0, do: `gridui.version() >= 0x050100` + */ + function version(): number +} diff --git a/docs/robot/lekce4/project4/@types/keyvalue.d.ts b/docs/robot/lekce4/project4/@types/keyvalue.d.ts new file mode 100644 index 00000000..761b09fb --- /dev/null +++ b/docs/robot/lekce4/project4/@types/keyvalue.d.ts @@ -0,0 +1,61 @@ +declare module "keyvalue" { + class KeyValueNamespace { + /** + * Get saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present + */ + get(key: string): string | number | null; + + /** + * Get string saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present or if not string + */ + getString(key: string): string | null; + + /** + * Get number saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present or if not number + */ + getNumber(key: string): number | null; + + /** + * Set value in KeyValue namespace, overwriting any previous value. + * Call commit() after setting everything, otherwise changes will be lost! + * @param key key, max 15 characters. + * @param value value + */ + set(key: string, value: string | number): void; + + /** + * Erase value from KeyValue namespace. + * Call commit() after setting everything, otherwise changes will be lost! + * @param key key, max 15 characters. + */ + erase(key: string): void; + + /** + * Check existance of a key. + * @param key key, max 15 characters. + * @returns true if exists, false otherwise + */ + exists(key: string): boolean; + + /** + * Save modifications to persistent storage. + */ + commit(): void; + } + + /** + * Open a KeyValue namespace for use. KeyValue is a persitent storage for small + * pieces of data that saves values across restarts. + * + * The namespaces are isolated from each other. + * + * @param namespace Namespace name, max 15 characters. + */ + function open(namespace: string): KeyValueNamespace; +} diff --git a/docs/robot/lekce4/project4/@types/motor.d.ts b/docs/robot/lekce4/project4/@types/motor.d.ts new file mode 100644 index 00000000..2ec31392 --- /dev/null +++ b/docs/robot/lekce4/project4/@types/motor.d.ts @@ -0,0 +1,82 @@ +declare module "motor" { + + type MoveDuration = { + distance?: number; + time?: number; + } + + type MotorPins = { + motA: number; + motB: number; + encA: number; + encB: number; + }; + + type LedcConfig = { + timer: number; + channelA: number; + channelB: number; + }; + + class Motor { + /** + * Construc a new Motor instance + * @param options Motor configuration + * @note Units used in the diameter parameter determines the units used in the other methods + */ + constructor(options: { pins: MotorPins, ledc: LedcConfig, encTicks: number, diameter: number }); + + /** + * Set the speed of the motor + * @param speed Speed in units per second + * @note The units are the same as used in the diameter parameter + */ + setSpeed(speed: number): void; + // setRamp(ramp: number): void; + + /** + * Move the motor + * @param duration Duration of the movement + * @note The units are the same as used in the diameter parameter + * @note If the duration is not provided, the motor will move indefinitely + */ + move(duration?: MoveDuration): Promise; + + /** + * Stop the motor + * @param brake If true, the motor will brake, otherwise it will coast to a stop + */ + stop(brake?: boolean): Promise; + + /** + * Get the position of the motor + * @note The units are the same as used in the diameter parameter + * @returns The position of the motor + */ + getPosition(): number; + + /** + * Close the motor + */ + close(): void; + } + + /* class Wheels { + constructor(options: { + left: MotorPins, + right: MotorPins, + encTicks: number, + diameter: number, + spacing: number + }); + setSpeed(speed: number): void; + // setRamp(ramp: number): void; + + move(curve: number, duration?: MoveDuration): Promise; + // rotate(angle: number): Promise; + stop(brake?: boolean): Promise; + getPosition(): { left: number, right: number }; + + close(): void; + } */ +} diff --git a/docs/robot/lekce4/project4/@types/pulseCounter.d.ts b/docs/robot/lekce4/project4/@types/pulseCounter.d.ts new file mode 100644 index 00000000..76ad8edd --- /dev/null +++ b/docs/robot/lekce4/project4/@types/pulseCounter.d.ts @@ -0,0 +1,64 @@ +declare module "pulseCounter" { + type LevelAction = number; + type EdgeAction = number; + + const LevelAction: { + Keep: LevelAction; + Inverse: LevelAction; + Hold: LevelAction; + }; + + const EdgeAction: { + Hold: EdgeAction; + Increase: EdgeAction; + Decrease: EdgeAction; + }; + + type LevelMode = { + low: LevelAction, + high: LevelAction, + } + + type EdgeMode = { + pos: EdgeAction, + neg: EdgeAction, + } + + class PulseCounter { + /** + * Create a new pulse counter instance. + * @param options The options for the pulse counter. + */ + constructor(options: { + pinLevel: number, + pinEdge: number, + levelMode: LevelMode, + edgeMode: EdgeMode, + }) + + /** + * Read the current value of the pulse counter. + */ + read(): number; + + /** + * Reset the pulse counter to 0. + */ + clear(): void; + + /** + * Start the pulse counter. + */ + start(): void; + + /** + * Stop the pulse counter. + */ + stop(): void; + + /** + * Close the pulse counter. + */ + close(): void; + } +} diff --git a/docs/robot/lekce4/project4/@types/timestamp.d.ts b/docs/robot/lekce4/project4/@types/timestamp.d.ts new file mode 100644 index 00000000..bd9ab6c8 --- /dev/null +++ b/docs/robot/lekce4/project4/@types/timestamp.d.ts @@ -0,0 +1,11 @@ +declare interface Timestamp { + /** + * Convert the timestamp to milliseconds. + */ + millis(): number; + + /** + * Get the lower 10^3 part of the timestamp in microseconds. + */ + micros(): number; +} diff --git a/docs/robot/lekce4/project4/@types/wifi.d.ts b/docs/robot/lekce4/project4/@types/wifi.d.ts new file mode 100644 index 00000000..7e532194 --- /dev/null +++ b/docs/robot/lekce4/project4/@types/wifi.d.ts @@ -0,0 +1,6 @@ +declare module "wifi" { + /** + * Return current IPv4 of the device, or null if WiFi is disabled or not connected. + */ + function currentIp(): string | null; +} diff --git a/docs/robot/lekce4/project4/src/index.ts b/docs/robot/lekce4/project4/src/index.ts index 334dcd9b..17e83e36 100644 --- a/docs/robot/lekce4/project4/src/index.ts +++ b/docs/robot/lekce4/project4/src/index.ts @@ -1,6 +1,5 @@ -import { SmartLed, LED_WS2812 } from "smartled"; -import * as colors from "./libs/colors.js" -import * as gpio from "gpio"; -import { stdout } from "stdio"; - +import * as Robutek from "./libs/robutek.js" +import * as colors from "./libs/colors.js" +import { stdout } from "stdio" + // Tady si napište své řešení \ No newline at end of file diff --git a/docs/robot/lekce4/project4/src/libs/colors.ts b/docs/robot/lekce4/project4/src/libs/colors.ts index 950adfbe..de76a121 100644 --- a/docs/robot/lekce4/project4/src/libs/colors.ts +++ b/docs/robot/lekce4/project4/src/libs/colors.ts @@ -1,81 +1,81 @@ -/** - * Barva je jednoduchá trojice červené, zelené, a modré složky - * - R: červená (rozsah 0-255) - * - G: zelená (rozsah 0-255) - * - B: modrá (rozsah 0-255) - */ -interface Rgb { - r: number; - g: number; - b: number; -} - - -/** - * Alternativní způsob, jak vyjádřit barvu, je HSL: - * - Hue: odstín (rozsah 0-360) - * - Saturation: sytost barev (rozsah 0-1) - * - Lightness: světlost (rozsah 0-1) - */ -interface Hsl { - h: number; - s: number; - l: number; -} - -/** - * Mezi jednotlivými reprezentacemi lze převádět - * @param hsl {Number} Hue (0-360), Saturation (0-1), Lightness (0-1) - * @returns {Rgb} Red (0-255), Green (0-255), Blue (0-255) - */ -export function hsl_to_rbg( hsl: Hsl ) : Rgb { - const chroma = ( 1 - Math.abs( 2 * hsl.l - 1 ) * hsl.s ); - const hue = hsl.h / 60; - const x = chroma * ( 1 - Math.abs( ( hue % 2 ) - 1 ) ); - - let color : Rgb = { r: 0, g: 0, b: 0 }; - if( hue > 0 && hue < 1 ){ - color = { r: chroma, g: x, b: 0 }; - } else if( hue >= 1 && hue < 2 ){ - color = { r: x, g: chroma, b: 0 }; - } else if( hue >= 2 && hue < 3 ){ - color = { r: 0, g: chroma, b: x }; - } else if( hue >= 3 && hue < 4 ){ - color = { r: 0, g: x, b: chroma }; - } else if( hue >= 4 && hue < 5 ){ - color = { r: x, g: 0, b: chroma }; - } else { - color = { r: chroma, g: 0, b: x }; - } - const correction = hsl.l - chroma / 2; - color.r = ( color.r + correction ) * 255; - color.g = ( color.g + correction ) * 255; - color.b = ( color.b + correction ) * 255; - - return color; -} - -/** - * Funkce rainbow zafixuje sytost a světlost, a prochází barvami - * @param hue (0-360) - * @param brightness (0-100) - 50 je defaultní hodnota - * @returns {Rgb} - */ -export function rainbow( hue: number, brightness: number = 20) : Rgb { - hue = Math.min( hue, 360 ); // Zajistíme, že zadaná hodnota není mimo rozsah - // fix range to 0-100 - let brightness_mapped = Math.min(Math.max(brightness, 0), 100); - return hsl_to_rbg( { h: hue, s: 1, l: brightness_mapped / 100 } ); -} - -/* Základní barvy pro LED pásky*/ -export const red = rainbow( 0 ); -export const orange = rainbow( 27 ); -export const yellow = rainbow( 54 ); -export const green = rainbow( 110 ); -export const light_blue = rainbow( 177 ); -export const blue = rainbow( 240 ); -export const purple = rainbow( 285 ); -export const pink = rainbow( 323 ); -export const white : Rgb = { r: 100, g: 100, b: 100 }; +/** + * Barva je jednoduchá trojice červené, zelené, a modré složky + * - R: červená (rozsah 0-255) + * - G: zelená (rozsah 0-255) + * - B: modrá (rozsah 0-255) + */ +interface Rgb { + r: number; + g: number; + b: number; +} + + +/** + * Alternativní způsob, jak vyjádřit barvu, je HSL: + * - Hue: odstín (rozsah 0-360) + * - Saturation: sytost barev (rozsah 0-1) + * - Lightness: světlost (rozsah 0-1) + */ +interface Hsl { + h: number; + s: number; + l: number; +} + +/** + * Mezi jednotlivými reprezentacemi lze převádět + * @param hsl {Number} Hue (0-360), Saturation (0-1), Lightness (0-1) + * @returns {Rgb} Red (0-255), Green (0-255), Blue (0-255) + */ +export function hsl_to_rbg( hsl: Hsl ) : Rgb { + const chroma = ( 1 - Math.abs( 2 * hsl.l - 1 ) * hsl.s ); + const hue = hsl.h / 60; + const x = chroma * ( 1 - Math.abs( ( hue % 2 ) - 1 ) ); + + let color : Rgb = { r: 0, g: 0, b: 0 }; + if( hue > 0 && hue < 1 ){ + color = { r: chroma, g: x, b: 0 }; + } else if( hue >= 1 && hue < 2 ){ + color = { r: x, g: chroma, b: 0 }; + } else if( hue >= 2 && hue < 3 ){ + color = { r: 0, g: chroma, b: x }; + } else if( hue >= 3 && hue < 4 ){ + color = { r: 0, g: x, b: chroma }; + } else if( hue >= 4 && hue < 5 ){ + color = { r: x, g: 0, b: chroma }; + } else { + color = { r: chroma, g: 0, b: x }; + } + const correction = hsl.l - chroma / 2; + color.r = ( color.r + correction ) * 255; + color.g = ( color.g + correction ) * 255; + color.b = ( color.b + correction ) * 255; + + return color; +} + +/** + * Funkce rainbow zafixuje sytost a světlost, a prochází barvami + * @param hue (0-360) + * @param brightness (0-100) - 50 je defaultní hodnota + * @returns {Rgb} + */ +export function rainbow( hue: number, brightness: number = 50) : Rgb { + hue = Math.min( hue, 360 ); // Zajistíme, že zadaná hodnota není mimo rozsah + // fix range to 0-100 + let brightness_mapped = Math.min(Math.max(brightness, 0), 100); + return hsl_to_rbg( { h: hue, s: 1, l: brightness_mapped / 100 } ); +} + +/* Základní barvy pro LED pásky*/ +export const red = rainbow( 0 ); +export const orange = rainbow( 27 ); +export const yellow = rainbow( 54 ); +export const green = rainbow( 110 ); +export const light_blue = rainbow( 177 ); +export const blue = rainbow( 240 ); +export const purple = rainbow( 285 ); +export const pink = rainbow( 323 ); +export const white : Rgb = { r: 100, g: 100, b: 100 }; export const off : Rgb = { r: 0, g: 0, b: 0 }; \ No newline at end of file diff --git a/docs/robot/lekce4/project4/src/libs/readline.ts b/docs/robot/lekce4/project4/src/libs/readline.ts new file mode 100644 index 00000000..4372efd2 --- /dev/null +++ b/docs/robot/lekce4/project4/src/libs/readline.ts @@ -0,0 +1,84 @@ +import { stdout, stdin } from "stdio"; + +/** + * A class for reading standard input line by line. + */ + +export class readline { + private buffer: string = ""; + private promise: Promise | null = null; + private resolve: ((value: string) => void) | null = null; + private reject: ((reason: any) => void) | null = null; + private closed: boolean = false; + private echo: boolean; + + private onGet(str: string) { + if (this.echo) { + stdout.write(str); + } + + if (str == "\n") { + if (this.resolve) { + this.resolve(this.buffer); + } + + this.buffer = ""; + this.promise = null; + this.resolve = null; + this.reject = null; + + return; + } + + this.buffer += str; + + if (!this.closed) { + stdin.get() + .then((data) => this.onGet(data)) + .catch((reason) => { + if (this.reject) { + this.reject(reason); + } + }); + } + + } + + constructor(echo: boolean = false) { + this.echo = echo; + } + + public read(): Promise { + if (this.promise != null) { + return Promise.reject("Already reading"); + } + + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + + stdin.get() + .then((data) => this.onGet(data)) + .catch((reason) => { + if (this.reject) { + this.reject(reason); + } + }); + + return this.promise; + } + + public close() { + this.closed = true; + + if (this.reject) { + this.reject("Stopped"); + } + + this.buffer = ""; + this.promise = null; + this.resolve = null; + this.reject = null; + } +} diff --git a/docs/robot/lekce4/project4/src/libs/robutek.ts b/docs/robot/lekce4/project4/src/libs/robutek.ts new file mode 100644 index 00000000..5a143f46 --- /dev/null +++ b/docs/robot/lekce4/project4/src/libs/robutek.ts @@ -0,0 +1,134 @@ + +import * as adc from "adc"; +import * as gpio from "gpio"; + +import { Servo } from "./servo.js"; + +import { SmartLed, LED_WS2812 } from "smartled"; + +import * as motors from "motor" +import * as ledc from "ledc"; + +ledc.configureTimer(0, 64000, 10); + +const leftMotorPins: motors.MotorPins = { motA: 11, motB: 12, encA: 39, encB: 40 } +const rightMotorPins: motors.MotorPins = { motA: 45, motB: 13, encA: 42, encB: 41 } + +const leftMotorLedc: motors.LedcConfig = { timer: 0, channelA: 0, channelB: 1 } +const rightMotorLedc: motors.LedcConfig = { timer: 0, channelA: 2, channelB: 3 } + +export const LeftMot = new motors.Motor({ pins: leftMotorPins, ledc: leftMotorLedc, encTicks: 406, diameter: 34 }); +export const RightMot = new motors.Motor({ pins: rightMotorPins, ledc: rightMotorLedc, encTicks: 406, diameter: 34 }); + + +export function init() { + adc.configure(LineSensors.S_1, adc.Attenuation.Db0); + adc.configure(LineSensors.S_2, adc.Attenuation.Db0); + adc.configure(LineSensors.S_3, adc.Attenuation.Db0); + adc.configure(LineSensors.S_4, adc.Attenuation.Db0); + + gpio.pinMode(LineSensors.S_PWR, gpio.PinMode.OUTPUT); + gpio.pinMode(LineSensors.S_SW, gpio.PinMode.OUTPUT); + + gpio.write(LineSensors.S_PWR, 1); +} + +export type SensorType = 'W_FR' | 'W_FL' | 'W_BL' | 'W_BR' | 'L_FR' | 'L_FL' | 'L_BL' | 'L_BR'; +export class LineSensors { + + public static readonly S_1: number = 4; + public static readonly S_2: number = 5; + public static readonly S_3: number = 6; + public static readonly S_4: number = 7; + public static readonly S_SW: number = 8; + public static readonly S_PWR: number = 47; + + private sw: number = 0; + + + private async switch_sensors(to_value: number) { + if (to_value == this.sw) { + return; + } + this.sw = to_value; + gpio.write(LineSensors.S_SW, to_value); + await sleep(5); + } + + public async read(sensor: SensorType): Promise { + switch (sensor) { + case 'W_FR': + this.switch_sensors(0); + return adc.read(LineSensors.S_1); + + case 'W_FL': + this.switch_sensors(0); + return adc.read(LineSensors.S_2); + + case 'W_BL': + this.switch_sensors(0); + return adc.read(LineSensors.S_3); + + case 'W_BR': + this.switch_sensors(0); + return adc.read(LineSensors.S_4); + + + case 'L_FR': + this.switch_sensors(1); + return adc.read(LineSensors.S_1); + + case 'L_FL': + this.switch_sensors(1); + return adc.read(LineSensors.S_2); + + case 'L_BL': + this.switch_sensors(1); + return adc.read(LineSensors.S_3); + + case 'L_BR': + this.switch_sensors(1); + return adc.read(LineSensors.S_4); + + default: + return 0; + + } + } +} + +export class Pen { + private servo: Servo; + + public static readonly UP = 512 + 180; + public static readonly DOWN = 512 - 180; + public static readonly MIDDLE = 512; + public static readonly UNLOAD = 0; + + constructor(pin: number) { + this.servo = new Servo(pin, 1, 0); + } + + /** + * Set the pen servo position. + * @param value The position to set the servo to, from 0 to 1023. + */ + public move(value: number) { + this.servo.write(value); + } +} + +export const ledStrip = new SmartLed(48, 9, LED_WS2812); + +export class Pins { + public static readonly Status_LED: number = 46; + + public static readonly Motor1_A = 11; + public static readonly Motor1_B = 12; + public static readonly Motor2_A = 45; + public static readonly Motor2_B = 13; + public static readonly ENC1_1 = 39; + public static readonly ENC1_2 = 40; + public static readonly ENC2_1 = 41; + public static readonly ENC2_2 = 42; +} \ No newline at end of file diff --git a/docs/robot/lekce4/project4/src/libs/servo.ts b/docs/robot/lekce4/project4/src/libs/servo.ts new file mode 100644 index 00000000..ac4fe3ee --- /dev/null +++ b/docs/robot/lekce4/project4/src/libs/servo.ts @@ -0,0 +1,34 @@ +import * as ledc from "ledc"; + +/** + * A class for controlling a servo motor. + */ +export class Servo { + private channel: number; // the PWM channel to use + + /** + * Create a new servo object. + * @param pin The pin the servo is connected to. + * @param timer The timer to use for PWM. + * @param channel The channel to use for PWM. + */ + constructor(pin: number, timer: number, channel: number) { + this.channel = channel; + ledc.configureTimer(timer, 50, 12); // 50Hz is the frequency of a servo, 12 is the resolution of the timer + ledc.configureChannel(channel, pin, timer, 1023); // 1023 is the resolution of a servo + } + + /** + * Set the servo position. + * @param value The position to set the servo to, from 0-1023. + */ + write(value: number) { + // map the value from 0-1023 to 0.5-2.5ms + const ms = ((value / 1023) * 2) + 0.5; // 0.5-2.5ms is the range of a servo + + // convert to a duty cycle + const duty = (ms / 20) * 1023; // 20ms is the period of a servo + + ledc.setDuty(this.channel, duty); // set the duty cycle to the servo + } +} diff --git a/docs/robot/lekce4/project4/src/libs/simplemotors.ts b/docs/robot/lekce4/project4/src/libs/simplemotors.ts new file mode 100644 index 00000000..c6f424cd --- /dev/null +++ b/docs/robot/lekce4/project4/src/libs/simplemotors.ts @@ -0,0 +1,47 @@ +import * as ledc from "ledc"; + +/** + * A class for controlling a servo motor. + */ +export class Motor { + private channel1: number; // the PWM channel to use + private channel2: number; // the PWM channel to use + + /** + * Create a new servo object. + * @param pin1 The pin the servo is connected to. + * @param pin2 The pin the servo is connected to. + * @param timer The timer to use for PWM. + * @param channel1 The channel to use for PWM. + * @param channel2 The channel to use for PWM. + */ + + constructor(pin1: number, pin2: number, timer: number, channel1: number, channel2: number) { + this.channel1 = channel1; + this.channel2 = channel2; + ledc.configureTimer(timer, 50, 12); // 50Hz is the frequency of a servo, 12 is the resolution of the timer + ledc.configureChannel(channel1, pin1, timer, 1023); // 1023 is the resolution of a servo + ledc.configureChannel(channel2, pin2, timer, 1023); // 1023 is the resolution of a servo + } + + /** + * Set the servo position. + * @param value The position to set the servo to, from -1 to 1. + */ + write(value: number) { + console.log(value) + + if (value > 0) { + ledc.setDuty(this.channel1, 1); + ledc.setDuty(this.channel2, (Math.abs(value) * 1023)); + } + else if (value < 0) { + ledc.setDuty(this.channel1, (Math.abs(value) * 1023)); + ledc.setDuty(this.channel2, 1); + } + else { + ledc.setDuty(this.channel1, 1); + ledc.setDuty(this.channel2, 1); + } + } +} diff --git a/docs/robot/lekce4/project4/tsconfig.json b/docs/robot/lekce4/project4/tsconfig.json index 98564f26..8cf82b10 100644 --- a/docs/robot/lekce4/project4/tsconfig.json +++ b/docs/robot/lekce4/project4/tsconfig.json @@ -1,11 +1,11 @@ -{ - "compilerOptions": { - "target": "es2020", - "module": "es2020", - "lib": ["es2020"], - "moduleResolution": "node", - "sourceMap": false, - "outDir": "build", - "rootDir": "src", - } -} +{ + "compilerOptions": { + "target": "es2020", + "module": "es2020", + "lib": ["es2020"], + "moduleResolution": "node", + "sourceMap": false, + "outDir": "build", + "rootDir": "src", + } +} diff --git a/docs/robot/lekce5/turtle.md b/docs/robot/lekce5/turtle.md index 4c76b6fd..eedfe25f 100644 --- a/docs/robot/lekce5/turtle.md +++ b/docs/robot/lekce5/turtle.md @@ -5,21 +5,21 @@ Z minulé lekce už umíme kreslit jednoduché tvary. Co když jich však chceme Pokud chceme nakreslit 2 čtverce vedle sebe, můžeme zkopírovat kód a mezitím se posunout: ```ts -import * as motors from "motors"; // ovládání motorů +import * as Robutek from "./libs/robutek.js" draw(true); for (let i: number = 0; i < 4; i++) { // chování opakujeme 4x, pro každou stěnu čtverce - motors.move(10); // posun dopředu o 10 cm - motors.rotate(90); // rotace doprava o 90 stupňů + Robutek.move(10); // posun dopředu o 10 cm + Robutek.rotate(90); // rotace doprava o 90 stupňů } draw(false); -motors.move(15); +Robutek.move(15); draw(true); for (let i: number = 0; i < 4; i++) { - motors.move(10); - motors.rotate(90); + Robutek.move(10); + Robutek.rotate(90); } draw(false); ``` @@ -30,16 +30,16 @@ Pokud bychom se pak rozhodli změnit např. velikost nakreslených čtverců, mu Můžeme si pomoct tím, co už známe: vnořeným `for` cyklem. Pokud chceme nakreslit např. 4 čtverce za sebou, můžeme to napsat takto: ```ts -import * as motors from "motors"; // ovládání motorů +import * as Robutek from "./libs/robutek.js" for (let square: number = 0; square < 4; square++) { draw(true); for (let i: number = 0; i < 4; i++) { // chování opakujeme 4x, pro každou stěnu čtverce - motors.move(10); // posun dopředu o 10 cm - motors.rotate(90); // rotace doprava o 90 stupňů + Robutek.move(10); // posun dopředu o 10 cm + Robutek.rotate(90); // rotace doprava o 90 stupňů } draw(false); - motors.move(15); + Robutek.move(15); } ``` diff --git a/docs/robutekLibrary/Robutek-library/src/index.ts b/docs/robutekLibrary/Robutek-library/src/index.ts index 131e6b25..712f5dc3 100644 --- a/docs/robutekLibrary/Robutek-library/src/index.ts +++ b/docs/robutekLibrary/Robutek-library/src/index.ts @@ -1,10 +1,14 @@ import * as colors from "./libs/colors.js" import * as Robutek from "./libs/robutek.js" +Robutek.init(); Robutek.ledStrip.set(0, colors.green); // nastaví barvu LED na desce na zelenou, LED na pásku začínají od 1 Robutek.ledStrip.show(); // zobrazí nastavení na LED +Robutek.LeftMot.setSpeed(100); // nastaví rychlost motoru na 100 RPM +Robutek.LeftMot.move({distance: 100}); // posune motor na 100 ticků + setInterval(() => { // pravidelně vyvolává událost console.log("Robotický tábor 2024, zdraví Jirka Vácha!"); // vypíše text: Robotický tábor 2024, zdraví Jirka Vácha! }, 1000); // čas opakování se udává v milisekundách (1000 ms je 1 sekunda) diff --git a/docs/robutekLibrary/Robutek-library/src/libs/robutek.ts b/docs/robutekLibrary/Robutek-library/src/libs/robutek.ts index 8e05f2bb..5a143f46 100644 --- a/docs/robutekLibrary/Robutek-library/src/libs/robutek.ts +++ b/docs/robutekLibrary/Robutek-library/src/libs/robutek.ts @@ -7,15 +7,18 @@ import { Servo } from "./servo.js"; import { SmartLed, LED_WS2812 } from "smartled"; import * as motors from "motor" +import * as ledc from "ledc"; + +ledc.configureTimer(0, 64000, 10); const leftMotorPins: motors.MotorPins = { motA: 11, motB: 12, encA: 39, encB: 40 } -const rightMotorPins: motors.MotorPins = { motA: 45, motB: 13, encA: 41, encB: 42 } +const rightMotorPins: motors.MotorPins = { motA: 45, motB: 13, encA: 42, encB: 41 } -const leftMotorLedc: motors.LedcConfig = { timer: 1, channelA: 0, channelB: 1 } -const rightMotorLedc: motors.LedcConfig = { timer: 1, channelA: 2, channelB: 3 } +const leftMotorLedc: motors.LedcConfig = { timer: 0, channelA: 0, channelB: 1 } +const rightMotorLedc: motors.LedcConfig = { timer: 0, channelA: 2, channelB: 3 } -export const LeftMot = new motors.Motor({ pins: leftMotorPins, ledc: leftMotorLedc, encTicks: 400, diameter: 10 }); -export const RightMot = new motors.Motor({ pins: rightMotorPins, ledc: rightMotorLedc, encTicks: 400, diameter: 10 }); +export const LeftMot = new motors.Motor({ pins: leftMotorPins, ledc: leftMotorLedc, encTicks: 406, diameter: 34 }); +export const RightMot = new motors.Motor({ pins: rightMotorPins, ledc: rightMotorLedc, encTicks: 406, diameter: 34 }); export function init() { diff --git a/mkdocs.yml b/mkdocs.yml index 78a1f6b6..116f1c79 100755 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -83,6 +83,7 @@ plugins: - zip_folders: folders: - robot/lekce1/example1 + - robot/lekce2/blank_project - robot/lekce3/project3 - robot/lekce4/project4 - robot/lekce6/project6 From b03add9f43f9f428890d6eaf7f21540957e4aab4 Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Mon, 8 Jul 2024 14:31:17 +0200 Subject: [PATCH 12/14] Add gridui example --- .../lekce1/example-gridui/@types/adc.d.ts | 24 ++ .../example-gridui/@types/basicStream.d.ts | 22 ++ .../lekce1/example-gridui/@types/fs.d.ts | 80 +++++ .../lekce1/example-gridui/@types/gpio.d.ts | 49 +++ .../lekce1/example-gridui/@types/gridui.d.ts | 321 ++++++++++++++++++ .../lekce1/example-gridui/@types/i2c.d.ts | 34 ++ .../example-gridui/@types/keyvalue.d.ts | 61 ++++ .../lekce1/example-gridui/@types/ledc.d.ts | 44 +++ .../lekce1/example-gridui/@types/misc.d.ts | 5 + .../lekce1/example-gridui/@types/motor.d.ts | 82 +++++ .../lekce1/example-gridui/@types/path.d.ts | 36 ++ .../example-gridui/@types/platform.d.ts | 6 + .../example-gridui/@types/pulseCounter.d.ts | 64 ++++ .../example-gridui/@types/simpleradio.d.ts | 84 +++++ .../example-gridui/@types/smartled.d.ts | 55 +++ .../lekce1/example-gridui/@types/stdio.d.ts | 13 + .../lekce1/example-gridui/@types/timers.d.ts | 31 ++ .../example-gridui/@types/timestamp.d.ts | 11 + .../lekce1/example-gridui/@types/wifi.d.ts | 6 + docs/robot/lekce1/example-gridui/src/index.ts | 76 +++++ .../robot/lekce1/example-gridui/src/layout.ts | 88 +++++ .../lekce1/example-gridui/src/libs/colors.ts | 81 +++++ .../example-gridui/src/libs/readline.ts | 84 +++++ .../lekce1/example-gridui/src/libs/robutek.ts | 139 ++++++++ .../lekce1/example-gridui/src/libs/servo.ts | 34 ++ .../example-gridui/src/libs/simplemotors.ts | 47 +++ .../robot/lekce1/example-gridui/tsconfig.json | 11 + 27 files changed, 1588 insertions(+) create mode 100644 docs/robot/lekce1/example-gridui/@types/adc.d.ts create mode 100644 docs/robot/lekce1/example-gridui/@types/basicStream.d.ts create mode 100644 docs/robot/lekce1/example-gridui/@types/fs.d.ts create mode 100644 docs/robot/lekce1/example-gridui/@types/gpio.d.ts create mode 100644 docs/robot/lekce1/example-gridui/@types/gridui.d.ts create mode 100644 docs/robot/lekce1/example-gridui/@types/i2c.d.ts create mode 100644 docs/robot/lekce1/example-gridui/@types/keyvalue.d.ts create mode 100644 docs/robot/lekce1/example-gridui/@types/ledc.d.ts create mode 100644 docs/robot/lekce1/example-gridui/@types/misc.d.ts create mode 100644 docs/robot/lekce1/example-gridui/@types/motor.d.ts create mode 100644 docs/robot/lekce1/example-gridui/@types/path.d.ts create mode 100644 docs/robot/lekce1/example-gridui/@types/platform.d.ts create mode 100644 docs/robot/lekce1/example-gridui/@types/pulseCounter.d.ts create mode 100644 docs/robot/lekce1/example-gridui/@types/simpleradio.d.ts create mode 100644 docs/robot/lekce1/example-gridui/@types/smartled.d.ts create mode 100644 docs/robot/lekce1/example-gridui/@types/stdio.d.ts create mode 100644 docs/robot/lekce1/example-gridui/@types/timers.d.ts create mode 100644 docs/robot/lekce1/example-gridui/@types/timestamp.d.ts create mode 100644 docs/robot/lekce1/example-gridui/@types/wifi.d.ts create mode 100644 docs/robot/lekce1/example-gridui/src/index.ts create mode 100644 docs/robot/lekce1/example-gridui/src/layout.ts create mode 100644 docs/robot/lekce1/example-gridui/src/libs/colors.ts create mode 100644 docs/robot/lekce1/example-gridui/src/libs/readline.ts create mode 100644 docs/robot/lekce1/example-gridui/src/libs/robutek.ts create mode 100644 docs/robot/lekce1/example-gridui/src/libs/servo.ts create mode 100644 docs/robot/lekce1/example-gridui/src/libs/simplemotors.ts create mode 100644 docs/robot/lekce1/example-gridui/tsconfig.json diff --git a/docs/robot/lekce1/example-gridui/@types/adc.d.ts b/docs/robot/lekce1/example-gridui/@types/adc.d.ts new file mode 100644 index 00000000..871de201 --- /dev/null +++ b/docs/robot/lekce1/example-gridui/@types/adc.d.ts @@ -0,0 +1,24 @@ +declare module "adc" { + type Atten = number; + + const Attenuation: { + readonly Db0: Atten; + readonly Db2_5: Atten; + readonly Db6: Atten; + readonly Db11: Atten; + }; + + + /** + * Enable ADC on the given pin. + * @param pin The pin to enable ADC on. + */ + function configure(pin: number, attenuation?: Atten): void; + + /** + * Read the value of the given pin. + * @param pin The pin to read. + * @returns The value of the pin (0-1023) + */ + function read(pin: number): number; +} diff --git a/docs/robot/lekce1/example-gridui/@types/basicStream.d.ts b/docs/robot/lekce1/example-gridui/@types/basicStream.d.ts new file mode 100644 index 00000000..70c9e06f --- /dev/null +++ b/docs/robot/lekce1/example-gridui/@types/basicStream.d.ts @@ -0,0 +1,22 @@ +declare interface Writable { + /** + * Write the given data to the stream. + * @param data The data to write. + */ + write(data: string): void; +} + +declare interface Readable { + /** + * Read a single character from the stream. + * @returns Promise that resolves to the character read. + */ + get(): Promise; + + /** + * Read a chunk of data from the stream. The size of the chunk is + * given by the implementation and available data. + * @returns Promise that resolves to the data read. + */ + read(): Promise; +} diff --git a/docs/robot/lekce1/example-gridui/@types/fs.d.ts b/docs/robot/lekce1/example-gridui/@types/fs.d.ts new file mode 100644 index 00000000..a21dc650 --- /dev/null +++ b/docs/robot/lekce1/example-gridui/@types/fs.d.ts @@ -0,0 +1,80 @@ +declare module "fs" { + interface File { + path: string; + + /** + * Check if the file is open. + */ + isOpen(): boolean; + + /** + * Close the file. + */ + close(): void; + + /** + * Read characters from the file. + * @param len The number of characters to read. + */ + read(len: number): string; + + /** + * Write text to the file. + * @param text The text to write. + */ + write(text: string): void; + } + + /** + * Open the given file in the given mode. + * @param path The path to the file. + * @param mode The mode to open the file in ("r", "w", "a" and combinations). + */ + function open(path: string, mode: string): File; + + /** + * Check if the given path exists. + * @param path The path to check. + * @returns True if the path exists, false otherwise. + */ + function exists(path: string): boolean; + + /** + * Check if the given path is a file. + * @param path The path to check. + * @returns True if the path is a file, false otherwise. + */ + function isFile(path: string): boolean; + + /** + * Check if the given path is a directory. + * @param path The path to check. + * @returns True if the path is a directory, false otherwise. + */ + function isDirectory(path: string): boolean; + + /** + * Create a directory at the given path. + * @param path The path to create the directory at. + */ + function mkdir(path: string): void; + + /** + * Remove the file at the given path. + * @param path The path to the file to remove. + */ + function rm(path: string): void; + + /** + * Remove the directory at the given path. + * @param path The path to the directory to remove. + */ + function rmdir(path: string): void; + + /** + * List the files in the given directory. + * @param path The path to the directory to list. + * @returns An array of file names in the directory. + */ + function readdir(path: string): string[]; +} diff --git a/docs/robot/lekce1/example-gridui/@types/gpio.d.ts b/docs/robot/lekce1/example-gridui/@types/gpio.d.ts new file mode 100644 index 00000000..09c167db --- /dev/null +++ b/docs/robot/lekce1/example-gridui/@types/gpio.d.ts @@ -0,0 +1,49 @@ +declare module "gpio" { + const PinMode: { + readonly DISABLE: number, + readonly OUTPUT: number, + readonly INPUT: number, + readonly INPUT_PULLUP: number, + readonly INPUT_PULLDOWN: number, + }; + + interface EventInfo { + timestamp: Timestamp; + } + + /** + * Configure the given pin. + * @param pin The pin to configure. + * @param mode The mode to configure the pin in. + */ + function pinMode(pin: number, mode: number): void; + + /** + * Write digital value to the given pin. + * @param pin The pin to write to. + * @param value The value to write. + */ + function write(pin: number, value: number): void; + + /** + * Read digital value from the given pin. + * @param pin The pin to read from. + * @returns The value of the pin (0 or 1). + */ + function read(pin: number): number; + + /** + * Set event handler for the given pin. + * @param event The event to handle. + * @param pin The pin to handle the event for. + * @param callback The callback to call when the event occurs. + */ + function on(event: "rising" | "falling" | "change", pin: number, callback: (info: EventInfo) => void): void; + + /** + * Remove event handler for the given pin. + * @param event The event to remove. + * @param pin The pin to remove the event handler for. + */ + function off(event: "rising" | "falling" | "change", pin: number): void; +} diff --git a/docs/robot/lekce1/example-gridui/@types/gridui.d.ts b/docs/robot/lekce1/example-gridui/@types/gridui.d.ts new file mode 100644 index 00000000..07e6bbe0 --- /dev/null +++ b/docs/robot/lekce1/example-gridui/@types/gridui.d.ts @@ -0,0 +1,321 @@ +declare module "gridui" { + namespace widget { + interface Base { + readonly uuid: number + widgetX: number + widgetY: number + widgetW: number + widgetH: number + widgetTab: number + css(key: string): string + setCss(key: string, value: string): void + } + + interface Arm extends Base { + readonly x: number + readonly y: number + } + + interface Bar extends Base { + color: string + fontSize: number + min: number + max: number + value: number + showValue: boolean + } + + interface Button extends Base { + text: string + fontSize: number + color: string + background: string + align: string + valign: string + disabled: boolean + readonly pressed: boolean + } + + interface Camera extends Base { + rotation: number + clip: boolean + } + + interface Checkbox extends Base { + fontSize: number + checked: boolean + color: string + text: string + } + + interface Circle extends Base { + color: string + fontSize: number + min: number + max: number + lineWidth: number + valueStart: number + value: number + showValue: boolean + } + + interface Input extends Base { + text: string + color: string + type: string + disabled: boolean + } + + interface Joystick extends Base { + color: string + keys: string + text: string + readonly x: number + readonly y: number + } + + interface Led extends Base { + color: string + on: boolean + } + + interface Orientation extends Base { + color: string + readonly yaw: number + readonly pitch: number + readonly roll: number + readonly joystickX: number + readonly joystickY: number + } + + interface Select extends Base { + color: string + background: string + disabled: boolean + options: string + selectedIndex: number + } + + interface Slider extends Base { + color: string + fontSize: number + min: number + max: number + value: number + precision: number + showValue: boolean + } + + interface SpinEdit extends Base { + fontSize: number + color: string + value: number + step: number + precision: number + } + + interface Switcher extends Base { + fontSize: number + color: string + value: number + min: number + max: number + } + + interface Text extends Base { + text: string + fontSize: number + color: string + background: string + align: string + valign: string + prefix: string + suffix: string + } + } + + namespace builder { + interface Base { + css(key: string, value: string): this + finish(): this + } + + interface Arm extends Base { + info(info: Record): Arm + + onGrab(callback: (arm: widget.Arm) => void): Arm + onPositionChanged(callback: (arm: widget.Arm) => void): Arm + } + + interface Bar extends Base { + color(color: string): Bar + fontSize(fontSize: number): Bar + min(min: number): Bar + max(max: number): Bar + value(value: number): Bar + showValue(showValue: boolean): Bar + } + + interface Button extends Base { + text(text: string): Button + fontSize(fontSize: number): Button + color(color: string): Button + background(background: string): Button + align(align: string): Button + valign(valign: string): Button + disabled(disabled: boolean): Button + + onPress(callback: (button: widget.Button) => void): Button + onRelease(callback: (button: widget.Button) => void): Button + } + + interface Camera extends Base { + rotation(rotation: number): Camera + clip(clip: boolean): Camera + tags(tags: any /* TODO: fix type */): Camera + } + + interface Checkbox extends Base { + fontSize(fontSize: number): Checkbox + checked(checked: boolean): Checkbox + color(color: string): Checkbox + text(text: string): Checkbox + + onChanged(callback: (checkbox: widget.Checkbox) => void): Checkbox + } + + interface Circle extends Base { + color(color: string): Circle + fontSize(fontSize: number): Circle + min(min: number): Circle + max(max: number): Circle + lineWidth(lineWidth: number): Circle + valueStart(valueStart: number): Circle + value(value: number): Circle + showValue(showValue: boolean): Circle + } + + interface Input extends Base { + text(text: string): Input + color(color: string): Input + type(type: string): Input + disabled(disabled: boolean): Input + + onChanged(callback: (input: widget.Input) => void): Input + } + + interface Joystick extends Base { + color(color: string): Joystick + keys(keys: string): Joystick + text(text: string): Joystick + + onClick(callback: (joystick: widget.Joystick) => void): Joystick + onPositionChanged(callback: (joystick: widget.Joystick) => void): Joystick + } + + interface Led extends Base { + color(color: string): Led + on(on: boolean): Led + } + + interface Orientation extends Base { + color(color: string): Orientation + + onPositionChanged(callback: (orientation: widget.Orientation) => void): Orientation + } + + interface Select extends Base { + color(color: string): Select + background(background: string): Select + disabled(disabled: boolean): Select + options(options: string): Select + selectedIndex(selectedIndex: number): Select + + onChanged(callback: (select: widget.Select) => void): Select + } + + interface Slider extends Base { + color(color: string): Slider + fontSize(fontSize: number): Slider + min(min: number): Slider + max(max: number): Slider + value(value: number): Slider + precision(precision: number): Slider + showValue(showValue: boolean): Slider + + onChanged(callback: (slider: widget.Slider) => void): Slider + } + + interface SpinEdit extends Base { + fontSize(fontSize: number): SpinEdit + color(color: string): SpinEdit + value(value: number): SpinEdit + step(step: number): SpinEdit + precision(precision: number): SpinEdit + + onChanged(callback: (spinEdit: widget.SpinEdit) => void): SpinEdit + } + + interface Switcher extends Base { + fontSize(fontSize: number): Switcher + color(color: string): Switcher + value(value: number): Switcher + min(min: number): Switcher + max(max: number): Switcher + + onChanged(callback: (switcher: widget.Switcher) => void): Switcher + } + + interface Text extends Base { + text(text: string): Text + fontSize(fontSize: number): Text + color(color: string): Text + background(background: string): Text + align(align: string): Text + valign(valign: string): Text + prefix(prefix: string): Text + suffix(suffix: string): Text + } + + interface Widget extends Base { } + } + + class Builder { + arm(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Arm + bar(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Bar + button(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Button + camera(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Camera + checkbox(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Checkbox + circle(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Circle + input(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Input + joystick(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Joystick + led(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Led + orientation(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Orientation + select(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Select + slider(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Slider + spinEdit(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.SpinEdit + switcher(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Switcher + text(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Text + widget(x: number, y: number, w: number, h: number, uuid?: number, tab?: number): builder.Widget + } + + /** + * Initialize GridUI. + * @param ownerName name of the owner, must match the name entered in RBController app. + * @param deviceName name of this device, visible in the RBController app. + * @param builderCallback callback, which receives the builder instance that can be used to create widgets. + */ + function begin(ownerName: string, deviceName: string, builderCallback: (builder: Builder) => void): void + + /** + * Stop GridUI. + */ + function end(): void + + /** + * Returns included GridUI version as number, to be compared with hex representation of the version. + * + * For example, for version 5.1.0, do: `gridui.version() >= 0x050100` + */ + function version(): number +} \ No newline at end of file diff --git a/docs/robot/lekce1/example-gridui/@types/i2c.d.ts b/docs/robot/lekce1/example-gridui/@types/i2c.d.ts new file mode 100644 index 00000000..fd6685a9 --- /dev/null +++ b/docs/robot/lekce1/example-gridui/@types/i2c.d.ts @@ -0,0 +1,34 @@ +declare module "i2c" { + interface I2C { + /** + * Find an I2C interface by its pin. + * @param pin The pin the I2C interface is connected to. + * @returns The I2C interface, or undefined if not found. + */ + find(pin: number): I2C | undefined; + + /** + * Read from the given address. + * @param address The address to read from. + * @param quantity The number of bytes to read. + * @returns The bytes read. + */ + readFrom(address: number, quantity: number): Uint8Array; + + /** + * Write to the given address. + * @param address The address to write to. + * @param buffer The data to write. + */ + writeTo(address: number, buffer: ArrayBuffer | Uint8Array | number[] | string | number): void; + + /** + * Setup the I2C interface. + * @param options The options to use when setting up the I2C interface. + */ + setup(options: { scl?: number, sda?: number, bitrate?: number }): void; + } + + const I2C1: I2C; + const I2C2: I2C | undefined; +} diff --git a/docs/robot/lekce1/example-gridui/@types/keyvalue.d.ts b/docs/robot/lekce1/example-gridui/@types/keyvalue.d.ts new file mode 100644 index 00000000..761b09fb --- /dev/null +++ b/docs/robot/lekce1/example-gridui/@types/keyvalue.d.ts @@ -0,0 +1,61 @@ +declare module "keyvalue" { + class KeyValueNamespace { + /** + * Get saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present + */ + get(key: string): string | number | null; + + /** + * Get string saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present or if not string + */ + getString(key: string): string | null; + + /** + * Get number saved value. + * @param key key, max 15 characters. + * @return saved value or null if not present or if not number + */ + getNumber(key: string): number | null; + + /** + * Set value in KeyValue namespace, overwriting any previous value. + * Call commit() after setting everything, otherwise changes will be lost! + * @param key key, max 15 characters. + * @param value value + */ + set(key: string, value: string | number): void; + + /** + * Erase value from KeyValue namespace. + * Call commit() after setting everything, otherwise changes will be lost! + * @param key key, max 15 characters. + */ + erase(key: string): void; + + /** + * Check existance of a key. + * @param key key, max 15 characters. + * @returns true if exists, false otherwise + */ + exists(key: string): boolean; + + /** + * Save modifications to persistent storage. + */ + commit(): void; + } + + /** + * Open a KeyValue namespace for use. KeyValue is a persitent storage for small + * pieces of data that saves values across restarts. + * + * The namespaces are isolated from each other. + * + * @param namespace Namespace name, max 15 characters. + */ + function open(namespace: string): KeyValueNamespace; +} diff --git a/docs/robot/lekce1/example-gridui/@types/ledc.d.ts b/docs/robot/lekce1/example-gridui/@types/ledc.d.ts new file mode 100644 index 00000000..49b016e5 --- /dev/null +++ b/docs/robot/lekce1/example-gridui/@types/ledc.d.ts @@ -0,0 +1,44 @@ +declare module "ledc" { + /** + * Configure the given timer. + * @param timer The timer to configure. + * @param frequency The frequency to configure the timer to. + * @param resolution The resolution to configure the timer to (default 10 bits, changes frequency range) + */ + function configureTimer(timer: number, frequency: number, resolution?: number): void; + + /** + * Configure the given LEDC channel. + * @param channel The channel to configure. + * @param pin The pin to configure the channel to. + * @param timer The timer to configure the channel to. + * @param duty The duty to configure the channel to (0-1023). + */ + function configureChannel(channel: number, pin: number, timer: number, duty: number): void; + + /** + * Set the frequency of the given timer. + * @param timer The timer to set the frequency of. + * @param frequency The frequency to set the timer to. + */ + function setFrequency(timer: number, frequency: number): void; + + /** + * Set the duty of the given channel. + * @param channel The channel to set the duty of. + * @param duty The duty to set the channel to (0-1023). + */ + function setDuty(channel: number, duty: number): void; + + /** + * Stop the given timer. + * @param timer The timer to stop. + */ + function stopTimer(timer: number): void; + + /** + * Stop the given channel. + * @param channel The channel to stop. + */ + function stopChannel(channel: number): void; +} diff --git a/docs/robot/lekce1/example-gridui/@types/misc.d.ts b/docs/robot/lekce1/example-gridui/@types/misc.d.ts new file mode 100644 index 00000000..05e77a20 --- /dev/null +++ b/docs/robot/lekce1/example-gridui/@types/misc.d.ts @@ -0,0 +1,5 @@ +/** + * Exits the current program. + * @param code The exit code to use. + */ +declare function exit(code?: number): void; diff --git a/docs/robot/lekce1/example-gridui/@types/motor.d.ts b/docs/robot/lekce1/example-gridui/@types/motor.d.ts new file mode 100644 index 00000000..2ec31392 --- /dev/null +++ b/docs/robot/lekce1/example-gridui/@types/motor.d.ts @@ -0,0 +1,82 @@ +declare module "motor" { + + type MoveDuration = { + distance?: number; + time?: number; + } + + type MotorPins = { + motA: number; + motB: number; + encA: number; + encB: number; + }; + + type LedcConfig = { + timer: number; + channelA: number; + channelB: number; + }; + + class Motor { + /** + * Construc a new Motor instance + * @param options Motor configuration + * @note Units used in the diameter parameter determines the units used in the other methods + */ + constructor(options: { pins: MotorPins, ledc: LedcConfig, encTicks: number, diameter: number }); + + /** + * Set the speed of the motor + * @param speed Speed in units per second + * @note The units are the same as used in the diameter parameter + */ + setSpeed(speed: number): void; + // setRamp(ramp: number): void; + + /** + * Move the motor + * @param duration Duration of the movement + * @note The units are the same as used in the diameter parameter + * @note If the duration is not provided, the motor will move indefinitely + */ + move(duration?: MoveDuration): Promise; + + /** + * Stop the motor + * @param brake If true, the motor will brake, otherwise it will coast to a stop + */ + stop(brake?: boolean): Promise; + + /** + * Get the position of the motor + * @note The units are the same as used in the diameter parameter + * @returns The position of the motor + */ + getPosition(): number; + + /** + * Close the motor + */ + close(): void; + } + + /* class Wheels { + constructor(options: { + left: MotorPins, + right: MotorPins, + encTicks: number, + diameter: number, + spacing: number + }); + setSpeed(speed: number): void; + // setRamp(ramp: number): void; + + move(curve: number, duration?: MoveDuration): Promise; + // rotate(angle: number): Promise; + stop(brake?: boolean): Promise; + getPosition(): { left: number, right: number }; + + close(): void; + } */ +} diff --git a/docs/robot/lekce1/example-gridui/@types/path.d.ts b/docs/robot/lekce1/example-gridui/@types/path.d.ts new file mode 100644 index 00000000..c4434c60 --- /dev/null +++ b/docs/robot/lekce1/example-gridui/@types/path.d.ts @@ -0,0 +1,36 @@ +declare module "path" { + /** + * Normalize the given path. + * @param path The path to normalize. + * @returns The normalized path. + */ + function normalize(path: string): string; + + /** + * Get the parent directory of the given path. + * @param path The path to get the parent directory of. + * @returns The parent directory. + */ + function dirname(path: string): string; + + /** + * Get the last part of the given path. + * @param path The path to get the last part of. + * @returns The last part of the path. + */ + function basename(path: string): string; + + /** + * Join the given paths. + * @param paths The paths to join. + * @returns The joined path. + */ + function join(...paths: string[]): string; + + /** + * Check if the given path is absolute. + * @param path The path to check. + * @returns True if the path is absolute, false otherwise. + */ + function isAbsolute(path: string): boolean; +} diff --git a/docs/robot/lekce1/example-gridui/@types/platform.d.ts b/docs/robot/lekce1/example-gridui/@types/platform.d.ts new file mode 100644 index 00000000..6fe4c710 --- /dev/null +++ b/docs/robot/lekce1/example-gridui/@types/platform.d.ts @@ -0,0 +1,6 @@ +declare const PlatformInfo: { + /** + * The name of the platform the program is running on. + */ + name: string +} diff --git a/docs/robot/lekce1/example-gridui/@types/pulseCounter.d.ts b/docs/robot/lekce1/example-gridui/@types/pulseCounter.d.ts new file mode 100644 index 00000000..76ad8edd --- /dev/null +++ b/docs/robot/lekce1/example-gridui/@types/pulseCounter.d.ts @@ -0,0 +1,64 @@ +declare module "pulseCounter" { + type LevelAction = number; + type EdgeAction = number; + + const LevelAction: { + Keep: LevelAction; + Inverse: LevelAction; + Hold: LevelAction; + }; + + const EdgeAction: { + Hold: EdgeAction; + Increase: EdgeAction; + Decrease: EdgeAction; + }; + + type LevelMode = { + low: LevelAction, + high: LevelAction, + } + + type EdgeMode = { + pos: EdgeAction, + neg: EdgeAction, + } + + class PulseCounter { + /** + * Create a new pulse counter instance. + * @param options The options for the pulse counter. + */ + constructor(options: { + pinLevel: number, + pinEdge: number, + levelMode: LevelMode, + edgeMode: EdgeMode, + }) + + /** + * Read the current value of the pulse counter. + */ + read(): number; + + /** + * Reset the pulse counter to 0. + */ + clear(): void; + + /** + * Start the pulse counter. + */ + start(): void; + + /** + * Stop the pulse counter. + */ + stop(): void; + + /** + * Close the pulse counter. + */ + close(): void; + } +} diff --git a/docs/robot/lekce1/example-gridui/@types/simpleradio.d.ts b/docs/robot/lekce1/example-gridui/@types/simpleradio.d.ts new file mode 100644 index 00000000..a1cb8608 --- /dev/null +++ b/docs/robot/lekce1/example-gridui/@types/simpleradio.d.ts @@ -0,0 +1,84 @@ +declare module "simpleradio" { + type PacketDataType = "number" | "string" | "keyvalue"; + + interface PacketInfo { + group: number; + address: string; + rssi: number; + } + + /** + * Initialize the radio. + * @param group The radio group to use. + */ + function begin(group: number): void; + + /** + * Set the radio group. + * @param group The radio group to use, between 0 and 15 inclusive. + */ + function setGroup(group: number): void; + + /** + * Get current radio group + * @returns ID of the current group + */ + function group(): number; + + /** + * Get the local device address. + * @returns the local device address. Only works after begin() is called. + */ + function address(): string; + + /** + * Send a string. + * @param str The string to send. + */ + function sendString(str: string): void; + + /** + * Send a number. + * @param num The number to send. + */ + function sendNumber(num: number): void; + + /** + * Send a key-value pair. + * @param key The key to send. + * @param value The number to send. + */ + function sendKeyValue(key: string, value: number): void; + + /** + * Register a callback for a packet type. + * @param type The packet type to register for. + * @param callback The callback to register. + */ + function on(type: "number", callback: (num: number, info: PacketInfo) => void): void; + + /** + * Register a callback for a packet type. + * @param type The packet type to register for. + * @param callback The callback to register. + */ + function on(type: "string", callback: (str: string, info: PacketInfo) => void): void; + + /** + * Register a callback for a packet type. + * @param type The packet type to register for. + * @param callback The callback to register. + */ + function on(type: "keyvalue", callback: (key: string, value: number, info: PacketInfo) => void): void; + + /** + * Unregister a callback for a packet type. + * @param type The packet type to unregister for. + */ + function off(type: PacketDataType): void; + + /** + * Stop the radio. + */ + function end(): void; +} diff --git a/docs/robot/lekce1/example-gridui/@types/smartled.d.ts b/docs/robot/lekce1/example-gridui/@types/smartled.d.ts new file mode 100644 index 00000000..a9c51e80 --- /dev/null +++ b/docs/robot/lekce1/example-gridui/@types/smartled.d.ts @@ -0,0 +1,55 @@ +declare module "smartled" { + interface Rgb { + r: number; + g: number; + b: number; + } + + interface LedType { + T0H: number; + T1H: number; + T0L: number; + T1L: number; + TRS: number; + } + + class SmartLed { + /** + * Create a new Smart LED strip. + * @param pin The pin the strip is connected to. + * @param count The number of LEDs in the strip. + * @param type The type of LED strip. + */ + constructor(pin: number, count: number, type?: LedType); + + /** + * Show the current buffer on the strip. + */ + public show(): void; + + /** + * Set the color of the given LED. + * @param index The index of the LED to set. + * @param rgb The color to set the LED to. + */ + public set(index: number, rgb: Rgb): void; + + /** + * Get the color of the given LED. + * @param index The index of the LED to get. + * @returns The color of the LED. + */ + public get(index: number): Rgb; + + /** + * Clear the buffer. + */ + public clear(): void; + } + + const LED_WS2812: LedType; + const LED_WS2812B: LedType; + const LED_WS2812B_2020: LedType; + const LED_SK6812: LedType; + const LED_WS2813: LedType; +} diff --git a/docs/robot/lekce1/example-gridui/@types/stdio.d.ts b/docs/robot/lekce1/example-gridui/@types/stdio.d.ts new file mode 100644 index 00000000..f0c1881d --- /dev/null +++ b/docs/robot/lekce1/example-gridui/@types/stdio.d.ts @@ -0,0 +1,13 @@ +declare module "stdio" { + let stdout: Writable; + let stderr: Writable; + let stdin: Readable; +} + +declare const console: { + debug(arg: any): void; + log(arg: any): void; + warn(arg: any): void; + info(arg: any): void; + error(arg: any): void; +} diff --git a/docs/robot/lekce1/example-gridui/@types/timers.d.ts b/docs/robot/lekce1/example-gridui/@types/timers.d.ts new file mode 100644 index 00000000..4f73380a --- /dev/null +++ b/docs/robot/lekce1/example-gridui/@types/timers.d.ts @@ -0,0 +1,31 @@ +/** + * Returns a promise that resolves after the specified time. + * @param ms The number of milliseconds to wait before resolving the promise. + */ +declare function sleep(ms: number): Promise; + +/** + * Calls a function after the specified time. + * @param callback The function to call. + * @param ms The number of milliseconds to wait before calling the function. + */ +declare function setTimeout(callback: () => void, ms: number): number; + +/** + * Calls a function repeatedly, with a fixed time delay between each call. + * @param callback The function to call. + * @param ms The number of milliseconds to wait before calling the function. + */ +declare function setInterval(callback: () => void, ms: number): number; + +/** + * Cancels a timeout previously established by calling setTimeout(). + * @param id The identifier of the timeout to cancel. + */ +declare function clearTimeout(id: number): void; + +/** + * Cancels a timeout previously established by calling setInterval(). + * @param id The identifier of the interval to cancel. + */ +declare function clearInterval(id: number): void; diff --git a/docs/robot/lekce1/example-gridui/@types/timestamp.d.ts b/docs/robot/lekce1/example-gridui/@types/timestamp.d.ts new file mode 100644 index 00000000..bd9ab6c8 --- /dev/null +++ b/docs/robot/lekce1/example-gridui/@types/timestamp.d.ts @@ -0,0 +1,11 @@ +declare interface Timestamp { + /** + * Convert the timestamp to milliseconds. + */ + millis(): number; + + /** + * Get the lower 10^3 part of the timestamp in microseconds. + */ + micros(): number; +} diff --git a/docs/robot/lekce1/example-gridui/@types/wifi.d.ts b/docs/robot/lekce1/example-gridui/@types/wifi.d.ts new file mode 100644 index 00000000..7e532194 --- /dev/null +++ b/docs/robot/lekce1/example-gridui/@types/wifi.d.ts @@ -0,0 +1,6 @@ +declare module "wifi" { + /** + * Return current IPv4 of the device, or null if WiFi is disabled or not connected. + */ + function currentIp(): string | null; +} diff --git a/docs/robot/lekce1/example-gridui/src/index.ts b/docs/robot/lekce1/example-gridui/src/index.ts new file mode 100644 index 00000000..e1eddd1d --- /dev/null +++ b/docs/robot/lekce1/example-gridui/src/index.ts @@ -0,0 +1,76 @@ +import * as Robutek from "./libs/robutek.js" +import * as wifi from "wifi"; +import Layout from "./layout.js" + +Robutek.init() // Zprovozní Robůtka + +function scale(value) { + // Scale the joystick value from -32768..32767 to -1..1 + return value / 32768; +} + +function limitSpeed(value, factor) { + // Apply a limiting factor to the value + return value * factor; +} + +function setMotorsJoystick(x: number, y: number, coef = 2.5) { + // Scale the joystick values + x = scale(x); + y = scale(y); + + // Calculate motor powers + let r = (y - (x / coef)); + let l = (y + (x / coef)); + + // Apply speed limiter + r = limitSpeed(r, speedLimiter); + l = limitSpeed(l, speedLimiter); + + // Ensure the values are within the range -1 to 1 + r = Math.max(-1, Math.min(1, r)); + l = Math.max(-1, Math.min(1, l)); + + // Swap r and l if both are negative + if (r < 0 && l < 0) { + [r, l] = [l, r]; + } + + console.log(`x: ${x}, y: ${y}`); + console.log(`left motor power: ${l}, right motor power: ${r}`); + + // Set motor power + Robutek.LeftMot.setSpeed(l); + Robutek.LeftMot.move(); + Robutek.RightMot.setSpeed(r); + Robutek.RightMot.move(); +} + + +let speedLimiter = 0.5; + +Layout.begin("Owner name", "Device Name", builder => { + + builder.ButtonBlink.onPress(async () => { + Robutek.ledStrip.clear(); + Robutek.ledStrip.set(0, { r: 255, g: 0, b: 0 }); + Robutek.ledStrip.show(); + await sleep(500); + Robutek.ledStrip.clear(); + Robutek.ledStrip.set(0, { r: 0, g: 255, b: 0 }); + Robutek.ledStrip.show(); + await sleep(500); + }); + + builder.SetSpeed.onChanged(slider => { + speedLimiter = slider.value; + console.log(`speed limiter: ${speedLimiter}`); + }); + + builder.Joystick.onPositionChanged(joystick => { + setMotorsJoystick(joystick.x, joystick.y); + }); + +}) + +console.log("Otevři aplikaci RBController nebo otevři prohlížeč a zadej IP: " + wifi.currentIp() + " pro ovládání robota."); \ No newline at end of file diff --git a/docs/robot/lekce1/example-gridui/src/layout.ts b/docs/robot/lekce1/example-gridui/src/layout.ts new file mode 100644 index 00000000..b87c4cd4 --- /dev/null +++ b/docs/robot/lekce1/example-gridui/src/layout.ts @@ -0,0 +1,88 @@ +// AUTOGENERATED FILE, DO NOT EDIT +// Generated by https://gridui.robotikabrno.cz/ +// Layout: {"cols":12,"rows":18,"enableSplitting":true,"widgets":[{"uuid":43594,"type":"Button","state":{"id":"ButtonBlink","x":1,"y":1.5,"w":10,"h":2,"tab":0,"css":{},"text":"Blikni","fontSize":18,"color":"#fb0404","background":"","align":"center","valign":"center","disabled":false}},{"uuid":11866,"type":"Joystick","state":{"id":"Joystick","x":3,"y":10,"w":6,"h":5.5,"tab":0,"css":{},"color":"#FF0000","keys":"","text":""}},{"uuid":4862,"type":"Slider","state":{"id":"SetSpeed","x":1,"y":4,"w":10,"h":2.5,"tab":0,"css":{},"color":"#008000","fontSize":16,"min":0,"max":1,"value":0.5,"precision":0.05,"showValue":true}},{"uuid":47586,"type":"Text","state":{"id":"TextSpeed","x":1,"y":6.5,"w":10,"h":1,"tab":0,"css":{},"text":"Nastavení maximální rychlosti","fontSize":17,"color":"#000000","background":"","align":"center","valign":"center","prefix":"","suffix":""}},{"uuid":26977,"type":"Text","state":{"id":"TextInfo","x":1,"y":0,"w":10,"h":1,"tab":0,"css":{},"text":"Řízení Robůtka","fontSize":19,"color":"#000000","background":"","align":"center","valign":"center","prefix":"","suffix":""}}]} + +// Add this as a file layout.ts to your project. +// +// Inicialization: +// +// import Layout from "./layout.js" +// +// Layout.begin("Owner name", "Device Name", builder => { +// +// // Add calback handlers here, like this: +// builder.Button1.onPress(btn => { +// console.log("press") +// }) +// +// }) +// +// Usage later in code: +// +// Layout.Button1.color = "red"; +// console.log(Layout.Button1.pressed) +// + +import * as gridui from "gridui" + +if (gridui.version() < 0x040000) { + throw new Error("Your RBGridUi library version is too low for this layout, please update to 040000.") +} + +interface LayoutBuilder { + readonly ButtonBlink: gridui.builder.Button + readonly Joystick: gridui.builder.Joystick + readonly SetSpeed: gridui.builder.Slider + readonly TextSpeed: gridui.builder.Text + readonly TextInfo: gridui.builder.Text +} + +interface Layout { + readonly ButtonBlink: gridui.widget.Button + readonly Joystick: gridui.widget.Joystick + readonly SetSpeed: gridui.widget.Slider + readonly TextSpeed: gridui.widget.Text + readonly TextInfo: gridui.widget.Text + + begin(ownerName: string, deviceName: string, builderCallback?: (layoutBuilder: LayoutBuilder) => void): void + + changeTab(index: number): void + log(message: string): void +} + +const layout = { + begin(ownerName: string, deviceName: string, builderCallback?: (layoutBuilder: LayoutBuilder) => void) { + gridui.begin(ownerName, deviceName, (builder) => { + const layoutBuilder: LayoutBuilder = { + ButtonBlink: builder.button(1, 1.5, 10, 2, 43594) + .text("Blikni") + .fontSize(18) + .color("#fb0404"), + Joystick: builder.joystick(3, 10, 6, 5.5, 11866), + SetSpeed: builder.slider(1, 4, 10, 2.5, 4862) + .max(1) + .value(0.5) + .precision(0.05), + TextSpeed: builder.text(1, 6.5, 10, 1, 47586) + .text("Nastavení maximální rychlosti") + .fontSize(17), + TextInfo: builder.text(1, 0, 10, 1, 26977) + .text("Řízení Robůtka") + .fontSize(19) + } + + if (builderCallback !== undefined) { + builderCallback(layoutBuilder) + } + + for (const key in layoutBuilder) { + layout[key] = layoutBuilder[key].finish() + layoutBuilder[key] = undefined + } + }) + }, + changeTab: gridui.changeTab, + log: gridui.log, +} as Layout + +export default layout \ No newline at end of file diff --git a/docs/robot/lekce1/example-gridui/src/libs/colors.ts b/docs/robot/lekce1/example-gridui/src/libs/colors.ts new file mode 100644 index 00000000..de76a121 --- /dev/null +++ b/docs/robot/lekce1/example-gridui/src/libs/colors.ts @@ -0,0 +1,81 @@ +/** + * Barva je jednoduchá trojice červené, zelené, a modré složky + * - R: červená (rozsah 0-255) + * - G: zelená (rozsah 0-255) + * - B: modrá (rozsah 0-255) + */ +interface Rgb { + r: number; + g: number; + b: number; +} + + +/** + * Alternativní způsob, jak vyjádřit barvu, je HSL: + * - Hue: odstín (rozsah 0-360) + * - Saturation: sytost barev (rozsah 0-1) + * - Lightness: světlost (rozsah 0-1) + */ +interface Hsl { + h: number; + s: number; + l: number; +} + +/** + * Mezi jednotlivými reprezentacemi lze převádět + * @param hsl {Number} Hue (0-360), Saturation (0-1), Lightness (0-1) + * @returns {Rgb} Red (0-255), Green (0-255), Blue (0-255) + */ +export function hsl_to_rbg( hsl: Hsl ) : Rgb { + const chroma = ( 1 - Math.abs( 2 * hsl.l - 1 ) * hsl.s ); + const hue = hsl.h / 60; + const x = chroma * ( 1 - Math.abs( ( hue % 2 ) - 1 ) ); + + let color : Rgb = { r: 0, g: 0, b: 0 }; + if( hue > 0 && hue < 1 ){ + color = { r: chroma, g: x, b: 0 }; + } else if( hue >= 1 && hue < 2 ){ + color = { r: x, g: chroma, b: 0 }; + } else if( hue >= 2 && hue < 3 ){ + color = { r: 0, g: chroma, b: x }; + } else if( hue >= 3 && hue < 4 ){ + color = { r: 0, g: x, b: chroma }; + } else if( hue >= 4 && hue < 5 ){ + color = { r: x, g: 0, b: chroma }; + } else { + color = { r: chroma, g: 0, b: x }; + } + const correction = hsl.l - chroma / 2; + color.r = ( color.r + correction ) * 255; + color.g = ( color.g + correction ) * 255; + color.b = ( color.b + correction ) * 255; + + return color; +} + +/** + * Funkce rainbow zafixuje sytost a světlost, a prochází barvami + * @param hue (0-360) + * @param brightness (0-100) - 50 je defaultní hodnota + * @returns {Rgb} + */ +export function rainbow( hue: number, brightness: number = 50) : Rgb { + hue = Math.min( hue, 360 ); // Zajistíme, že zadaná hodnota není mimo rozsah + // fix range to 0-100 + let brightness_mapped = Math.min(Math.max(brightness, 0), 100); + return hsl_to_rbg( { h: hue, s: 1, l: brightness_mapped / 100 } ); +} + +/* Základní barvy pro LED pásky*/ +export const red = rainbow( 0 ); +export const orange = rainbow( 27 ); +export const yellow = rainbow( 54 ); +export const green = rainbow( 110 ); +export const light_blue = rainbow( 177 ); +export const blue = rainbow( 240 ); +export const purple = rainbow( 285 ); +export const pink = rainbow( 323 ); +export const white : Rgb = { r: 100, g: 100, b: 100 }; +export const off : Rgb = { r: 0, g: 0, b: 0 }; \ No newline at end of file diff --git a/docs/robot/lekce1/example-gridui/src/libs/readline.ts b/docs/robot/lekce1/example-gridui/src/libs/readline.ts new file mode 100644 index 00000000..4372efd2 --- /dev/null +++ b/docs/robot/lekce1/example-gridui/src/libs/readline.ts @@ -0,0 +1,84 @@ +import { stdout, stdin } from "stdio"; + +/** + * A class for reading standard input line by line. + */ + +export class readline { + private buffer: string = ""; + private promise: Promise | null = null; + private resolve: ((value: string) => void) | null = null; + private reject: ((reason: any) => void) | null = null; + private closed: boolean = false; + private echo: boolean; + + private onGet(str: string) { + if (this.echo) { + stdout.write(str); + } + + if (str == "\n") { + if (this.resolve) { + this.resolve(this.buffer); + } + + this.buffer = ""; + this.promise = null; + this.resolve = null; + this.reject = null; + + return; + } + + this.buffer += str; + + if (!this.closed) { + stdin.get() + .then((data) => this.onGet(data)) + .catch((reason) => { + if (this.reject) { + this.reject(reason); + } + }); + } + + } + + constructor(echo: boolean = false) { + this.echo = echo; + } + + public read(): Promise { + if (this.promise != null) { + return Promise.reject("Already reading"); + } + + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + + stdin.get() + .then((data) => this.onGet(data)) + .catch((reason) => { + if (this.reject) { + this.reject(reason); + } + }); + + return this.promise; + } + + public close() { + this.closed = true; + + if (this.reject) { + this.reject("Stopped"); + } + + this.buffer = ""; + this.promise = null; + this.resolve = null; + this.reject = null; + } +} diff --git a/docs/robot/lekce1/example-gridui/src/libs/robutek.ts b/docs/robot/lekce1/example-gridui/src/libs/robutek.ts new file mode 100644 index 00000000..a6a934b1 --- /dev/null +++ b/docs/robot/lekce1/example-gridui/src/libs/robutek.ts @@ -0,0 +1,139 @@ + +import * as adc from "adc"; +import * as gpio from "gpio"; + +import { Servo } from "./servo.js"; + +import { SmartLed, LED_WS2812 } from "smartled"; + +import * as motors from "motor" + +const leftMotorPins: motors.MotorPins = { motA: 11, motB: 12, encA: 39, encB: 40 } +const rightMotorPins: motors.MotorPins = { motA: 45, motB: 13, encA: 41, encB: 42 } + +const leftMotorLedc: motors.LedcConfig = { timer: 1, channelA: 0, channelB: 1 } +const rightMotorLedc: motors.LedcConfig = { timer: 1, channelA: 2, channelB: 3 } + +export const LeftMot = new motors.Motor({ pins: leftMotorPins, ledc: leftMotorLedc, encTicks: 400, diameter: 10 }); +export const RightMot = new motors.Motor({ pins: rightMotorPins, ledc: rightMotorLedc, encTicks: 400, diameter: 10 }); + + +export function init() { + adc.configure(LineSensors.S_1, adc.Attenuation.Db0); + adc.configure(LineSensors.S_2, adc.Attenuation.Db0); + adc.configure(LineSensors.S_3, adc.Attenuation.Db0); + adc.configure(LineSensors.S_4, adc.Attenuation.Db0); + + gpio.pinMode(LineSensors.S_PWR, gpio.PinMode.OUTPUT); + gpio.pinMode(LineSensors.S_SW, gpio.PinMode.OUTPUT); + + gpio.write(LineSensors.S_PWR, 1); +} + + + +type MoveDuration = { + distance?: number; // distance in cm + speed?: number; // speed in mm/s? +} + + +export type SensorType = 'W_FR' | 'W_FL' | 'W_BL' | 'W_BR' | 'L_FR' | 'L_FL' | 'L_BL' | 'L_BR'; +export class LineSensors { + + public static readonly S_1: number = 4; + public static readonly S_2: number = 5; + public static readonly S_3: number = 6; + public static readonly S_4: number = 7; + public static readonly S_SW: number = 8; + public static readonly S_PWR: number = 47; + + private sw: number = 0; + + + private async switch_sensors(to_value: number) { + if (to_value == this.sw) { + return; + } + this.sw = to_value; + gpio.write(LineSensors.S_SW, to_value); + await sleep(5); + } + + public async read(sensor: SensorType): Promise { + switch (sensor) { + case 'W_FR': + this.switch_sensors(0); + return adc.read(LineSensors.S_1); + + case 'W_FL': + this.switch_sensors(0); + return adc.read(LineSensors.S_2); + + case 'W_BL': + this.switch_sensors(0); + return adc.read(LineSensors.S_3); + + case 'W_BR': + this.switch_sensors(0); + return adc.read(LineSensors.S_4); + + + case 'L_FR': + this.switch_sensors(1); + return adc.read(LineSensors.S_1); + + case 'L_FL': + this.switch_sensors(1); + return adc.read(LineSensors.S_2); + + case 'L_BL': + this.switch_sensors(1); + return adc.read(LineSensors.S_3); + + case 'L_BR': + this.switch_sensors(1); + return adc.read(LineSensors.S_4); + + default: + return 0; + + } + } +} + +export class Pen { + private servo: Servo; + + public static readonly UP = 512 + 180; + public static readonly DOWN = 512 - 180; + public static readonly MIDDLE = 512; + public static readonly UNLOAD = 0; + + constructor(pin: number) { + this.servo = new Servo(pin, 1, 0); + } + + /** + * Set the pen servo position. + * @param value The position to set the servo to, from 0 to 1023. + */ + public move(value: number) { + this.servo.write(value); + } +} + +export const ledStrip = new SmartLed(48, 9, LED_WS2812); + +export class Pins { + public static readonly Status_LED: number = 46; + + public static readonly Motor1_A = 11; + public static readonly Motor1_B = 12; + public static readonly Motor2_A = 45; + public static readonly Motor2_B = 13; + public static readonly ENC1_1 = 39; + public static readonly ENC1_2 = 40; + public static readonly ENC2_1 = 41; + public static readonly ENC2_2 = 42; +} \ No newline at end of file diff --git a/docs/robot/lekce1/example-gridui/src/libs/servo.ts b/docs/robot/lekce1/example-gridui/src/libs/servo.ts new file mode 100644 index 00000000..ac4fe3ee --- /dev/null +++ b/docs/robot/lekce1/example-gridui/src/libs/servo.ts @@ -0,0 +1,34 @@ +import * as ledc from "ledc"; + +/** + * A class for controlling a servo motor. + */ +export class Servo { + private channel: number; // the PWM channel to use + + /** + * Create a new servo object. + * @param pin The pin the servo is connected to. + * @param timer The timer to use for PWM. + * @param channel The channel to use for PWM. + */ + constructor(pin: number, timer: number, channel: number) { + this.channel = channel; + ledc.configureTimer(timer, 50, 12); // 50Hz is the frequency of a servo, 12 is the resolution of the timer + ledc.configureChannel(channel, pin, timer, 1023); // 1023 is the resolution of a servo + } + + /** + * Set the servo position. + * @param value The position to set the servo to, from 0-1023. + */ + write(value: number) { + // map the value from 0-1023 to 0.5-2.5ms + const ms = ((value / 1023) * 2) + 0.5; // 0.5-2.5ms is the range of a servo + + // convert to a duty cycle + const duty = (ms / 20) * 1023; // 20ms is the period of a servo + + ledc.setDuty(this.channel, duty); // set the duty cycle to the servo + } +} diff --git a/docs/robot/lekce1/example-gridui/src/libs/simplemotors.ts b/docs/robot/lekce1/example-gridui/src/libs/simplemotors.ts new file mode 100644 index 00000000..c6f424cd --- /dev/null +++ b/docs/robot/lekce1/example-gridui/src/libs/simplemotors.ts @@ -0,0 +1,47 @@ +import * as ledc from "ledc"; + +/** + * A class for controlling a servo motor. + */ +export class Motor { + private channel1: number; // the PWM channel to use + private channel2: number; // the PWM channel to use + + /** + * Create a new servo object. + * @param pin1 The pin the servo is connected to. + * @param pin2 The pin the servo is connected to. + * @param timer The timer to use for PWM. + * @param channel1 The channel to use for PWM. + * @param channel2 The channel to use for PWM. + */ + + constructor(pin1: number, pin2: number, timer: number, channel1: number, channel2: number) { + this.channel1 = channel1; + this.channel2 = channel2; + ledc.configureTimer(timer, 50, 12); // 50Hz is the frequency of a servo, 12 is the resolution of the timer + ledc.configureChannel(channel1, pin1, timer, 1023); // 1023 is the resolution of a servo + ledc.configureChannel(channel2, pin2, timer, 1023); // 1023 is the resolution of a servo + } + + /** + * Set the servo position. + * @param value The position to set the servo to, from -1 to 1. + */ + write(value: number) { + console.log(value) + + if (value > 0) { + ledc.setDuty(this.channel1, 1); + ledc.setDuty(this.channel2, (Math.abs(value) * 1023)); + } + else if (value < 0) { + ledc.setDuty(this.channel1, (Math.abs(value) * 1023)); + ledc.setDuty(this.channel2, 1); + } + else { + ledc.setDuty(this.channel1, 1); + ledc.setDuty(this.channel2, 1); + } + } +} diff --git a/docs/robot/lekce1/example-gridui/tsconfig.json b/docs/robot/lekce1/example-gridui/tsconfig.json new file mode 100644 index 00000000..8cf82b10 --- /dev/null +++ b/docs/robot/lekce1/example-gridui/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "es2020", + "lib": ["es2020"], + "moduleResolution": "node", + "sourceMap": false, + "outDir": "build", + "rootDir": "src", + } +} From da5c6654ffc746e1982a9f16f3503089c7c7c2e4 Mon Sep 17 00:00:00 2001 From: c2coder Date: Mon, 8 Jul 2024 14:54:41 +0200 Subject: [PATCH 13/14] Add gridui docs --- docs/robot/lekce1/assets/add-wifi.png | Bin 0 -> 21724 bytes docs/robot/lekce1/assets/connect-wifi.png | Bin 0 -> 21365 bytes docs/robot/lekce1/assets/wifi-passwd.png | Bin 0 -> 21277 bytes docs/robot/lekce1/assets/wifi-ssid.png | Bin 0 -> 8637 bytes docs/robot/lekce1/index.md | 22 ++++++++++++++++++++++ 5 files changed, 22 insertions(+) create mode 100644 docs/robot/lekce1/assets/add-wifi.png create mode 100644 docs/robot/lekce1/assets/connect-wifi.png create mode 100644 docs/robot/lekce1/assets/wifi-passwd.png create mode 100644 docs/robot/lekce1/assets/wifi-ssid.png diff --git a/docs/robot/lekce1/assets/add-wifi.png b/docs/robot/lekce1/assets/add-wifi.png new file mode 100644 index 0000000000000000000000000000000000000000..1da617524817662453cdd1cbf8b7b03014603599 GIT binary patch literal 21724 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 literal 0 HcmV?d00001 diff --git a/docs/robot/lekce1/assets/connect-wifi.png b/docs/robot/lekce1/assets/connect-wifi.png new file mode 100644 index 0000000000000000000000000000000000000000..c18d82a425d40f4b1a3fc77743edd653796d153a GIT binary patch literal 21365 zcmbSzbwC_VlqUoyNCLq%K!5}b!973-!DVoQySrNg1eYYZ4eku??(TyQGWg)`dc(JO zx3{-@dwa(p&|NguHT|ky|Kt^+it>`!n538p2ng6x-@Yg#AiPk3A9tdo!oMrJ(u=}> zAUlaksi4EZywQzA;O8XH5*p3`J5y&j14k1CGg~_w6BZ{UM-vlUCv!XJBcwKA_(hx+ zDjLq>jwS}q7IwDODi$^-@J4u-cAXi#teqL%ktU9=CbmWqJfJ1M(MT6cXq@pE}Lo2D%q-%VP@^& z%T2XfP16NXdVH=6Sh@+46(5(^VC9dAj?U;`84!X+&Cy=cCej;DlOu02(~=HbUtRy> zH0s@!?ldZxTn9b6N>#c&-&y~H{x{Cf1ai`1)9&n=zUc)f#_p$QXW6Z-l)+sNQsMZ|x4$k{Dpa|?x#0?AnD*0+U7>rv(L`+5syU|s!^LDu%-Up`qS{d%~7YfghaKue>acB&v9BSg=L-l z2-uyJI5(YQsdKu^!N9`0VyG7#j$%{zVI_vJ`J{@~Mil zfdOQ##@ZI_Nm7_wgW*q!Xu+%@zAdzj(CXCm!4h6U?N7(IkH~p(%8EK36L)q6UD@vf z%J$`N;#rK7$P4zTH*)EfY)Y=dmb^?7a(6?0c=1(1X*X&J5&1d_&tZ?|H*8mi5j~~!XM)%F@-qO4u2;}9?oFp_-tHP4e}6;u6iU$ctDKJ2 zd0-R^9YFsz4>)GWM4|Es+ukpZC(=kwD`SC;|2%Ul zm(k-5{7JmFB0-$^WE1L-qVQrYb@9)l&#>}Cy^5gwW|iMam&W2%6iPNj;m7uC&-UP(YA;)fG&A3H z86+X<0qr#rNmx4JE)(XrxyE12*#|1Y@&XhzN$KZ};rl{lcOrX!;?zhpR~tTwU6>e9X$ zY=0QSyq@B62cK0fs#q~Ww-r8{++m!chTBGS`8OIC;6e&riG}M=^U_B@TXu-oqU#K^ z`9(g}z473sGFRXk$WycwyWigHl?WL%Y*uOm?3TuqCa29g0$tGFm zNq!}QyH9|Ua|juItn^fz`VyJ?C81G_T+{~R5*6V4fvhgyaq&Z!8hoLCA0+`=MiBz0 ztB7cCA#*XlA(hGC&d{v9hu>B^N>u@IGB3-2Ij=V|vF7(xsM5`H+4jB08z~U(FfMsY zrYe2|!66w5S?<%U5JEGX`uNqwp?tm1FUCx56$blRxQlF6m*=FN+Sz-wPL?+-&!wT@ z;)|-S?3-DlCQ*!-O)_7`gd&p9l?*Io8O~p;WQ1HZs-)RZerjgnLfPZ}zSs355&cDs zG~eMlO>-_=&w!-g7=k)JjvT^6DY3l$<=0kCJ^v*p1QAJW9GzE4Wu#$#Tb^q7DnUg) zcBNFV*hq5F@|HO+P{CDol~I>8ccMx)1$R8to$G3U5SFh_Q(Q*HQvZ!D3z{rWs->P* z>P)`dIeMwBnIU+Pjj`I}weX5o>8Dh)mRr{^o=5uYZUVfvrG2Fs8FWSrNp0l0S`=S~ zr)NLad%b3g8Je%NAT!|URy^(+XfG(R@X*qR*wPZuXGV=cS>8bnGS14a8cf}V^vdKi zN!8~r3+FnLK_(gjjSfqy;vp>GMaAYVRsl+2*M4))1$@o3&FI7j2>bPJas7HldQ;p@43TmUcg)vGL*ET>j)foK9kM&xqha!ZjCGO% zR_0!Rcia3pT+D4>kE7_7QAbqeI;|G5OL$Jgm7J+_su6bylZ(WJzf7zIV3xM34xgZ) zrC$@{Nmlf-27_|h#8bHhTJ47fUuR!&knBikAi^pFawx87m{p)T6i@x~?gVCkH7z6@ zwE9KtLm=^))#IM8<_jr$A22r3%6~42)tq$miASte+m}#73AXkL!z!coPfz+XY@0u5 zs3ytiM*iex5wbTA*q-VX)6K;2lH&da`N)r2V1Hem*J4J}U{Y&(r(Ac~#RAA@Kd_xm zFXX{~t8%tb9GwOd0E-ToD13cSh&CtpHKOLTxg=!4Y0hbd*tI>mDIx7f9#SJhAhJ97 zlChv7lb2;-RrtB1-?o5nB3Nk9XPbmAKUnec z1rJs5eos&6;##i$=+0@`2Y+bM@utc#m4y`nU5R5+Q?%DLFBy(E8VUrGWo+ZHtxZq= z#=JkZ6c=c7Dnq|InnQv5`|Jy71+P^r#{c~sRKt~~WkGgX$T*lgx%;J%38tUycw1v zKA!c;wiEcBBDO}V3n;2ARQE7Cu6F1&c4%@x3L!>y#qE=Jd^5_MAo1n!wI-)`!K|*<`4o+SiPw-~k&1&C zN_^Jl-T_Jp6#QmP*edo_C;gArjekm7hG*t^J%ZdLY~A9 zmteLW0AFh0NGnUy_y^?ZR4wax%S!GtQ@0xuJr-c(ljWzBLN>wnt2Sd!E$EtxhmnK( ztDITH8kkPl()cf?+g}dGOVRsf)rPoHrfYF;M{sggw_pH)a%>%IRBH(VGBL%IKi-0@ zd!N+-G9NH6IW^tRyyjUhc$@E!mETXLaxa;eaTr~k^hxF_dY!t6wD6b$4ybMo)yOZV zAT1VqN7%Ux$%iZDnFUiYa=u$!)hYc_TlKDJ5uZMXVtkh|t0c$X81z_Ed1ogVUKic# zBCGd>ZtL#MH5yaG$lC;`Ogb>B?5m!tQq;2n{LR%&D|AkI5YM9)2Z||!+}}Bp;fssw z+nemX#S7gYBoW+?DT|DX&oimoNq*tIsLjqKH!)S(Ua>mGToiB>Q6)CsfCpC9HWgs& zO+1VlX@r?_H8E6xJd(prB=9c{aXooi;JGEk->k2XNSpayZVRnn7v{43!T2z)$lR~UKz*t<1WUZac8UZi&S}T>jF~6d;AJS z;B%FyEjE7QA>t85TDKdPk;>_aofV-XaGBupz(YocD5oYbyU z8#Q%CU5GxI4N+Lm$t{MTb2;#8H}q+O`E(fe#ETZ!5Q4P1{N?IU{w2sC-1M$O2%?k} z13vD@l=~*x2owayPp~7CIrSGiTqxO9t($%1BqJPhIxDo94fnP2{;aePZV*}GlH^tN z&djQMo_ek@a*s*RYdrF$gt=8;cdLwL6}g?vk&*>H9QppG)~J>MRCaGMo+(#>OMH}W zJBX;R&HLamgYJS8cN^`V(RNya1)Dg6EY{x`}&Q`z5mNCFKH3FwV zB)(%SQYto%!q`p;IiOb|PtSm+xXzffWL=327cYG}>a`I)7;L)cF7+<1%|SADP+9&` ziDL-~a{$C&6J1mqlKL0l`Gh6s_=T{isE$*hoVGVH`wFRTBp_f(*7knPT12_~GO~%7 z|JafKYEn@TAU_&)At!m@8@#(QVm*nS`!u&evc;*S=CeDYvU`eEOJUvT+tfcA}>qF8LLCXGT!4h-s792(IVm*#yvI6hx zt59#ViZ8^sA7sY-UjF60HqQ!&`>Sl*1MGN9X>eUR_rp|{P0CV0NU>dJbnsqOYos<< zA&L?5elFqLmBFmN6$1wenff0Ot%ahjmXamV>S@P%{ScCDpW}RuFVQbTSOY5PnJpp2 z_V<4?+nWKgC$TQ}hu5F0=zO&|yE9@ISwHN0LWSKiVmzaNv5CElH$+XQVUNCTX*9{3 zD$dt84O={EK*aO?slVACC{HdO&vdVTiuC|DJM?8A$BzQUhfwR);NT_Mou3ER!qj8* zcyO%6%9&LA4|n55`|k(WX>@|m_YkWWMOm@uRV<{F{O2@PDpz^cKGjn<6E&l;kyJ!P z1Viy30%vB=ORl1>&U3GWq|cqgo|5udn(#~(1I=ZFlK-nz^Z(9H?K`?+x|kv`%25ZZEq$R=aLw!!LPZtxDjhfh@CLyc}g#PaPZ|;%v(F zp*t$pwTJv(FsopYRq&Vf{O|Xzzu>DOTy%YOl-maxu+==8NG+g-#whdV%tRfr z8MXYquwk_VlpB$*>1R{^AQb`7oBivb{^3doey@XPQrhF+##$stwQtZd0Os465>N`} z-4_W;#EcBhjjGln4kDp>H7WvWywjeyu94JcvtqlugL74n#s!n<4r!yY6){w-TODb6 z5-*W1dL4_>{_6J1?Y*?#+U5s$zOwN|}h}@JweT z{Zcj6-7E0dFZhHR!Gqs@bxshej+p6N^8_+R&n#IpZpyyn^f;S@H+Z zF%4EWc4R}syH^V6%=SJl-7B#JRc!~$C^k9}d-s{eJI?E^ly<#1gCf@aeK*y$_q4e~z<04t|*GuMh z;|7a$$+(1|)!#xP4jhNs@DG&Ga*L>L27=YpwdHnikz!05GsJv%!=BLbp05Xoh#Tpf zCm^_bH-z<*t!?Ai10(xvrqG-jxa&i5RA}SdiGf}gG-$9?}d|IGV(~Yn5fZBnT97z)r_`Z!oiO_uhq9$&I*s zO297DPMd0qpkbq^tMK90U>V}EhU>%m&!X6j{*@mi=;&5ibM5-o*%lk^C7G_9Io5Z# zKI0dzqgy}I9VVK(v8 z#Rhiee{!3oyUlOu%L_yvM7yv>$FD8*+0ktXR{b>tuv4o(Z{8!Zoxbl}h;Eez(kG&~ zkW_)6Qv0SGwHaLf9uaZ#HvvYqhTF5}T+ltL_0`gw=DR5`;Hw_#oZW4!r z3*@;^iFb>Fm_h;$ci=JmtLZ|)yQSA^El;l}*p~W#@>N~8&Or+FMnaig%~IUUy%;zJ z+?;4QgLsdaTbF7ACJl^2_g4FQK?LJmL6?2X*Y}CoSZ%ODuDq5tVIY~I(Md7KJRmbu zt^kM`js*P!_NcXEd8r2+05(#WdLENdHb94C%KRkRGcIid^g76aPt-_&jp^p=>$C_N znf0oprvVvJCl5%2e^PjiPFh5LUZzNj-W#{E`heehkI}(;e1R=}=D?KsooK2McnWoR zt@9gYJ*>;WqBn39+;p-h&!mP{Z4HeK`AjjTrmW`kfibmze(10hpD|wA6{Izg*8#=LL_hibu=|?pPB2`jF#W1 zQMC(|>=7i58ETJEH6pD8@m!N(d*o;SM>ngdrd5!Si~8I`n0=y=JxkCO>hh%Zt$3jB zc&d59o_u-4O@`)A~DRYYHeA7D_5ffoDf6J%}^z`pTB9^=D1P7w^Q^LU8y$8}sJajM!X0nCYBN zVdam0E+~tW&d#0Ka~*r7<*r#+z{_P=z)t8?trp!OiD9j>*m9j*HZ=HDKRVqtwSbxW=o;8tC>751~?N#79DGam5+uMJ7$TJ_rJaQR}CUB6j};@wD<#FE(kw4Z`-R8@`?kt$5IRSH`HJrY-o#Rnr`3inqmrXahSH^LvpeEyMnD zwbdrBMJdV-H&p*c&DTryMyhWL>Smu-(a~v!#WNe_U%(~mkhWkEZkv4blRfU#zQZPr z=^(Z_MMIm)7S%pRf1x*T03>%+FWHN0w&iUv_XQ)0X&>y1F3!n?f}(nwu4|@HU76G* zg5~p9rnbi|Y8WlmC(pJQ6AIb^I@?i)Mne^si_S*OeV5%$hr9fd>YAT0-o5?KXG$$f zisI}E3EWoFjNFLT-kxN@@vHIYuNIhi3-kRtxua4mU3(FzeAg zYnj(e@;OCKc*ItHpB_&ccf$7^ng0#lAInOf^9GihkU4Go@ed54;x2y7?VM9a?~Sg0 z(lqMu0fDFlj9M?m#g8l0D>>eW%hNKjm)9`Jr~MS2jm}h66ZHDP7Sc2*bde^ zq_wt*oSPTIG9*k5UZX}YC<9NJZD}v=7wZo%cA`;%G8b3BXgdYk+_4OMUt+%y_DCw5 zv0mGes=9IaPYRbKb{c80vzd&`fGRCW;);D zZ9RIiH6{2-?+3V@EoUthFbjz#O;2q+z?54|h3&lL%ueyo>NQO|wA2U6!ePfIxAT&L z&Z>8s4`;r>rTsXA7Q*@YihoUahAGW}s4B*QsxbKTSGgtQfP6xF3 z82V&9kp@nB+>$gW*B{XtYdR{W^Sbcf@>=RUVDsr0lE;?>a39a~k)+z?Rq1y) znl5^XP(@5C0lY&T1rpiDPo51Fq5wP+5)Y{@8Q(M48)(P+tyv;bQh)YSU8Yk1D?c`z zvX6(}n<#?uKyUA7@OwgZUiV}p5Zv1y=kVH?;hnVoE!inL2JKuImnv+*j?*XgZq8?0 zEGqvQWE0JxXnyp4KP`b}suBL(_nVC;TKe_gSv1+mgZdXRL^8O6H^~mhLO;zcvOoi0 z*=2iwv^-(4Ym*#wipq9q>)jONwq8CsRDYwNP~P{kwPEX5tV3iuEtP zVqP9?KxNc_DyY9XsB%@`DKmif|jP~nw$rS>rpJ)*deT=PNNa85aTBZQ89a6!zy9T^dfAoj zwUX8nH4O2z7Vynjsdi;XQ#;uxhAJPL-$$1H9);jtS@P=zwThdkT`=v~tP@XdS+x$- zr1Ga+Q37PO{6LyX{ z`?PVOCS6rUS}Sl*Q-3>JxHt}aM>+H9(z_`{p>7L&SvoHpb=uCfMj=d+G777L8fpKs z5|lDq96O{{JKUeakQ4jFOSgtukoU;NMtfzg!{Rg9dCmdNi%L`{cG_PJs8&@O185b?+}6TsELn>VFT4!}1LJGTH4-jxNcs4G5*g^-R3m zF1=$?rj11qrccDZ?~b^zlDPbJS=#8^j#a!w>qfVJg*cu*@K5q}qe(v6wti2Oy;I^D z=c#-Y{Kgr;&D~x&12(yLKBQa@_?Q;y zZ;4*CG&!CCZ4VW62J3hF2o(%KI{zxf$7s!8&xvJwjnkOnzh^*V3X3mS0(h)IG_<_5 z-V2l`NgS1B?dBzT&{M}CiMEhT_6{aDR9*9{%fQI;ISx))i8%=|YuVPjHcw&;{TsXo zoqKz};;mtGdE8y!#{9bft3JE$GN)!BU9?!(pzj$6?os)r;rw~KZ52M>xQ`T1 z_ofLl5zKynSr`!>*$uT=JH9NtHt%sg)yCSm@hZ!WYwL76y2^E4e*x5~i2l7A)8M!N zs?*zLo7tq~>DZ)Iw#xD5?2P1lTqu7Q*}(%r6#YX#L-_1(8^`JWX)kgP`lt-;-?%e> zoz>VhyA?F2=YnK6bN?tI^DmTV!00Z$PdTX=2rGp82D62-srhc0({151D#2g-rDznz zoPx#1H(>n(W+RFR=W>Dhj?uwxAhJSTHabi99)H_1T#0j`omh<)z{hoqZWW{I|I;=hh(-ax6qC2 zv9a8gcw_S6Po3~U?9e&Fb)-iKgXi?=bje-AMJDX#LsOo?U{&joIk2UHL+VJVN9amq z(9p?_GLbW5pfNA%pp#eY_w2Fo0m0SD;wyq;y|e)xT)~n1vO0j{r@-37nb2Jqs^jCX zu`2#P|05UN6Z=_ZLLnC_Fwi@gRw&Q@s9gU|Q)pIqy5{kk_@e6-))p+-aWBW}1Q2Ul z)^#)GS<5|^p$FY8hgYrSQuhK^v zqXp8v!&^YTr`I{l=06~#*&epi%njjsTcuuR$EL|dJVK|`NH3<$YyAdgtf~s;ZZL2c zMFOJnb!g=zPhXx`;V9IkTK`Hu-Mmk4y`Z_$UBDNS$Sjq> zQgo2=oRCaMb zsZO0nrwl-fom)hrVDD4Zm9X~UBwKvNdAJF)hZCvJk_O$r${q=%4J~*Eq;KaTBrW$E zTBac`>n zk2N{8r^Fm*z0{O$X~cSWW?9Y$+PH5FSo@)<!rKOdc*lwVOKO?qz zJM3Cu+*QO9tC7l!>3TNMNayc)Ve9&*1&m1Of^g92N;4m0A!* zWw;Y9kRd~!=KO)?4ws(MhNAe>V234*)kVHFyPKiC+mmTDRmQS)*38h$fUlh~uf*=B z>s#~}Q!A3jIJnOmf`7`3OLBO00tD9|Fr(>6jxVBDg7S4YPlLPx60UKZ)+2}+0`a4V zl}({Lr0s;_Sdihk4GnU4lOyW+#}^O$Udm7q{^pKI{R}79na`=V6>v0Ri6f9uSM9?G zc@X;qaX6n=aoId z#mR+4QknZ~=I;fpWXzz#aJAZpnb10nPy%Hc6$zNvMP3D5Jgok!t{kL5X1A{^w1d-SiifM&lx2QozBCG8Mm;qvom+9!BI_~}^i=B=^lJM^t}Z2gHF6?99b}V&|&orRoGHk4apbr7l0M1_whPj$PF^T-_}d^#(GrWo|M|%$9fNUPII1)jwZry*b1EsQA~d zB`bj_aXFr`!Zm!z9CxDZ5IYvRT54_uziOLz(|a4k?j04~LFp5~_9^flDc_ zp!>HVEQA!hk3Y3&@5VHUa?VNR)0469*Czmb1^cv=h6CG=&KI)pl6qhEvc>YpU`T#` zWk88J?NZ8iPxup&n5npYmZY$DcgI2NA-}kwq&v3tPXctwex_;L(h_uieTzh(U#Gl& zo~QGh$Nq9%ahj~~qvrd0u4uE_H{1}l1y^2N`3Ra-5MQ&pVtC;YFX2eYmn*XQ6>m$I zx{)nL7kb&%khId*WWu!;ctbSS~U>9e2dBX>gUNL6&o zwl2-*YSyWrJ5!G4ENW92W5i;RRCL6{`{>#NY(lMd)j^Vw`2i)#oUwRUyh6;fP=BeU zIXLH&5n`(zm7gz)5*FV+{@XPDDW_7k(#MV8V0N)1ZWUNcB!orW3vouIuEY9 z6f!cw*h2%~?YGR_xD5DUgJ zg3L7gWUbGzNSjEYpiSdF$#$%y?7Cx zP6tjCBs-txiU}eO9k_M9txxl~xA=8rC*p2Cb$#PeI3ZI=(R+1QFtXNBzm zthGkFl@$)xZZ@Znh$;ouN_|lA#%zz1MtnoeX49<$v;-xhxQy;j|LwaP2BMc-P86+z(ONB-qmx@6 z%KP-S-nDt=?)7PL=ddxK2!ZFk~gZ;K62N#m9)8ep5iXh1-V{v z{!wGjlg=F0=XwgI{^~`1)p}7D<95KZ9vN5aDRc!gzQh#5mG_D4S;ow3HjZy=N}M8{ zg*BM&Nze2w_NCr*^*^21=xW;K>E!?eigCE~>fvgTjC5#3M64eq3>8NQoo6Ij?z+*; z-Oh|7+~W(tR*-+)J8_-hD8kE;5mLrY%yVaDY~*!f9Qb8^eu z# zvyd4><%b8~tZqEwu9&keM<1($TviGA5U$J4ntXvhnd~2L<8EYa;zoiM+IoA9YG3Ni z^8FhFtDf?&?ICBfn{NFt$h&BEGb7EeR$rd6wU*iw!E)+fHO1|*b|}=h714>)eMCbR zo5hxsH``Qpf2$v~TW4W_o%+wX`s&qKiR>wqCvYt&dZ)AcJzNF}5m`FIRH&f{=1^&h z2IVf1;LV0b?qRYy>%d}TQe{n^O1~3kn65jh-(p0DW=4*Lan$norpzcGIIpSf?JRKl zr|P^cEM_tqy9H!QJ81OlaRf88%EF!LO?lt?cCNjEPV^Zsgnx98khWqd0Wb^ovWv+C zMaHsvcn_SCaUp@7ErQdAUYf!mH!)#zT&t#R%F!-7dzWe{q|>VaJ6sh)2hTGW1Z6;2 zR^tAb%56%#ist+ zL!8{_#cb6{edO(r!){cfKe3k2A#h@o+gMrD^wzT$+G9su;!jb3m}Ot@N=p?kHH}Ic z&cd+aQ`}UD4vGI)iiVUCM%=vwu+35^z>PJlxCKkx$Lh#+>r^F?Z~j#87V!SWo%XIc z%5f-%o7v}9Zmswrkw41xF}%+Hem$wV2J|9dYm95`A!exdKA^5vb>%-IW*O!rkNX1O zP4Mh_t!`2@JM%E;JiCkIAsS&=5DjhFKvAa2!hYyZmQKwL)sL(H?&PVAqwB} znc06fmp@`zZw~6q*8qjV>87zRWM#stEAk9w}nj1otIfZZB9%$x|;)0_Sja5dyznW&9eEPS6!PQOgj{ z5NLVvQcAikul5Y?yWq(jt|c_=Et51SZS}}S3-OdOv5v%MW z_?;RSKf@!``vAH;>Bvfd2hRv_>F&qEs?-iw>RhAH{&3BzW^+nfZpZl%6xH5mRgcUe z0=u|YQ5n{)_IH%cLJ~o}oD`3&=h@KkDxvIA=`9PrE3D>u5OX8Uiz$ElsH(;=x=&%v zGBy5bs%^lx@crF{^S)HQ{4c1<1NyKOT~(g{!TSd1sK_qupQTIuMx)=dtCMPE;I@XV z5zqu~r;tA0?{y4~@sYzA?+I~9>U8=^GBt6^X%r606z;4r$Ul_S2#^Cc+Pt6O^j!FM;%n2^B^zPq1?iIE!2zX zcLf2sY;29B2x$qFAY1B#nGJ{bz8}jLIVF`AiWOc#LDA4b1k?l-F61bPTceIKK_?Da0J;4HDRTvgV0%$QVWBY_Vc{$4Bur)P#q(A#Rr*P!g$-;W($-{l!Ch z0>q)Zv-jSOcZXy}PR_uii!rUJG@@CWf#RY0C;kXg-UQgp9t-H9=eS*u%B`%L(^?M|pnOhfOSvv0Bl(m= zh3Y~$78@7XkXy8`9<4U)ZG{=Xe zX*23PZo8edB_teFS&kGb3HX%Zgyhp`i68HT80noyR2yWm{@E}DN(Q6VG*LQafPlf z3FiPgl$qducyazgD8MY^88+_b(SD+&Bz*(I0ZDhf#*=U^P^h|Kbj8Og3^o_>d_jTv z_nQ!j#1#~R7`(wJgCi! zQo*!u;$v^buB>jd)f6+^dkm-alZbKSnC~n6WB(!Ov$Iw1+KHWXUw*X47tW?Kl#Wmz=S6x!$IcQ*jFtGlHuY6F-w{pkTA8sbkhTj-P0l*~Dn)r>VbG`89yYmb2@lqp z7`K7AGVkD?(A~m%E>jox1eQB-enf{deS35PSIpVN7RrKe=jq=ErjM4`hcL$S#>G>j6)8UXlQ9S37>sXv5+&mHXDX#hvGlk ziSd8mtMQ**L8SN}Bb4`kVkSiK|60ByqZ9TViF|Nl1GfCQ;ICdr zuEolfJ-Z?scrM0zlwZVAh}4kFv03~wdaP}ZpA+;!peRIiQD~F?Ht_bd2Vt>@PPfAR zw8je~_uXHij2@;C35i!3rF2FI>Lu!)9KITy2Cep-t4xnadSve_8pNbFE&xf~S!kdO z%-(Yr?^<_0ulfj>ZkqJ`ox-u^S0RK}o{_bKnFWvY2bQTCyVj?#XiHX{(X!1!1>>L3 zxoMNg;y)f0e<@1K|N4`2et>RPN8Fi6kNc2wZJd}G^6m$PUMbCK5brH=soA;%*#+WK zC^jq2`{@vPaACU1{T*-(yLHZU20-MzXRUjo*B627W+=0|SSSW+L)pUY%@q z{<3B%K{CI*fBbzkP)~9-!PFf*r2FurxN7F?lwLu%xtK*J-8bgiQXnrXJLzeSh5X5# z-p-~NP9>d2oSrr+S)NuvHU#b$lzkDQwA+Lm0X1}K(=vo7fcU*ep@)0(s6$ZJ?6_|A zx4YE4cy+F+JvgZ3^Wg>y@f<^%U1H~0hrcC1PeJvIz8S&yTAip^c{Zx3I}+U1txAg* zD=hAI?!jJ1EeU;fGaOq$4Cf1Hmuz^6$Lg^oFvWogAUyZAy39E=5LcQ2P^bM* zY27CeeFTKoSy|Y8E4NyUV9a|Cy)A|j1j>ddPc8M_Dt1X3Yimgaa#AF5&+4~Ec9_$` zW=BsV`lV8!(a(oxoX*?n>B zbTIc%xymY91X6o`iq(e^m(Ya`iM=ZI}z2(Rc^ zbV9D8Am2%u*D`8ubwtPNH1LFz;))v-dDQu(qR+w#!nr{xgaCeuY4ME z#-PVomMwX#!$W?CT?p-cr6VCi_-=kY~CupD0uSO;l*v zn5*hvVP*bGhzr967m7vpkb|a;j+M&u52str1B!eiC4mUL;Fy8No3EZVm$`CRA8~9)yHR7`(JOwUi`vdW`Vu4#nOQU) zo7S8aOk*o1kT^i9ck@M5{tG^7Yi4>YDtvy)*AKZy{t&DqAsfp;u*46DYXv$6#{dzR zr{2W#$Wj&U4)zy2Pfm>8sTi5LX8!Lm>q)@(DwzRJ$m5u1K3Z?FES58c+c^$Tsj4dL z#1H>X5TTxM?p-_i%mXRqaqK*lYf^jsy`}ZT<_w!&(aP8_7YV+8xJ^6KDwYZkUy1FG z-IaID--*a2Na3yvD__%XSu7=gW1!rCwH)py!dKZqRLsa2@!N2ZP$uz zjr)K55>II|4+-7f=Dpl3=_8tQe!4w3#))hSs;{ROI|K09uQBG5#0~FqyaJHDlxll} zsAyC9di>KHY_tkQ92*2tVMQe+3}ha?h>`C2vDPf`lH196qM)f?!mq`3_`7(`uY*eK zY$NFeiRx(DVQE7t(DDuEj=HRy(dNC`g&JhZU*^W)_aFRb4&xtjfnEo~?>4r#}f- z(c`|mc3TwItMwE<12q2&*n0YaKR>8VWoKPaEAOU{7P@O+jfUSZNTNGh_q1`O_8WYH zv#eeH8wEGAJA3dt#f2CU!>E#KoS$PTY@-Qyt_az){3bJU4gUIl)YZGO=%M7%LqrJ- zU0I(!VvsLG{a2*r@d9Ctwn<7jvoAjG8?eV^S@2nb3gd(*z!j);?{=mKYi2*Uw}r5Z zD5)B+_hn@)Sx-y2_7WUcWzX2^iCfy(IA@XO-7~&&0B*oEpAY^kdvG%lc~`+dEstL9 zhutdA(mwy$NkWz@D zF@CXz$oM95!~(X(E)f2Mw$gHEUPjR9p+ z{IAfwj|lU-w=Pth66cRPL{z*cm%EKO>dE z>$|2jjzW{7fAp;^%Q`1|&5x|lqcwOT^)LI2Wseo<6{O|d2uro)w~_5iium2z>HfO4 z_98b0uPYNAF{+DQJAvc zzI0#amjm}C7}gH?Ttl=f!`c8^tN};=OAX1WamF+JXD5$vOfnL zgj6VZx?&sOBX<)DZ+|PwEplZ6N8t_%J@kQ|oMcs>1_1&2s~5|nr8QpuZheB%k}tV# z5{$-$Ai<%Bm51={RqaW;DO$dTz1$)m)!=-~$V|^?O&e0z{RhYVyW|)lVo$ zm(WcTITq7UnN#hYSSa+YDgL*w`TN!Mh!xG?Nm`6BEg3XBx4)xl2404g2+66OzWh{O z1gbZi5SN{|nl^g~2zX`B{}*8FN^gl2B(O5}M~cGSGEdB6E{@8~ITL6`RD1Znb3Q>k ztNyma4fcI&i4FV0&NqX|??hHI7Y2mV`6+1e1wh+WKxAm0i@HC360nYZF z=eNHw8mq;qGs1bR zZKTf7gbe8eU*W!v`J3M#BGc{ZVzy)@M6zwFW0{^+VR(P8P7>!5cqpz5-xE^CNiGn7 zC3H@%dr6WWM06Vd#Jm{9W~f>8IO;*#5_QnQK38oiCE!F57D*P6Vu8U+IGi^w%P+KL z)cmKuCM(#9Tm%*NxD!6J?Xuas!Rd^b;`yfPIIgaosv$&fccvtTY28&rts3T9QSz9*J(A zyrjHWTp>h{MEIOwAG$bD)7M+!U=0@FQMa1q&#}>yszNNAFpxw=%EL0vap4=W+zWFW zrUZH)66_PDcA1}TFEXw#%ogaVMsNJSE#*V?yED5# z+_eit9wiYvy5@&o@+T@&;*|U}}$hoxkKmt?jQGe&$)kp&w0MvbDqz$etJGEXfW19 zS#E4B+BEhTP1b}A_idB~I3j)(USNIpSn3pp?(??6q*V4q$ zX#V5pa_9W79?mAn*R3yZ6;E-Cw?(9@Hs7OK>;t;*1g3U&|8~;mvh+pv4JA?EcWI$h zmiRQEBbE^oSRcl9Th+0ov*PCpHe$=Mc}P2_aJePPe)lQ-LfG^-_sxF<9*O#f9>fh7 zx^30nx)ps&X(5243cq62$xJuRU0rasyB)*ozk@GRe3_|oDOEF<{WCDSE`5A(eI)#$ z@}R(eXpU~)D1+zC_#6H%4LxQTip13CydOxxK{upcVoR17n6Il4!I#=Xx4?GX>oHTA zkv_!jWFBn?dP#W_B?ZO7J30TLp3p=2E#pG^F^Fw?nrgxHSQ1yAXA5wfiig4?TeoyR zZpOA&*YV{@I>-v6lv8!ow;6L2qx=231Ude` zrEY64J68<7*vTj~yb>!FFv5@Y4a|{kxUlQU|IH6qq#C4!zA7JZ^RD$|5AY^vv{zAZT)qHBW44 z+|GvMc(T!2_=^GoAeh^ zM@ivapZ3P*Bf#hdEG}C(?d^4HTT2^|%yZ}24`Kvp?kL=#G_+vkl!Y~+)N#Wy&a$)b zCKn4d)6`g>zPiX!w=*)DRJ1+XIhOkUa&I)YG_P6;Ue3oS5Y^zDRuaoMB9j%ZK`55h z5p=L7Sc6fnEX%?th)lbwVR&0k1nbJE%q@a(Wr4eXjc#VXEQ^5=-V6CjM6|vlz{6x5 zUs&T*mQ`Ds>ibI4i=AilMny7 z3`7|I8ss7Su37H41a*+wz2N!p{a|y1Ywy<5x?>y^osrg@ufwtlAt}gcf*0wTzBAdE z6I8AP$B}kb)m*tU9c-h9_MO{(q4#n7#God);)NN0Lr6b1t4A>ZOXT{Vb2C1z!)0W@ z1~Sc0d|lEqG~6XoWKj+1ghsKCPtvuZHP=jrA(S!LubSOrKH8vG=BnsZ4+(K&!H;PN zc4=}y*Na|FR3>_^mUU?f8rSO37Dox^hpim&FA?4HS6*h*46f7h=AspK^)0zkNwKm{ zhMB=DD3G(XJKFQ~vz$nmN+{2`0G9PM%ce*(G$xFz1ae*!B9%Yx|0vY@SO~yjFoX znbP^(y>(tIp;f15_+;PZ$|#^Ja4jS8I$q7Km2S_dc)6wCd|ay%m6tf)f>z}_nG?11dGWqf+&!lu$t=wr(BL^q=rdGQL@Y0%j=M2j}qCT178{Z05h#%=$YrvA3NJ z4`Nq-lNhy}hpr(J^K_$yJ#VhK_1%<`%%6l+7g@*V926&H_$YdK*318T=&Fdi=eb#N z+kyY~^2Pe>RW=!NZ*q~LkgKAf-1jh|E$_QVoi2ku;a{_+R3st@>FbMyEy0kCCD!rH6Vkpn%t**tlsndIxadw&!Qp1(n&}kI0G4n4zP)A67`y4J z`cw@;LuWtnTT54#f_#L$B^qDGT{qEa3wTL`VgtKL|7#2wT9_Rq(tmrj=0+E}pUf~# zqw9?U*cU?Y9h?3X6OZuVlHj3lTO1zMnmoJ=5WpHmValblse5F^c&#x?1SNnvcHCLY zhf4&>b+dZn?<}NW8^)y@b$5=(niD(_&&bd`kS5UdRXk%p?C(n&&9fc(4WHFf5~0t{ zT6Z5v51IHWcZ_^WDN$?>p->AKhVvN=LsmI0*zfNd1x$U8ebuT-%|=iqZZr2%)tY^r zOf2pl!}-fNq+XdnI>|9l8KPb3EvgTCL|I1i15Y3ONMH#2A6+9jpBTJt4`dOw4Suii z6j}0fd{9D4t~y9>=I8|T;~`48IP8nXm8Tpr8_{@vZu7Qj*Cg}$y^x6R!w0v?9x>Ys zozbhFF{h^}`FR}wAY#3ff7PtJHk6M@C{RI&Mq^mkmms<2A)KZfle_oK&2g{H5@Vc= zf^Y5J%qF0iFzXUN>CZ^YA-%ayiVQTW3vb=6dH1`s}#Y}U5E zN0^Hr=g&31JIL-C^dS9`sx=|P@QULu=w3!lbMKv)R)TA?IejFgCljY=7Z%Eh`utdu z?D_Mk=b-GO{a}%&$R{(b;T-y3Yi>_a38BSEH-tQo$2OE+hU0m*0;=x#TvrMIEi_U-TvJ*BtNiH8sT)bVIsPn`6ZrVT%w9IqOD{Yw6RD&9xZ(&};%iACO@M7SYnu^W#aU`DKCr06TDL`XJ6MNSM zv5>wdo#B%%-TDqM_mBU6$Ba)@;}cB`(s&5?{2mrN)^52XE4fFx&9M(1INI_iCdv~E zuc-3#Y!mui^a2Z4&dPyp$M)NQ#X`E~YEC(dWU;iuqOryGK=LYP5!Ko6%$G7omkqGHB*Ylt35=x0PjTubGS68{-J1c$oze-9%^z_#uRA z;nb_*da-8xDC5tI-y%43dL53^OH9ajzV>?*n|3ToRF@A-)~T4?F?+~9DHTtL+4D%> zVNQ<&+cuYTuD-9AL7t$82_2L!%_c+ne%b)ylwqMEi+WXHb~b= z)1ve|H-kSxOY5+h=)muHWqbrrJ#iU@t)kp}g`CqNZ6e~0UuaAF?GD5{^fFe7z}2HU z7fXPAY`*WDqqLuFy7RQZLNsa{&Z6a?K#dsLQrltZ^}%`KwRCcFj!B~^Nnh~|a7vqI z&qX)(Lz-YfG6vKuc`rQvjtsw5!wBg0mXZ<6{slX{xvHKakH5LZ>38m8^hJLY|h zw2rXLp71%5-YHd}#q!py`n*G@?y=rzIJ+VYc%Ys}>MI*=g+m1xKu)HqaQ?R5l-M3V-Hs ziEc=I^9P+R&J(zM#pIv8#jV(s;Rg5ED2n#Pq_ezWc;F``8JFBa{Yo>WCH6kkUILg? zVa3T-UMUmxk!!P#P_Di10-AL$X|dX9Cgy_B z4DP8jQM*jPlnbR}_f0M77YU9Ca>w133|xn1d0Ay=!87R}vN zIf8G)PAUZfFFAMxYb5=z^vuAmnoeCWjs~a{!^I^RD~(KlMb)HmcP+KUWHIn>RA}YS z+A^WtJ{aTmBzK2OvZeqDe92>DuYb-|fQzr5+3BO6Jb<(_zpdz*ZN2t`f8!4a#@!(P zKb(ZO&cjJ^E#&(c980Ay^;*g1fuBJHg$8!w}qIa0wdR-GT?V;4-*taM!`zot@;ByWjr5 zy}Pwld#iS->6zx#nIqj#_vxQJ6QU?D`3?aG0SXH0ozy2WWhkiEL6Eu!94zF04_i11 z@&V&0BBcrk`SXMWe1rUt?If=0q+7RcNQiVHoDI5%xpaD zEIdqXj?&7 zGB!M{vMZm(D$gsGRM06XvuS+s2aoX6Mwg&iYf81OybX3`TSmbm~FY(^TPBHzYV&~$UGFDyv zo>GmfzA1!|uGS~?lcG3KTC+fkI?Ir{ZdhK+WNRKtBi3KGfI*3%j-Vj(?S$k>rCyT| z*V?&)9b?1T;FfXqiR0{?r>qyJ1UbhiI&Jfo79u!5qQ^n6DGQi19uZ%38E(RC->`vA z4WGuSw-=)Qf#@&otRAIjV6d2e!Ai5FUi1A4~&}Yb!U1DJ-kDy*fa5SyM zqPwm~lVvJn0W%Qnx?E3F^U@6UVa%VTD+9R9310|Pe`g+OLpiqd^pa`ns#e!s8nr})r2(K1k)vPC>#R4fgCU5>t5 z<-qDJP?ReFW4QuF6v-~HZJuH4*~qK7vND#><0$i2HJH?x1Nc8-qG}%CEr6GST8W$` z^w~@0fjf&HFzlFVJI?w~p{}YZ%H9c$fVykB;}O_YU2;A&itl>DG&=+3bnj(8wr?A6 z+`-h{nhsmf0DqJP7oFrx6R@*(&)&RtO~?P#K4PnHkLGKQ@obs;lTt^p<#k&T=2G*5 z($F{g#~P_f9!52Z%m`xgxV4i)ZJ<$M|Meh=<3VFr%Z)<#c!i#DwO&nztn#^}9Ruh5 z=FTZ9LDYDOUWd)c2^H+hHCNe}em-Kk4tg#03$55Qt|F1Z_D7j}pyZvyJeOLg_jcCX zl$K1hbRXgOFv7Qi4_-1dE9i_biFxB$HLFaS*W%2WN!ZIwp{_u*-A`!cT44; zrElt5B{ZuTZ%6OLZ>$oa!@W@t`Hok3c@UhQd7I~Jmy-`pA2n0)zTC>EdusR|N9NkC zc01-24-8(?w>7X2#Ad&DV-k*n4mZLsY)<`waQKXY`g46x!l3WlbnyWPt z_tB@sI|=_>Kw`&Ui&&-rUj7wtw=-r8bsdtg60Hu%`faP>54)W z9x0KXXf#_h%IJh|;2V(SIcP_*j5V35Ro6x+#u;Ohs&-Iz`K<~+Ta9@4>fjQ3e(Gx@ zqkVu2xDOH7r~Fyfzq{$LJ%rJ-yqp_IYcgnJCv8e`MW7jvP!T1dPFIRBN8Y|1g6LoN zSXI41d)kDAZ=2^BM9U(NRNc3^P2obn*fr3egg^Bb?9-=uF9XITWe{X&m23j^Dvc-O!T}q_0SDlaRm9!GncPK zUZrqmDsh>HXPYEek(t)UI36w>J1lgtF(zfz%^RAZ)6PUX6MaND^1-RI0!Z~%mpTWz z-P(nRWC$D8)*0si8oVcy|G2|zH@Yxtsl&c#>^izJB2j_mCR-S~bd*0i_r)FeOOqO( z31xwy-4x$P1x`drFcFr|^kU7Z%p200X5ZG*XTlTULy{f;kwM zby3}(l2~tj#;6pj@Sa-bK8p5LlBLTz!>gx#igmKmih4VnqC}q8B*lRM@|}Gn-S6ZU zSzCO>urv~h>S}GU(Mw7gc5+QbxK)$bR*Z*OUqrnR8#ri==M!ks zRb-Lpk0$vY<%B=3*^#~;DkF5UcGsjZd1r+dyYInYPC8ngda+suNgp#8N*NNZV?181-r2Eah1OUO%!F2T)$Xw~Ds2qo*7_d->`E%nPn32CG_d3^= z;lxCpI2WOowJGx#$Ad^Nop|gTRPS?JB^T?HjM$CW!@@T;bhz*`F2zVLrFkrI$$tQ- z6VsiXrO@l%(FDn7GN{&v82+4*`61S}-c$3TWe17B#j4@=Os}IlG&wz`LQTiYZH^iN z6C#T={xwi*N^7}HUfY zvSY@|JKU7j1qXLH7ve#B_QUa_1&~LBFcF)6q!92)$+A zQZ(aY*U9?S^oY6}BD;A8>~{I66*g;Wk$}J9*{qIkyu*;#h9BWGY~j$A+wk^tvIdB? zne|a0SnbeEQ?6V9-PQab19^~eYt0yO8GiS3*~7BFE&c=#?QU{BFmm+ruP+GKy? zhHZigT>y)MC3_dTvz9r&!Vw!|J8{LO?)OpGHAKUie;*0*f@1Df{QU_d{Nuwr(LbAD zUcLXPmc1?VPPBQWaO}X$MenaK1l;V3D3CvpDnXVz=r;5@S*aqUGkcUQQ)6mZ2=dp= zVX*EFF`A!S6x9D5c0H*S9F#4GPb?IgpB|;y-m`UIb@P76i!Yf_gdtBeRk!=coc$m{ zdcG0w1>Nr}_aka~L(hp+`)ng!q!i1IW=#>mS=%pPHam`qoE@*;Adtcr`u*f5ZN;B` z;X1Xa7O=WTnS`|{Q>gu80OWP1vQ-T?7}eB&pW1qa6G_(#M$NxA!Wbn6JyC0~ea5Vf3T(Wsf+woWFAC4+t1l=oCoikzwSQd;oNd>M!1L|q zi@B*EFh9zS?Az>S0W@OGj~bk`FRkOF|Lk@&a~+iZ_vIN{tYZD$iR=CpI6;54(QBfA zY9bM4SO|Y@^!>NSo?}S$*D0a?txbIkTCYeM>6;qDl)be&;ibg9$liI$P{wwUd? zprtk0!e#^=H#_mvWm0EGxH00bmf!AO@GxYcKpJ~BC+hk61_XV2F(!BcSs+7h?P*m` zT(vW*VK%|Yj^&EuVitG}`65z+7M3oc(6froy+F_5rWi)Z{)mGUwXtAhev2Te6WXje zT&0?7yQY+`R|+>F%w&uIG_Vlv3P`%xL9^XYmUYfWuV*=l{G6f@?lLQ&Nm1dbUD}Oi z)i!i*cwo5i_TK9f`5>R8@MwQ$qF&S$YCOb;Lu&t)dH0{?7PT(eTbTxUhsiO>BWr8lHJUlt0U z&4Qj21fS2MB8(gFzrk1mGQBk;M10S;@k$CL$ntb(@L5@hYAt6|ZO~ueb`rN(T{zjq zE>7CBB-r#81~DW=xeRb#hnppJ3_b~$>w`widg#-$-^yNnTS>XDoLddOm@d=yWN!_y znQY)i6ZT5V@U`Kz*`v?bm1c22!pV{=KU;8Tof8xg7Lu;1{3*v=(|$CEfZbXPoA&pp zlcrkZ!KS)7GY}Y5GY`(6UtG$J{GdOUlN6dXM+NCE{iGj{*iep&@g3i**50#MkE9m?Y;Ro3`k1k!;%KHgHZ-HcmT7Vd zSqwVPPsm`CQ(xvUvefI-qEp_w*DvQz5rp_V-jY=nx!LN(9FxtkUtSA`j zUl=lcDxG1!tsLfRH~OhmGhHl}l4D5ig9{G&8o`zq%x9jjwI5lxIV`!Wub8fMl zHS0>e@4QmXia#q+D>ET6ArVlbHxU-Qnv5gL9r{2uCWS~6dlK-fGfVQEp`gyU_TF)d zdU`SX_RW8VS$e$w$oXpS%Q~n)N&_e9#hinDxa&@D_}02$QZi6)MKD(l&J_?M!V=h4 z;cKEi?qXKU`1#~YKh^tCia9;x>o(OfBofnH6y6`GDFY)i8^`nb9C_y^# z<#9*w@ovn=25Vr)*%+YR+$ws^tTpMs&3jF|@=Dn4&9zqj=5UAfF8aF9)=eL3&mXk9h4GT=X)&lHbUK;EO94aE{8V#V5-C zuF-`N%Cd$MC-7>wkwtnHOL%&I>fCl02`%0^HiZRCO5K4b+rWC$T8~YQBx#{Ra{)46 zfyze$t~^q(W#P%5unjR6VK>Q0!IM6T68M;xAtjkqX z3?)^|zkwgnG;NxRGsW_7<-^C~4zUpzS?yQul?YbtSm2~iI4Vs`TJkyS_n{tgr;!<* zGq$Y|0+Ne&9CvJ%!_JlwMhw<#?ZmH&!`!o{_i{-27hITrLE~GYQ(R!3(`pm#y&ONV zEHHud$VbgL!bU`#q*J0vph`gYu4U8mq4|ghOz4+|(TZYiT6$Crv$Bc~#D@pX1Zsz| z2+l;!PCt4%t@^?iZLK$S9$LUszwb1CduWrcoMLol^dJIRkkoJFcKHb0!__a2j0wKz z2nNaYejT`e69h)*s78mz@hW$okr_WyVX99Wf3?KoAo(`)Y^McJ5JM`K>SU(;@DE(+ zq`^>sbEu5Amt_?HeHIM6z}cw>lu34X#1uK{=R|6B(M;ywcPqnvk)~uoVrevvSPGRN zg&HC4`iM;lJO5a9`p6Iju6SOp^o&aK53KU2o;tkt8m`7q1E`(kKxTem*fI-O{vh~9 zpZEOa{6l2Gc#{s7Wr9d-=-~`TnYJKKg00L79ccTLlNpL@zM5c4v*qxLez~_W0Am+j3}(_2u{E zlBn+%mDiVZ$=#hSC3iakuUWJvlps&)@%Sh}qx*zo^YY_5iqBdfp-(BHXf>%)f#)?- z_C3N7AdGRslVN6(CRLfN*`bse@Nxc|TurQCiffVT>#2o89{<<4`99$$uYNtGEOgi5+*i$ed$^?u3X+~_j5Dg z`GJyRm!6UNK`nGi?~RrBm$n1J&y|7q6L|0;HTAnrckF#eKD-?O5(SN0f)A!KjzK52 z19x|VDri@d$`5;ECi5)pB8CYgeVFD%{w4s;P*Z#Us0|e>0PZ_jhB8*}hUebdG)Mnw z`&JIj0+YTYAXLsQWSzRs2YT6xQ@Dkcxz*L3)t6_N%{_}!77Pw6Oiu^q4E9qq0Uj@$ z(8jKH(N=NjE{6@^@vMy@{!=aalS&?PW$Cp%v@)IF1zQ|=;Gq;#z8qxp z_#3bN7_90ol@pK2j^|vf{ThNB`6BrGtekVLntEG2Rra4C%dOK>)35aKE;iH|Z- zRp);9mVDDCzSsD8T9d;meIJ^GL*ixKSAOF23=wrLFrUuNVK1YG`;N zhhNhNg)%foKaHC*2a&b85qm0h1?Yc2yQ@}TPP@!Sw@+C{d8}BOdO5;xE^ePPq!Xv(zoL4#f72HSHb(Qd-ad5QYB$8W*xVQPBQr%@ z%d1qyjVh~)4okW#@|pm|doa0hCTCdB!r{wAw#{Q(mi=?6rUjE)?H^mwORy330eGN| z&h_0qO4MwPUs{V)9VRJ!tniJXajED%Nig4~zP$FP%Qy`HiNRy2p|*2+)Sa=HA~GSo<8@5jD$-g?m# z@8?sFD=3tZzJUMPU`JC&C#FHAf7$K>bJWNXG`3VHRV4IheC z0SKn1mi(Ctf(5&L$e$vkeNI9W_xLns%`r^avi%^2<6)L*%P(J=BhPak)wSogzx^V_ zEoUTbVr3RCnFVZrX0VKN(Pa49=Hob&Tj~rVH$_vS*ak2)s`w%2FBc}=t3dlaT%^MqTM zk}Qxs^!fct=-@UUXSeM;$QZoIxpG5#6;5`Beu;dyn4VETHo+=BoHzpCEOVqJgZcBp@A5Cq?)&zcrhl*E{P+M4AeqQ4NF z`QjoCkp*PFJf2jP#>bepJT{&D+M9DPaOK!;we$Rg?Hil~en)ng!2QGO3vxT;2|NrD zHRhHq&!EU3zj^3V!&0i)view?o}_FHD5eBXhyxjJ6#-*YGRJyl0@_+S%_9*Pdi|cE za>s^Le7*&PT$Z-wnp)v;Hhs+os~_}TLltc~tMN;XB4y91qb3R00Er->{|ROrUB^{f z=$#(RwGIB!HljuOJYNJ~j<46b`N4gfmMg!c<p=KD6Q_K1N_J0r&G(6gcS^bKF z8jzjl`{;0XAycXO=3)2mdo%WanN$!w`&|kJpF%*-R~3(EQ9f39L(`IE2Ba64T^8uC z<2iRU+1W{m!ODBdDO%;a%`;B9@7pnv*GH6dU}P;@y2athpj^C65=^QtSzX)!)FT(7y; zG|sZ6ZUyN&O5uIzN-Z~Kp+p>-?Dw1%~M$`}zqd^{a5Wmtpr<3-M=+xgaZ<_ZoRZ%wK8aA?GADFuQogjC zO5g!NLCoNpJbPo}0nqxSg7iiWP$+g+J1SxH6`a9eTtCNuGfB@|r2_CUiD|sj zncRcUQ{lsj?S%2;0B-4A(>KduO%#82L;*TBvwY2&eTJeG;n+l?lz&&wI1 zz2r9Vs<8Kj`b)LYJ+PPU-ONL%jo_NuG^!8XEke??^F%vHI^oM|dKKy^h+Bw?NBNy+ z{R(Fmlel!ho5HzeGYaeAr)Z-PO=BPL{=3}KS!aZ`?wYcTea=dabnIpx5ES&BB zZf#PV9bEp;&Z`0A6N#A~%Nt1SMe^bqYZj^Aj`=)yWkFzDP>iP@r$k*3=F$#996P^R zn=xnu{}o&QFtF)On>p)ON(x{;7AjRDa=43RM8cP|hB#VMia%u)M@%hLs8k^v|{l88-6m!{HObp4z3V@Rxnur#(}2Pj!{ zPfm?PTaINYLJR#EXJ&0(W0570)uH(3itBzP7#%CYh?phjxkcLS3WXXa<=yGXa=xB2 zvV%Yi84Fupf13eU9_xrn!SZyYdb7UVbZ7DAhGT(uJI4qv1ceibZxGxQRiy=8?u0-| zgTPRKU(pwy=8%2o&B*$meLsjB{4US4|MBRbIzrQ?8Kd`jK)Gh|hIt5R6|2@7 z#?WZLKo1;Ri>FCig&<(ikR%tX$brLpsTzV5O|`c6Pn+M&0=-+!V=C!HHvd|J`V-)| zoY}*t@jlz|nP&QG`7Wf9YPn5G3lc6k#4s6W$Lzfi37aQ zOCGz#Kw62q34fz<0l(sog-K&$RK%gu?JP8eet@_ngr*fy5r_rvi;o=vFMEVVY89Me-*8C3 zU=6*_yJ4*j<~;uvQsP0ktZz^=f{#_`G88iAJanVVcd^BiNN(6iq+gv&&49Bhm zZDe(zo+U@}5abwJU;P0c^;h5BW$V;uN+Lv{=DYUVsvUn1tul5c@;%L-8&|v92ly3- z7{3_OkNP4>qQiuIT8m6RP^b<&1NSQL7;BS#;P)p`u~ ziqY23<=qq5H6f)yCQ^}CQ>WzD>a5)K&YDU@p%6M)CSP!2Xx+x5+S3}y2o!S2Z25kG zjqhUXTirIC(J_YI5=WFK$He36KXBCvzF9T7(4WsW<57D0tsf?Bn9}Y081E9e5tH+<8tl2@z4p(W92|D{YmBt*g?_iG7@Z2?9{Y!5jma3%vfe+_;<1 zop78Fxx+BL?JU&WGyMfnX<2$B5ip+aE}05ZJ&UWU;EP79r`z`bA_YqQmI9APHUHHN ziM1-`^?r+ncBkQ{%jeElXIF7)PWXp)UwzIk+B=ti$b*$fdo|$&cIIVM#7M+vsEi$* za)7+^QYUQkAo!#~K_1#ObeCLXzB^T;ms)uXZeb1_zQ$Od-|R!dhRhE%=wVM@s4tm^ zgDT3AX6!!i22V(&i=@;a8L>mJqcaz+k_BByZbI0u$N5&>(>292oDKX>=GY$r-;9e7JQl?ET`Y@P+BRS9z#6Wxq3O(twel=h^c z;QT;OOi5=8k?<3vfZc8Hq}wk&rofeipqAT++hqt<^(a^-p)8Tfe4@2^pTXI8=?QoE zjGg%|r8@|dkuAy)f+=hg3$b-t=5C8nv=jhAujGV*(gqj<)@4k04OhWP0jrgxOsjl{ zMqZ~=X$lrr{3?8V^7fm4?<^oX-}=Vq6J{8P1L|Io0V%C`khsj#H|sdOeJl- zCk&Gh!KIGc)bEsbXW#g_2T_j?$pLKL3RHk7dkyi3%hDL_;9>##g?*Jtzf_4)i`gKy-uhmw_?SMvW*eFDK!X0w2 zU~}WA^RX5pfGnrivvob|?5f$rLgmwM2iC!86%&`-{S^D~f2h)d4#_9Sx;+c`Z?k4s zL;x4^r;lGHf(dp$AMvcPvW!I+tR>Qg-XY0^9SOc@f>FsGv(C8(XrPr~`P??*9Ies} zlCHHwUt|C%!xy@${Jcz{?%D~CR<9ztzFA4Zr%N;_NO&{*JRRuY-GE+}8|-)YNl`0f z?%Htzp}-^D>i&!&0m`0kA!wl~DJGRY8o)b<8VGRX!7L0w)ZeApR1wVsJu-a>zmDK1 z-5-4q0)47e?9IOXkm1@MfA!HZ%_9QsJq|<>lxAuy^;aQ&A8F?Tdu_un;iEw*T#s81 zV++m@$S2F4jh&l@(;stPf%^k6W4jYob9Ax4s9@*RH_RC9Ogs-zj|J@@`!|3Ez@a zl*Z+{YL)7lQHh&f@ikl=8w)tya1g%HrtfA$B;Cp4^)O_lH6(gyMerkaNq7i0;=zydJ8y3IA!kE0}!y zsnAJ8RJcW^Dh%}0&C}dGF^xvd^F6y?o`-3`wt|I6!^?SXdnpO8b(~nzMoK(QW7o0$ zUT^aj>N&iV&bf=Ia6V-{Dw+%JkVmc*Arzr9n@OMch)ngVPsbh zlUJKd=e2jqt^!}_pRpa&YVqdXlkXX%f4ejn)j*G_{Eh0N=t$5={(bO)$H6pu*q0(` z4}?O;CL2m{S$&ae!!h!Y{$jui`{$AO`m2a*_CTUnoFK0rIjB0V+Y0kv3B6#SR5|zy zyEaT#@3g&3rR=1ev16%=e$+q!f|vfQp}g-W31xpF>#E;$0esnDcygI%$R` zQ4IZ8U6ZW|Bnx0FBUf8~DETEPX zO`APMk#;f-aG>p?vpxQr8|+PKFpG7;P&KzEf}QLmwR%hi@u^x2&p!nxVEdY?+&^R6V4}!d3%<`t)<}C&iwM3l)E; zCpr}QnIz29wYUK-oxHCihS6rw%bnWWwDKy?8Wv*4e>~T^dg4bim#Wf0wAq(+2;O(> z`Xo?+OS-sml(A%7UGR2fhv$CuFLAbna_oN<)E8E;E*1Y@jP;RiQ`knc#9WBlpZC3N zyqKlwjJpKwqXZg5)WC@;t^9*!^Y22{Gp853kWf%q`S5u8ir7{_eGQLG&6vKm^Jo#B z?w2~)`L^NT_pL1MIHxQ0*TfKM0H(orj~55w3`d)3$v+Ymv?!|@UVZ%_F}0tZ{e1TR zdRW8};=Jf&(f>`d>3jD4I!=pNZMuq^86!N>DD*T_eZiS}q1e};ee}j`yzC7*VQ~IC z6mhNfyG~(Ja{Tmp>%2Ws1R!k8w%OBiYN*^;^ukmT6^^$Defze!3b{j3oQZ>keIyh?VMm>wb-=C7N}i=lNJ2V(RG*i3D!W8g_@r(b6@xzEy0<>N z5X8cYgoIY@nB{o1z}}X;+|K9adYJFVz(_Sw>ECSo-};!gll zJyzpn=v(uvgquf4M6mFAh7HWIte&)#!`9v1T0E_zHD@unq)heg>(EQzdB>%J9MFqr=;(9l7xZCFE9NcYRw3dsQ=i(YI)4bI0c4IaT!He4v47wGWrd+IN#;+^2>+ zN;sk?II81=wm3`0moU1yHyv5FLt|l*Hr#O!F%(P(wdJd~3BA4Csx z9%YXWY|Ed_22O&HdJ~3XYb^RCf=d>p4AtuyH`q0$@|a{1n@i46YG?0Al#i?pKEJ$& z&SqMduWSUQ=abAEi#M2lPM~87e|l`&})LtK6sM@=e6G|5kRF&Z~^c#~R;tEUVG;v&r&nY*lR~JWh<^z%K9SV(#CL3WT_bOBWtWaT8C@94eWB3W=%9ZBbfv z#&M>O?Y4v1HyB8cu`?6@hmL`cD^=$0WRC8wB{wxTxXl()vijcbJbYysr-YUoC~a$cdM8<})4JQZZ1a2?EO-jB=gmYB z&&N=yw0(+et(2|1MIG5O;xEgg7O$r5lMhgjiI`)fy^9Oc@L|W}n@}kv!ui`g;5NMU zuX$kn*px&5BoQs^An~NgweWtQUZyirxD6nwqQj-52OwLj_B&X6^>yeNB(n|jG4fkE zN3GG%?>|y&-!|QDHl}RD*q3x9BO6`GnS^v@*wYbATnL};x`$Z0_$YSUEXB^wi>%xf zq0~{KV3Ntu6kMPh2cdrHR|9P9#0@Q-vD{m9-1&mH(43#kJD6>6)`8MOZ{>sW%~p}k zGzA&0gYyR}Gui7vQ)V?y}?7 z)cW@8!p)Zu7J(Md+#d<(Srr;%d-8=D4ii?%SD`U|PR0GpdOQ)<1@(pn+C3^jBb<}{ zSJn4d%_*A|Hc#do7x`@V-f}%{WRvrvK2+V^ajy_$Zc=n?U+m&mea;6cJt5`r|Zsjv_C+=>~_G!Q~6xr5j+*Kito(1y@>aV?%v}I5a8z%*32z|kKm+&fo zZC$zD%d78$$Yf9hQW_Om3`O52>h@U30N}w8+sMJfVpF=%`iAZ&SAwf;u4iBbhk1@S z2V^k`_{)!9i=kKOnOf{K!jtRGKCpfu&R#>8NkA}FaG{?IG5H%9j1@SMWf9hv zy#1JQd0e&FzYGLwoH#yQAMLMhp*H2I+fr%(=LWT?ypIk8P*ev2aq#L{<>~38dFdqj zEuCs350Ka!UkkCE$ZAHzvo$tt(w=0>Whms@9qy!;Jgo-CIjlADlpa zb;Q{*2g5jUBO|shbq*4Yn_~M=2kUqcP9X-xMw~qtpk=*cBHfLH zMPl-9kMkA_7nxSLkGd4X=&n!Uczjbaz)Npfo8FsFyfaA*a z8t|U6ZHi%O!=!-9lVs-vB%5DhsX2;5?&o(O&lu9R1>|NxL02y=_tRwb-<1b5ndLsG zjrv5C6Yi9Q=8r7j>#fLF(K}DBR`_KKq!$d%(Ck?5+T$1571r)1M`j0W> zGx3S?rNM9B6#Xbp$`lghFRWIaQK72)!0x<=RIqbm(TYux7|XSn!25&IBKK4g*O-Q?+Yd4~>2g7C4~ z+V=kDnRxql1Dgl!k`b8P`H+|l|4zd&evnZ$W?qdW#nSs;G?TBSDh{NHIFu`!h87^I z!sp=9-X_Id-{Vja_&6=~63s7^v`D6Jc7oIfwhDhhBwad;VnI8E8p=!7CQW;7?XjEVe4a=IJQ%LwF%7|#;J*wLdkX!aJJy|A>;x6S1s?`y+H1*vky5aFM)v8 zFCHc#@uZy{(P2&CgZ<3|O3po=Icw;bt!dxDMx_jz?DU8#RL{|x6qXRN&{Cz|?Om}W zV0GMzdAc_mLxPW!`Udr}M4#(Q4s=7-sCZC6}Xp*(wYg2*1Q|2Iu2TtEKzCY0PBHxb;LIr#$n zpzXW5gYTXgnw(G?zGvV3s`#Xj7;yFi3vZvXP1+Pp9iFON2_gl{cxCBzm zUYz$eL>&ZMGx>0=q&)@QoNyb5RwMZ99gJvj zeXS%YQ72C4IR-HPgFNNnbgPbO5gq1MAmb6OgdSc-I@=SQbo{X$228IW1(v^tC<=e_ zN&Jz)e`l>=BK?!K!ogm63f|xpc*dE|l2}iguJm5uGZTeI;?DSVs4{#Kt*ObK(r*%3 zVeEZ#)<^iFP=fnCoX>crruT~X#K^nsy5_;ul>>l^tJgBl7sRi^*r=5$z$`g$EB-$x zW)I!^{|7I-UsDv$m$p7QNEBD7k9mfnx&wNM9{q4q=NWJ@53!*L09af$&lXe6^xor` zwf4R5FDqmscmwBBUcZF;L=N|V#@>3af!Gg$l?;~wnWx%2ld6M@lQ~LMEu3Xz#&8B? zA%)*ve#k{xfJ7rRsr^QgLbkx4f67}!IA*<5s-^jYbokDS^pdDMOh(r;rv3z=;Li)h z@5Kn!G7@JHPsY&JrRU}+KfCZlT++P6Pi3G&jVJimn1{>(`=0v#N5ccW)%DzcYvdlJ z96RN4C34&If4JW?wa+_*x$_IVn=F{=9#=_H$GJWv0sDu(PqVwU%tl)FG9qTm*5L(x z?jzR@r94HgL@kt^_H zIpzl_7>=(RJ=B>v$xG(Jq!>Q!yT&!HEZ`hWKJ(y8*CQ;Jf-xgUVx*L+WGVQG0F5zl zquZSWaGU2${_baT%sSK?&uTmFQRW)#4<*4ZW#K*!A9vlEO@J(-#)~=5=+U*T@9^x- z7W;GJRsr6g95My^%kl1i-8^S*iFb#y*UE0cIeT1OV&^EhkE(JvB!w%jaEB~Tg`cv@ zeeHrT1By3kctbo6+S-*&;@qO{*OY_G*NGLZn-T} z=9$C5jPjISrR2b0cwaz#Z-;C#t3YI7A15WML~w_~XLsV5SW^z=5U~Y0adVa4wtw5% zW|l}AJ#S=;HDqV`vW-V^FS^jvd|>z5sEPJZ0+bE?EIJktk0p3wLcN*;|H4}_d9*GA zGchqbvng)r1L-%UU|BTZ@Nnq30Wm{jMa@r7A$tt1e2j%q+#S4&pE?7`C?qh1BzTHo zN$)%He$|p{;-TyzCRKAT;{G_+Z}Xc=xJcoeYJVP?W{uaZu_GZ(t2;Bg`YB4G0>3N4 zO12<;AB(t$D5X`C6flb;|B#fy5*Dmd4(^Bl(~e7?nN!3#Kvv7&DPQ9JNb(6$BGd zP^cQee;2@lrn@+bOr6t@%b4SBb$E9#4%7ZS#mct}y+-X~pMe=wx~3TO6raAay>>1v zcQoJuPC&C-N9;1kWU5eljWWwdzO>za|0|)Zyf;D6Gqx(Uv839Lhb|dGW}iNT$rM?% z%u@4qF$0jqCCndihf6V~ZMqXnDRsaV4If9rYzh{0m~ClavTht2mh_LnxC0d0NwMf(5PMW_h1QF>`4IijqiKiZf$&je_{SGaejB%O}AtoFQfRmPHWLy{3VSFB*MKXY8Ry-_*fpXAA} ze`n(<%EA1bfOq^)zGZO;!9Q7+P->q(^+$~Q|CPIEho<-u6LWQ!?@zZ7!EwG~j{jRP zg!*?fE)Hq`?fSeu$&H-|4$MV~PSd}^33Lj}HEthqY`Xl!(jlC*VjBe!fy{k+@!;!Y zG16%pO{h(H!%i;9R|MgpqGUQckHCivE=z0awRjPo{+=n;5zFN34RZQ)Wb12JQUXi) zzl?7h(zF&uze{(WaHP&#w>s~K62a-URdu;Kb$WHVdh81;FsA1G8Bujpoe-4<*jo5xs2RltCQ7;BHYm!JuNjxIinM0b*Dp# z$8FC+ehodL*sZN7<9I9w(c3K2ZCd%&95T*VHu{+hstX2!xTQ4QT1@Jq(Z$cxPCHbr zyg_0rpZ8u5_?IbfJV7V4xK-y29AWduS2pUSPu8Qyp?PL1ycJ{K_rC|*t$QTNp02y5 zqR=9iM%c*UD~^szenwNz?*J0FJ5y_(Dsh8#5!`w~h{ghpR@(+_@mLz})8-WcpDn>% z4~tAE{tj*S4Hg0*>wQD^zy&$U2tPhrjp=J@ReA4ajSl~etAGKPOqvizkTcVR;+|T4 z{{0%lGY=P5?qL_@Tf97OI&^d@W_DCI9Xg`C@TilM{+0-nJT=#@%=T}}*HK5q)hP6( z3_nUo>m#mEf}@+3CB}qDQ&}2S7kQ~!qarrnS-niD)kIjX#{ftuN5G8)AFZ@oqxS^S zr(w*CIb4`{%z45*oK5y(ojtXWEuvS-W+o3bYFu0NO)oPqJa5iu^j7cbKg!{eM3Fh( zG}t%Pm9C{V_pvsKQfSMw`7kgcd88NiT@{vPiP`t6>$!RG79P<%=KOqnvsNVB;rX~7 z74EfngQtH@ll5^rsASYT>f&<+T1y7h|HONIT&TA>!Lh;Px=6gI`Th7Z7HrW2HX_ z%B^IlyKBk>8gF|g$%j|}RnEzxmT83LVZY(`Jx=q+g4GQIzpB$m+4AS#ku&QRp8oY| zo?W(=(u*!c0>{o`R^$o({PY685A4ZvFU;YYk~u8gclMIsS^A5yObI{A-&gG82j^Y< z?STR8`Riv5wp~|A?1L;D--*3{d5XD({tW%!MZ7faT#NNKTD!&*b7ntlW5%$g_}{EA zc48R#AP;oYaCpaNzC5y<|BRhQuLGZOCaDkI{l4Uz3h(jqD7Eze&9e+Heuufq2eI^? z%>T`Jj9*0|9 zL6M87Ct4j+=%+b%081n_v& ze16(YTx;*8k0d!sXr<$guL_AWst8qhe&_e6@N5M{R-hO6*UVs`j^Q?3YNazm0<_Z}Dx^c*aI+>ZNzd;f#!r>8Fh;J)hIQFdLfZ=||cJ*&Euf6WM2ldw**nYx_ zR8hc|4?m@<*C2ZOx6GYex`+r|h4~oTx8?MA-%u3Xo2irfP^FGx+SohssmSACzLqY* z=P)_-L}+X4rL|RW#&s~UWlIJnE)UMHH}c-eFa`~1*CKfnZVd|X4n=Jo5Xy!BZ%s5b zvZ|Yb-7ENbasUUXhVbTH*QX*WWgF9DTCw&>8BT?hxr6=a7Odfw34zRR-}Dc%SsoWt z6C9)^={Ptom|deoYZ~)bh6+|s@Zs}E^&CtJ3 zy_IGK@14&lIVu1Eh0@OCs4zBuUs7i+4|hdXu;pQY(x&LyJ}Q{_>P9Ltu9STbgpfMX zkF+Em+lGe_sZs!Lp1RM;3!MWA3@>M4&vx{_L&@$(^bGTto89K7;X$N6w`|ke85>1qohX?xGnPd#( ziF^I{bYd_GT6kl!nn}UlvZ`B#pH9?q@X;{tl#K-1)Um!qu5Q2Lbb=aX!2u&eIj=19@M6@ z;Sfw*`W|!pl=1GT$LTk25Mhn92jC4Rqg(4a`Qyo&&wNbT-dc4uu}bF_~kxHOlXGH*~Xc^n-1DUVj| zI-qWu+zGdU%kJY3ZBxd=JKD0~qe}X&KF7-3GuI_UeY)C+C=Os#u?>$)&GVZTM6Ni? z?=nv^sIxC}6CFGQ;#AcqzF-a}g)qC_MS-NGU{9u+`%+4Yf47JYmOP&6p#x79$^CuF zKPr>B&P?BwBG#Sp4xe~D5?>y5Gi-e+k9>ZdhwjogIO*P45}S_L>D^6Dq)I_dmr6RE z4`Oe5?ND22>oQ)6Ys)L!Z6yAqn3U2}EFBbD6I5tiDrRk|Br&CkLz;8UjIQ6{mlXss z@9-%O9d$A6Ju|y4j%K&nJ6&iZe1hEkAyLQNexW3W2N7jd(M@xejkTwWSwocQ&jc~_A7(~;bebYp^FuL` zzmt^7`kH$BuaSlZb-JL~k3Vj;(|7fGhHpQ?fH*Y?erke@WjxiU9ZBz(k+7zO(Yw#p z1Q;5pn%{kJ=`wWN)x5RO&A>J1>9_U_=2qoA*2WtEYSa>0M->g~xYEbQp9Y7K_Ju^k zni3vAnA_Bz-H>{i(9^<{AR~{wS4O|ovz!erVR2k=O`7Xb58JdWWB0>)RtyNBr@sU(PbH2-A{$O2AVA|jaNp9NsX(+W{qIb~aV%AV4!G(bgU1w(a_LKDJ?cwq23zo*q zNy(P4G^Co&@%F9?e!1y9`|CV(rSz$+SB6g>K0%k}oidun?ghFE=A7eHbnJrT@F9E# zjHG9$9`y1%#`^6)5ZXBk?Jq_$J|dSdD!b6hkxOnxZN1m|%J|x>SnJ$-T57R^N=Dr> z*%NL7mmT56w^MTVgo1Qy6`sKY2#xw8Dl_zCJx*SF60hj}I#Hnbz5lq^_ZC$STK4?SYDKuc#?Ik4M4Q!xHBM zZFH~hEiE*AC~|x7cvPeploJ-v{Qfn!%^y#>_`LMS-jZ;!f;@>~>&+ytGt={(Gwi33 zxjloMPuK{huY*3?VAlRnj>7|$6>7dKY5s;sk44Rt_2!y-I_Q&z_n5k!62768Jb$;6 zKlKeI$WMVmt;A~fAy-qzvLQh{+EGmawJrInb1qK1E?tHmj{>XO#uI%c{yI2-ZUKIT zRu3`Ro$zC6!#W;psHAJ9j(79D`*rG!n&b9MKSc1e*F#SgiR}aU?3j%rw+FXF&0AS! zV%unHg6->34^N*8VDk2Jq?MalGB$wO9lQbdH$MdMp(CGXVuBjau3Mtq>GZZajZpeJ zxGO-_j-VSdlGZ=$W5XxAIoGt9?kj%v_l25@A}KK)F$#quRk|yujzS&Ng`a)ToPvJ~ z`IzS5+XMET{(`C zhF=o2($IF5bv83{wQ{h(tYKwm22Y|;Y7X_OqTKbV4X@0c-OcPx%<5D5dHDr7>r?pz z#f5mq1;wyLEAc4QWt5WKZ4J-FC7hS5#-pI7HHYV%*V5j;dCqzMpJ$g(oGm^t?DX-f zcb9@{PbtfXt}u&JX|$yetoAm>JO>-4w*-HTg$7*}i_z?K@8J8tR(1W7DHz$uh z{gcJ{XkcVNvSm@jJ%%DJAjL&gEq?)5xl*Ito4;JMBGpPNGW_H+3w*^U4Mpb#hK7bV zm)yIpEGH+|AWvBg-{kb={^!GCQdyUAdE}YfQo6`nxId&`hi?-s#Q#1tURz)Pd5!A= zd{H|_gojuCbUP0}SFl>msr|P<_#c1&|F4G)6m)dfG$BN{6a(~fHFr1WgR80}sKTei z9%;b6Oj#S#oLXk&I^pQy!6qt7gXR64ot^&a)6D7oRHTNzZ8dg0nyr}N;9x6T+lFyR z{jACt@PdAC;*4dU84wK+PpQ9$^HxNv%25MK3m2j4E@vb$lmzP`?#MRry3^)+$v_gs3Vajn%gHSt0=R6bjO#`_CR zX4cbW(G;|_Ewd>BD$dUQ)6?xnE)xwe8X6ma_V>?eY=w~0H@CJcoLRva`ZqCx1u>1;VKfAp) zd5w*2a(=$$&p?saX;AO1LKF zt`qhnI(KQP?4g>PW8LEBe9viB)lOyqqv~qO##i*{%uF^nH#d}=yu4A_5Urr)n}=Ot zFJE4hkzo$Z$>Gd&u07nfc^efq6m6)k?%d*Lo9!@KTV8SC*>OKk-0kN4{QRF$Ov>)A z_p#&0okST>|GXDGi{W!?o9KTsXF}(pl~_87FK<7jU#UnslrJ z0g1+%DLHYp@2-I78%A_E1|LGs+!080e06=Dg_rlu`nvPhp8;8U`IG4iuUKy04Ey}~ z#>C|0K(&_u89I1%)sa;tF|@2qL|$J08Y`j!%THa51?mSnonxGPt#)R8=)3V(ihu;()NjVRUY?vg{m9(`|bFV0@k(UJGX zix*NO(w1az%SOC8o<4p0>({RWhf(RU@bHSoysFiW4b#S#w25LaAsHFhm^{abM*R2e z=;-O^aaAG_Qd=~JzH2AhR1#57e!OF6knvB9Vp9bu_|fp<;u;>G{`KqI?1n8-Zr6yoJ7~oJi-n8 z&pL;Og&7l;ar@Ie0cjsT1f9LeigMhTd*JNiGEn0qk|1nXKkhg-If-TT7_Or>^sR#d z$@=oecz=7%T*t}D$<)+zH5Rk~okWKVR)U82393u-=nD)4GJ#uvbWV{moVf+N_xbbZ z@*J#Nk{$E6#~wI&1%*@eBKA{r-Rak^T|*VP%&IIbEc6#!T^uU4OD`^d_x?TI9a&i- z8d1l~B_$>L9ym9YHYXMAgQ(xG;K;}byvuQQ?DmM)EaTUrqKesMpEf2|Lqo$;q;w5U z%mHn+etU@v;;wUQ0B8LrHnBaqv0t*Yvp#=*vOd*f&Zx_As-;w^UwHae;#3j+GTVtSbq#bmIQLuSi8*=u}=y1#;q}2OEdRqL+TWn{p4(IInSOwD<~us85vpUG3ux2=O=ZVjNvjXYe;vx zqDvE_hvT1qzMnlk62q@MG_xJIzu_>C=v)m5H&xGF6$4O-#-b z6Z6NGELT{^3;c?#ufJ1WUA>COUxhWDY>GJEPGnL4>}+{emB5p4f$lYuxo*OW*_pZy zhAGm1po_*1_f|wwczAeL1jlA(X7~XD)gL~DKWW^T_xN2*48ofsH&EHVgWPh_v9Xhr zlgDq#%F0%qJ?N5b(|Pz%7P^RDaVQ%SbDlb5Wo7jYjTUhKqYaZZP-t=*UNp6^h)PPj zKU(WYE9QKSFd87UQoCm#L3ov)?+}4zKvqO^S*!BcV;FH35s_F%33vU~N_b@!^zRxOT?sLI z6BQ+^p+T9JmNv5mh#`9Nfh8FqZj<{uD=WX*56X#&iMy(*D;sVhMvgOWv}8<@&A+l9 z4oMkmYBs{fpyy-&UrTok6*;NS1)n0F$j+}_*eZd^)+%D|w6YgOr~d9M{L#{K1zHYZ zh#JP>+LPZW1HG91=`REX5r3X=kxj+1m8;BE{LdUQjIqX}M|@Bj_=8m8C$l%1JqY$y zOK|;#U+j$$%nn-y(^FIDFJ26ejg1|kf0vM;?CM%9MnZk*(({N2a*K|{`LELMRY^$_ z9{lEQH1J6hlU+PKESle3nJFpVKRBQjwv8K57V}(va_rbKM;DiLkChQ%K6NdvzI3LW z*}Xqt71%jA_HGj}0Qk{kZ<%tdmMdG?`}=K$rT15{_I=8#s7+(j0A;%*D%YT2zk{QPepWotg3w|2D}srDja5M8~eCCl9{MHt4u7qZ#zNja1Q z()Ad2I?cqyq*bZAepL|mCE0s{J$0vOyIYaDre27m!o6Z|bVro@WXh~<{`sIi&k0e2 zltff*P3oOwzbzfWQ7=I6cC*RGS0-x{-`i4TQe0*`;#+4sQ##isn_x&UuV9X(K@6jE z^ef(ifWY(JvbX0xT4O${vqfJjzUtdZ15X_i^B?qF}1527y z7M;n{;XJiZRz_+V4K`&!M3$F}8U!4;Wp2{b(_2|uUY3$dBBd8r)70DwUXB2Qdkx|M z5gVX&bgDi1fEHEq5y^M=-o1sdiON#H`S|!awF?4w@Uvw;_8^fmGBcyzzxRG%W~g%e z_U-)%YRnU;@~__B3+K;&gUx9E@nZszN~_ZQ)9<~WrY3aRsDA~kqv$GF-%_c1c#k89vC}fMPyo3t>en(%Q0jaid!mAn|f_QY1^Yp!1*f;lSX29&A z#b1|z`asR_*u=-ONJ=Kc&EN-Z@*|_8pQF*IA3S(KNlk51sJ-3p;E!cX@vR>$u_+u{ zg2{RF<;xe9R-1tcm=qFP{%2w?Gv^r@+bi7`2Wz%6We(USJeD&G3kz|(wY9a9J5y1d zy2YWO%}1^ejnJa8PqE{?%*+z*Z6s0D6Y3EqIQz`Jygck|P;l_t+FHfjSDwso-)@1< zYvvqc022^UH4Rmai=(-7=MM0P#pXiq%xNzm zbVO+$9v&u&It76+Ng5o~HSpgtLuuw4+&4C6f%ov4G_Ka4<3aHP10bDzs469|qeBb2 z7a3#ua4M*CU6RL0iyr}p?zO%yAP|MT5Ee$va`md${#sK@XXos1JqI(G4F(DKKg<$C zxFo1$a(?Y@7+_v}tBJAk4NlH$Qc{e8m6hVPd&?536`3s8u06w5c|0^UB>(o}BAd6X z{=(wo&T`d?=lsW5&*^Bb?R|WwnA^g;9vg+1Gkw{`tCRbe7EB~M%%&D%>iVi$rz1c&uK#HRQ=a4ns+3=NJ(Lij*hNgYorJ6 z_o%EELSwtFzFFC{zZde|nT{QH?YY0bySqN0nex>OZw?SO#JET-#V21RnCOO{iw^d*Q{l_3Sa z?(S|-@@RmRj%2A;fHYB;84_4i$;0i*XV0HM(AI8-0d1Sa>G-S+mGSV@Zhxpq0)sGy zIb6heuGA1bry>pdH+KdxX;h-?wL71wQ*3zw+!P6uWZauK=hfBK`-?2d z$z}F=|MfFyEknN&>_}T)Y0;9j?`E%7`zCxO@!=H4v*hF!%fl5UG`y%PTt}(hP=BqT z1S*R4{=}@zkr?a-GB(j1TFxLAcm)I>mSWED;e=TY^XgwSnhT0C+y1En66X7O1**r7L?lHIJ~Y z0kDaHMMpzVPZW%$LIl%&aFHT7*Nau|OB2xBS#aYT8X5?RFIUVmY369(c5@R_mD;2{ z+RZ<*CcE+yaKE@%kcwMB6eI{IXIst+X1^8V2!TN6o0IJ|LYUp&e2^HYz)U*PEwFQO zeT0G;9_{u4{zaQKy>|1NTqz9;3v2G^U}a@(WVfsU4G+ZxoRfImH}2R4VM9o@&q__~ z=+Pk|N4JC+x&$sMUet-DQ+kgx#Avl@^x(CgLrt{b-d5q0x?`Go`by^Jtg!I?OYWRh z%IMaidrC_4Yj3pF)Xtlkne7i@4(MT_bF>SCAg%x{+Ow>!$4N!SW6*|X_P-0+1SoT4 z!hk$bSYD{MDQNWlWe*J2yl8x8Mge4Q6=8oaJ%Wbc^bDGuSvUaY+9#|FYxC81fd6oR zRtC}3a&jlYaf8V&GHsza+-{Ot2cHx5=FK;OPDw1(6=3>tDPg)b7C#lGio6luU%Cl; z8Q&(*=?F%V1pBSEH5}w?4EN(O3`V9?iokGH;pN!q(ZMLtAwpeFJ z1~|W;8A_4|8$FV14HwOa7zNBpFgxuIyd=Tbm19=Ng2}%cVMA1g904q$J77D+d$U2i zKdw6FxmtG`oVv+){d3Uwh`PAR!GVbWq~DmhT@SZE5*+#2OV*3S6)qqTx8}-2o1a(M zMvXsISEm8LfdYhpEdl`Sgkh$UaOdpWC&Kmm#+bFn6#5@{<2ykXSNWPk%Zb9jBdS9O<1b~>_>$-{ zH+M%40m1OUc?U;&UfyfB#ov6+Q+FY!BKrL0%LH&(oq16_^C}7h<{xfr(QzADZ;%>f z;G_Y4WmYG`c@Q9YQm75pK@dJMkpYIk*(SfHvDm7ojj)*Y_ALcy{KS}(Cr_4STiAe* zY)h6(c=wLd+S(emz23^Bq^z7?T1tiGEkD>A8UVC$uVO@F>%dL59oQCXL;g@dG?W13 zQe=@@G@Z*R?HdQa5x@54ifrPb!lIZYFczR%Pk(683LKFIlU5+e$`x>iYI=IDRV%g1Kqj|h?L^tw(D|6dO2s`whs260@Y=7w zJ^;!eFdR*g84cAe8!wFZ7Ovz}=apvXLLNYv|me37A{XEM`@OT`n?dmjReY%KJivn0C!K>*|-@ttLi!ZuuDV(}nIAv0-d zZ-*&-Zg{Yu1*=6tP2B{^CF;!CvuPO_4f^?&mG7Z-0u8Q$9zS4z5#&NdE+g4h6eT5P zf%}p!lF>jahRx9_f+WBM0v<4V!VV*U(Ff!T!dkK+Bz$NdZzSFWc+E5mF)Ef=6c81S zLuPJa!5Z2F@-VbwiP6pwF#H@fx2rgZAjk_WFCreczw`Llf$laYOVA>nc4@aaOMtdE&G+x$ae3tJOCRiR*>_jg5ahJ* zySmBb#Ka4w`^%;HI|L&Om*U^KdQHLP(#?P!Fnp$zxnF0D4Us)k{9SPxi78^?~EiJ8r%>mb(2D#Xhfsfe2=x)EO=E!r~*n|Xn zdwYB8VLfgZT$Sx@4u~RQku#K{M18h$29B?9_8AH|j7YNHe=q0ly-O}UUy_mW46d(& z4gGHH{Rao9VQnHLSg)C7MyzTW$xfdpXSxPA19_o1sQ_IFk_I*HZZ7K7`g%nWp$+z2;_t3Z+MZYouGePvj{w*qW#19 zk4wh$itV+yT!lv}fY( z!Utl05^N&nC_m@%I4oKf7{W}(r+~xIf9E=AlXNl3vH5wc!^)hjtSdKeH1ErwYroyy z*%_e}#TqXqgZvg4-H8N;TE2jJMsNV9nLGnwGKPnTJ?GNUh*R4-ep5ms;T65eSD?vE z!bwO>1H~eT+S^~<4nBos{2rX8tJ^9T(T!>Axi)APV&4a>i<|D-9wZU_fM=cFt87P3LuOY0u8i z0wL97$z@)CH?D&VP*6BOFw@e~5`er0@1#1UpsPy54I*=%10P-?M65`N=m{_n)D9>iXlN_z#%@x3;cl3urS2+o{PC< z+U1T{APz&S0uqGy+&P^R>q|uzosHHdZ&Ok_m3eBWTI5*Ng@{6g|@bTT=FOc zRqIuil#C#Zfi|OM(PZ(Eb->1c5_A2PhtS1LTRavdh5{%cXqvUh*@W=i04;GuTmchI zCi^TK&HwyZy}#ZHF~+CXrAv|F7PPQPaPpCR1fj2o=ewxmt(#U=+-6ztG=j|m;dS?la)Uhg zVpp1e5~64qY_CQJsKd0#%oUSCTt+939B?{aAZ3JnD6~8@C3@=6e|#CzzR; zLFWM#SW!2_uM~D}ub@yw$C3XFAeUhhVzfVLfaZD%N50my3IJw&vB*chOW>qxmEm6L zqR7!j|2=E35H;_z_DU^4&f=ZDv^it8T#Ci%q>JlA7CS1>)=C2h*?`uF%X&RrIr z%Ut&F;ckVa3U#wJE}^&E`R{#JkU7-#I6wD99#nWfHdwF8F8F9;jl5CRCLxJ?cSL+) x4LKE_*M*7rTZsApr-1Q4^2Y!B!>J?GEAwP)_JK>ckpD!XB(EZucjrma{{XlN9*qD1 literal 0 HcmV?d00001 diff --git a/docs/robot/lekce1/index.md b/docs/robot/lekce1/index.md index b7c96522..94758866 100644 --- a/docs/robot/lekce1/index.md +++ b/docs/robot/lekce1/index.md @@ -210,3 +210,25 @@ Ve zdrojovém kódu jsou komentáře (`// tohle je komentář`), které nám pop - `pink` - `white` - `off` + + +## Ovládání robota přes mobil + + +[Stáhnout ZIP s gridui examplem](./example-gridui.zip){ .md-button .md-button--primary } + +1. Musíme si stáhnou předpřipravený gridui ukázkový projekt, ten můžeme rovnou nahrát do Robůtka. + +2. Dole na liště klikneme na `Config WiFi` + +![](assets/add-wifi.png)
+3. Vybere možnost `Add a WiFi network` + +![](assets/wifi-ssid.png)
+4. Do pole zadejte název WiFi sítě. + +![](assets/wifi-passwd.png)
+5. Dole v terminálu zadejte heslo WiFi sítě + +![](assets/connect-wifi.png)
+6. Znovu klikneme na `Config WiFi` a vybereme `Set WiFI to Station mode (connect to wifi)` \ No newline at end of file From 56587a84826f1734b2e6222cdd171b9faecb97ae Mon Sep 17 00:00:00 2001 From: c2coder Date: Mon, 8 Jul 2024 15:00:31 +0200 Subject: [PATCH 14/14] Add wifi name and password --- docs/robot/lekce1/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/robot/lekce1/index.md b/docs/robot/lekce1/index.md index 94758866..63ee1955 100644 --- a/docs/robot/lekce1/index.md +++ b/docs/robot/lekce1/index.md @@ -222,13 +222,13 @@ Ve zdrojovém kódu jsou komentáře (`// tohle je komentář`), které nám pop 2. Dole na liště klikneme na `Config WiFi` ![](assets/add-wifi.png)
-3. Vybere možnost `Add a WiFi network` +3. Vyberte možnost `Add a WiFi network` ![](assets/wifi-ssid.png)
-4. Do pole zadejte název WiFi sítě. +4. Do pole zadejte název WiFi sítě `TechnikaNaVylete` ![](assets/wifi-passwd.png)
-5. Dole v terminálu zadejte heslo WiFi sítě +5. Dole v terminálu zadejte heslo WiFi sítě `huratabor` ![](assets/connect-wifi.png)
6. Znovu klikneme na `Config WiFi` a vybereme `Set WiFI to Station mode (connect to wifi)` \ No newline at end of file