@mikrojs/native 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CMakeLists.txt +198 -0
- package/LICENSE +21 -0
- package/README.md +49 -0
- package/cmake/mikrojs_bytecode.cmake +146 -0
- package/cmake.js +22 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +132 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/include/byteorder_apple.h +11 -0
- package/include/byteorder_windows.h +12 -0
- package/include/mikrojs/cbor_helpers.h +24 -0
- package/include/mikrojs/cutils_wrap.h +59 -0
- package/include/mikrojs/errors.h +144 -0
- package/include/mikrojs/mem.h +11 -0
- package/include/mikrojs/mik_color.h +32 -0
- package/include/mikrojs/mikrojs.h +331 -0
- package/include/mikrojs/platform.h +82 -0
- package/include/mikrojs/private.h +281 -0
- package/include/mikrojs/utils.h +125 -0
- package/package.json +100 -0
- package/prebuilds/darwin-arm64/mikrojs.napi.node +0 -0
- package/prebuilds/linux-arm64/mikrojs.napi.node +0 -0
- package/prebuilds/linux-x64/mikrojs.napi.node +0 -0
- package/runtime/ble/ble.ts +231 -0
- package/runtime/ble/types.ts +194 -0
- package/runtime/ble/uuid.ts +89 -0
- package/runtime/ble/validators.ts +61 -0
- package/runtime/cbor/cbor.ts +1 -0
- package/runtime/cbor/types.ts +8 -0
- package/runtime/console/types.ts +50 -0
- package/runtime/env/env.ts +17 -0
- package/runtime/env/types.ts +12 -0
- package/runtime/format/types.ts +4 -0
- package/runtime/fs/fs.ts +93 -0
- package/runtime/fs/types.ts +92 -0
- package/runtime/globals.d.ts +87 -0
- package/runtime/http/helpers.ts +222 -0
- package/runtime/http/native.ts +151 -0
- package/runtime/http/request.ts +25 -0
- package/runtime/i2c/i2c.ts +35 -0
- package/runtime/i2c/types.ts +55 -0
- package/runtime/inspect/types.ts +10 -0
- package/runtime/internal.d.ts +456 -0
- package/runtime/kv/nvs.ts +17 -0
- package/runtime/kv/rtc.ts +17 -0
- package/runtime/kv/shared.ts +107 -0
- package/runtime/kv/types.ts +150 -0
- package/runtime/neopixel/neopixel.ts +38 -0
- package/runtime/neopixel/types.ts +27 -0
- package/runtime/pin/pin.ts +51 -0
- package/runtime/pin/types.ts +49 -0
- package/runtime/pwm/pwm.ts +32 -0
- package/runtime/pwm/types.ts +29 -0
- package/runtime/reader/reader.ts +167 -0
- package/runtime/reader/types.ts +34 -0
- package/runtime/result/native-result.node-shim.ts +44 -0
- package/runtime/result/result.ts +26 -0
- package/runtime/result/types.ts +60 -0
- package/runtime/schema/schema.ts +321 -0
- package/runtime/schema/types.ts +152 -0
- package/runtime/sleep/sleep.ts +14 -0
- package/runtime/sleep/types.ts +44 -0
- package/runtime/sntp/sntp.ts +54 -0
- package/runtime/sntp/types.ts +38 -0
- package/runtime/spi/spi.ts +31 -0
- package/runtime/spi/types.ts +42 -0
- package/runtime/stdio/stdio.ts +44 -0
- package/runtime/stdio/types.ts +22 -0
- package/runtime/stream/stream.ts +150 -0
- package/runtime/stream/types.ts +47 -0
- package/runtime/sys/sys.ts +90 -0
- package/runtime/sys/types.ts +131 -0
- package/runtime/test/test.ts +595 -0
- package/runtime/test/types.ts +97 -0
- package/runtime/uart/types.ts +75 -0
- package/runtime/uart/uart.ts +51 -0
- package/runtime/wifi/types.ts +156 -0
- package/runtime/wifi/wifi.ts +208 -0
- package/scripts/bundle-runtime.js +149 -0
- package/scripts/compare-minifiers.js +189 -0
- package/scripts/compile-bytecode.sh +38 -0
- package/scripts/copy-prebuild.js +20 -0
- package/scripts/generate-symbol-map.js +146 -0
- package/src/builtins.cpp +82 -0
- package/src/cutils_compat.c +38 -0
- package/src/eval_bytecode.cpp +42 -0
- package/src/fs.cpp +878 -0
- package/src/mem.cpp +63 -0
- package/src/mik_abort.cpp +160 -0
- package/src/mik_app_config.cpp +358 -0
- package/src/mik_cbor.cpp +334 -0
- package/src/mik_color.cpp +46 -0
- package/src/mik_console.cpp +422 -0
- package/src/mik_inspect.cpp +850 -0
- package/src/mik_repl.cpp +1122 -0
- package/src/mik_result.cpp +344 -0
- package/src/mik_stdio.cpp +147 -0
- package/src/mik_sys.cpp +239 -0
- package/src/mik_text_encoding.cpp +443 -0
- package/src/mikrojs.cpp +942 -0
- package/src/modules.cpp +944 -0
- package/src/platform_posix.cpp +134 -0
- package/src/timers.cpp +208 -0
- package/src/utils.cpp +173 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type {Result} from '../result/types.js'
|
|
2
|
+
import type {
|
|
3
|
+
ArraySchema,
|
|
4
|
+
BooleanSchema,
|
|
5
|
+
Infer,
|
|
6
|
+
NumberSchema,
|
|
7
|
+
ObjectSchema,
|
|
8
|
+
OptionalSchema,
|
|
9
|
+
SchemaError,
|
|
10
|
+
StringSchema,
|
|
11
|
+
TupleSchema,
|
|
12
|
+
UnknownSchema,
|
|
13
|
+
} from '../schema/types.js'
|
|
14
|
+
|
|
15
|
+
export interface RtcMemoryInfo {
|
|
16
|
+
used: number
|
|
17
|
+
total: number
|
|
18
|
+
entries: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface NvsStorageInfo {
|
|
22
|
+
/** Number of entries in the kv namespace. */
|
|
23
|
+
entries: number
|
|
24
|
+
/** Used entries across all NVS namespaces. */
|
|
25
|
+
used: number
|
|
26
|
+
/** Total entries in the NVS partition. */
|
|
27
|
+
total: number
|
|
28
|
+
/** Free entries in the NVS partition. */
|
|
29
|
+
free: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Values that can be stored (CBOR-serializable). */
|
|
33
|
+
export type Storable =
|
|
34
|
+
| number
|
|
35
|
+
| string
|
|
36
|
+
| boolean
|
|
37
|
+
| undefined
|
|
38
|
+
| Uint8Array
|
|
39
|
+
| Storable[]
|
|
40
|
+
| {[key: string]: Storable}
|
|
41
|
+
|
|
42
|
+
/** Values that can be stored (CBOR-serializable). */
|
|
43
|
+
export type StorableSchema =
|
|
44
|
+
| NumberSchema
|
|
45
|
+
| StringSchema
|
|
46
|
+
| UnknownSchema
|
|
47
|
+
| BooleanSchema
|
|
48
|
+
| OptionalSchema
|
|
49
|
+
| ArraySchema<StorableSchema>
|
|
50
|
+
| TupleSchema<StorableSchema[]>
|
|
51
|
+
| ObjectSchema<{[key: string]: StorableSchema}>
|
|
52
|
+
|
|
53
|
+
export type KVError =
|
|
54
|
+
| {name: 'StorageFull'; message: string}
|
|
55
|
+
| {name: 'EncodeFailed'; message: string}
|
|
56
|
+
| {name: 'WriteFailed'; message: string}
|
|
57
|
+
| {name: 'ValidationFailed'; message: string; path: string}
|
|
58
|
+
|
|
59
|
+
export type KVOptions<S> =
|
|
60
|
+
| {
|
|
61
|
+
schema: S
|
|
62
|
+
initialValue: Infer<S>
|
|
63
|
+
/** Called when reading stored data fails (decode error, schema mismatch).
|
|
64
|
+
* On decode failure, corrupt data is deleted. On schema mismatch, stored data is kept.
|
|
65
|
+
* Return a fallback value, or undefined. Default: `() => undefined`. */
|
|
66
|
+
onReadError: (error: KVError | SchemaError) => Infer<S>
|
|
67
|
+
}
|
|
68
|
+
| {
|
|
69
|
+
schema: OptionalSchema
|
|
70
|
+
initialValue?: Infer<S>
|
|
71
|
+
/** Called when reading stored data fails (decode error, schema mismatch).
|
|
72
|
+
* On decode failure, corrupt data is deleted. On schema mismatch, stored data is kept.
|
|
73
|
+
* Return a fallback value, or undefined. Default: `() => undefined`. */
|
|
74
|
+
onReadError?: (error: KVError | SchemaError) => Infer<S>
|
|
75
|
+
}
|
|
76
|
+
| {
|
|
77
|
+
schema?: never
|
|
78
|
+
initialValue: unknown
|
|
79
|
+
/** Called when reading stored data fails (decode error, schema mismatch).
|
|
80
|
+
* On decode failure, corrupt data is deleted. On schema mismatch, stored data is kept.
|
|
81
|
+
* Return a fallback value, or undefined. Default: `() => undefined`. */
|
|
82
|
+
onReadError: (error: KVError) => unknown
|
|
83
|
+
}
|
|
84
|
+
| {
|
|
85
|
+
schema?: never
|
|
86
|
+
initialValue?: unknown
|
|
87
|
+
/** Called when reading stored data fails (decode error, schema mismatch).
|
|
88
|
+
* On decode failure, corrupt data is deleted. On schema mismatch, stored data is kept.
|
|
89
|
+
* Return a fallback value, or undefined. Default: `() => undefined`. */
|
|
90
|
+
onReadError?: (error: KVError) => unknown
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface KVValue<T> {
|
|
94
|
+
/** Read the value. Returns undefined if the key doesn't exist. */
|
|
95
|
+
get(): T
|
|
96
|
+
/** Write a value, or delete the key if undefined. Returns the written value on success. */
|
|
97
|
+
set(value: T): Result<T, KVError>
|
|
98
|
+
/** Read, transform, and write. Receives undefined if key is missing. Return undefined to delete. */
|
|
99
|
+
update(updater: (value: T) => T): Result<T, KVError>
|
|
100
|
+
/** Remove the key. Returns ok on success, err if the storage operation fails. */
|
|
101
|
+
delete(): Result<void, KVError>
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type InferOpts<
|
|
105
|
+
Schema extends StorableSchema,
|
|
106
|
+
Options extends KVOptions<Schema>,
|
|
107
|
+
> = Options extends {
|
|
108
|
+
schema?: never
|
|
109
|
+
initialValue: infer I
|
|
110
|
+
onReadError: (error: KVError) => infer V
|
|
111
|
+
}
|
|
112
|
+
? I | V
|
|
113
|
+
: Options extends {schema: infer S}
|
|
114
|
+
? Infer<S>
|
|
115
|
+
: unknown
|
|
116
|
+
|
|
117
|
+
export interface RtcStorage {
|
|
118
|
+
createValue<Schema extends StorableSchema, Opts extends KVOptions<Schema>>(
|
|
119
|
+
key: string,
|
|
120
|
+
options?: Opts,
|
|
121
|
+
): KVValue<InferOpts<Schema, Opts>>
|
|
122
|
+
|
|
123
|
+
/** Clear all RTC data. */
|
|
124
|
+
clear(): void
|
|
125
|
+
/** Get info about RTC memory usage. */
|
|
126
|
+
info(): RtcMemoryInfo
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* NVS-backed key-value storage. Survives power cycles.
|
|
131
|
+
* Keys are limited to 15 characters (NVS constraint).
|
|
132
|
+
* Flash-backed: avoid high-frequency writes.
|
|
133
|
+
*/
|
|
134
|
+
export interface NvsStorage {
|
|
135
|
+
createValue<Schema extends StorableSchema, Opts extends KVOptions<Schema>>(
|
|
136
|
+
key: string,
|
|
137
|
+
options?: Opts,
|
|
138
|
+
): KVValue<InferOpts<Schema, Opts>>
|
|
139
|
+
|
|
140
|
+
/** Erase all NVS key-value data. */
|
|
141
|
+
clear(): void
|
|
142
|
+
/** Get info about NVS key-value usage. */
|
|
143
|
+
info(): NvsStorageInfo
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** RTC memory store. Survives deep sleep, lost on power off. */
|
|
147
|
+
export declare const rtcStorage: RtcStorage
|
|
148
|
+
|
|
149
|
+
/** NVS flash store. Survives power cycles. */
|
|
150
|
+
export declare const nvsStorage: NvsStorage
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {NeoPixel as NativeNeoPixel} from 'native:neopixel'
|
|
2
|
+
|
|
3
|
+
import type {Result} from '../result/types.js'
|
|
4
|
+
import type {NeoPixelError, NeoPixelOptions} from './types.js'
|
|
5
|
+
|
|
6
|
+
export class NeoPixel {
|
|
7
|
+
#native: InstanceType<typeof NativeNeoPixel>
|
|
8
|
+
|
|
9
|
+
constructor(pin: number, options: NeoPixelOptions) {
|
|
10
|
+
this.#native = new NativeNeoPixel(pin, options.count, options.rgbw ? 4 : 3)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
setPixel(
|
|
14
|
+
index: number,
|
|
15
|
+
r: number,
|
|
16
|
+
g: number,
|
|
17
|
+
b: number,
|
|
18
|
+
w?: number,
|
|
19
|
+
): Result<void, NeoPixelError> {
|
|
20
|
+
return this.#native.setPixel(index, r, g, b, w ?? 0)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fill(r: number, g: number, b: number, w?: number): Result<void, NeoPixelError> {
|
|
24
|
+
return this.#native.fill(r, g, b, w ?? 0)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
show(): Result<void, NeoPixelError> {
|
|
28
|
+
return this.#native.show()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
clear(): Result<void, NeoPixelError> {
|
|
32
|
+
return this.#native.clear()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
end(): Result<void, NeoPixelError> {
|
|
36
|
+
return this.#native.end()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type {Result} from '../result/types.js'
|
|
2
|
+
|
|
3
|
+
export interface NeoPixelOptions {
|
|
4
|
+
/** Number of LEDs in the strip */
|
|
5
|
+
count: number
|
|
6
|
+
/** Set to true for RGBW strips (SK6812). Defaults to false (RGB/WS2812). */
|
|
7
|
+
rgbw?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type NeoPixelError =
|
|
11
|
+
| {name: 'NotActive'}
|
|
12
|
+
| {name: 'IndexOutOfRange'}
|
|
13
|
+
| {name: 'ShowFailed'; message: string}
|
|
14
|
+
|
|
15
|
+
export declare class NeoPixel {
|
|
16
|
+
constructor(pin: number, options: NeoPixelOptions)
|
|
17
|
+
/** Set a single pixel's color (0–255 per channel) */
|
|
18
|
+
setPixel(index: number, r: number, g: number, b: number, w?: number): Result<void, NeoPixelError>
|
|
19
|
+
/** Set all pixels to the same color */
|
|
20
|
+
fill(r: number, g: number, b: number, w?: number): Result<void, NeoPixelError>
|
|
21
|
+
/** Transmit the pixel buffer to the strip */
|
|
22
|
+
show(): Result<void, NeoPixelError>
|
|
23
|
+
/** Turn off all pixels and transmit */
|
|
24
|
+
clear(): Result<void, NeoPixelError>
|
|
25
|
+
/** Release the RMT channel */
|
|
26
|
+
end(): Result<void, NeoPixelError>
|
|
27
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as native from 'native:pin'
|
|
2
|
+
|
|
3
|
+
import type {Result} from '../result/types.js'
|
|
4
|
+
import type {AnalogReadOptions, Attenuation, PinError, PinMode} from './types.js'
|
|
5
|
+
|
|
6
|
+
// Public mode lookup. Kept as a runtime value (rather than inlined) because
|
|
7
|
+
// user code occasionally reads it to build a mode-picker UI on host-side
|
|
8
|
+
// tooling; the native:pin layer uses the numeric constants directly.
|
|
9
|
+
const PIN_MODE_INPUT = 0x01
|
|
10
|
+
const PIN_MODE_OUTPUT = 0x03
|
|
11
|
+
const PIN_MODE_INPUT_PULLUP = 0x05
|
|
12
|
+
|
|
13
|
+
export const NativePinModes = {
|
|
14
|
+
INPUT: PIN_MODE_INPUT,
|
|
15
|
+
OUTPUT: PIN_MODE_OUTPUT,
|
|
16
|
+
INPUT_PULLUP: PIN_MODE_INPUT_PULLUP,
|
|
17
|
+
} as const
|
|
18
|
+
|
|
19
|
+
const NativeAttenuation: Record<Attenuation, number> = {
|
|
20
|
+
'0db': 0,
|
|
21
|
+
'2.5db': 1,
|
|
22
|
+
'6db': 2,
|
|
23
|
+
'11db': 3,
|
|
24
|
+
} as const
|
|
25
|
+
|
|
26
|
+
// Wrappers translate the string/enum JS-idiom args to the numeric codes the
|
|
27
|
+
// native layer expects. Errors flow through unchanged: native:pin returns
|
|
28
|
+
// {ok, error:{name,message}} directly (see mik_pin.cpp), so no remapping.
|
|
29
|
+
|
|
30
|
+
export function pinMode(pin: number, mode: PinMode): Result<void, PinError> {
|
|
31
|
+
return native.pinMode(pin, NativePinModes[mode])
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function digitalWrite(pin: number, value: 0 | 1): Result<void, PinError> {
|
|
35
|
+
return native.digitalWrite(pin, value)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function digitalRead(pin: number): 0 | 1 {
|
|
39
|
+
return native.digitalRead(pin) as 0 | 1
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function analogRead(pin: number, options?: AnalogReadOptions): Result<number, PinError> {
|
|
43
|
+
return native.analogRead(pin, NativeAttenuation[options?.attenuation ?? '11db'])
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function analogReadMillivolts(
|
|
47
|
+
pin: number,
|
|
48
|
+
options?: AnalogReadOptions,
|
|
49
|
+
): Result<number, PinError> {
|
|
50
|
+
return native.analogReadMillivolts(pin, NativeAttenuation[options?.attenuation ?? '11db'])
|
|
51
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type {Result} from '../result/types.js'
|
|
2
|
+
|
|
3
|
+
export type PinMode = 'INPUT' | 'OUTPUT' | 'INPUT_PULLUP'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ADC attenuation setting, controls the measurable voltage range.
|
|
7
|
+
* - `'0db'` — 0–750 mV
|
|
8
|
+
* - `'2.5db'` — 0–1050 mV
|
|
9
|
+
* - `'6db'` — 0–1300 mV
|
|
10
|
+
* - `'11db'` — 0–2500 mV (default)
|
|
11
|
+
*/
|
|
12
|
+
export type Attenuation = '0db' | '2.5db' | '6db' | '11db'
|
|
13
|
+
|
|
14
|
+
export interface AnalogReadOptions {
|
|
15
|
+
/** ADC attenuation. Defaults to `'11db'` (0–2500 mV range). */
|
|
16
|
+
attenuation?: Attenuation
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type PinError =
|
|
20
|
+
| {name: 'InvalidPin'; message: string}
|
|
21
|
+
| {name: 'SetModeFailed'; message: string}
|
|
22
|
+
| {name: 'WriteFailed'; message: string}
|
|
23
|
+
| {name: 'AdcInitFailed'; message: string}
|
|
24
|
+
| {name: 'AdcReadFailed'; message: string}
|
|
25
|
+
| {name: 'InvalidAdcPin'}
|
|
26
|
+
|
|
27
|
+
export declare function pinMode(pin: number, mode: PinMode): Result<void, PinError>
|
|
28
|
+
|
|
29
|
+
export declare function digitalWrite(pin: number, value: 0 | 1): Result<void, PinError>
|
|
30
|
+
|
|
31
|
+
export declare function digitalRead(pin: number): 0 | 1
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Read the raw ADC value from a pin.
|
|
35
|
+
* @returns A Result containing a 12-bit integer (0–4095) proportional to the input voltage, or a PinError.
|
|
36
|
+
*/
|
|
37
|
+
export declare function analogRead(
|
|
38
|
+
pin: number,
|
|
39
|
+
options?: AnalogReadOptions,
|
|
40
|
+
): Result<number, PinError>
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Read a calibrated voltage from a pin.
|
|
44
|
+
* @returns A Result containing the input voltage in millivolts, or a PinError.
|
|
45
|
+
*/
|
|
46
|
+
export declare function analogReadMillivolts(
|
|
47
|
+
pin: number,
|
|
48
|
+
options?: AnalogReadOptions,
|
|
49
|
+
): Result<number, PinError>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {ok} from 'mikrojs/result'
|
|
2
|
+
import {Pwm as NativePwm} from 'native:pwm'
|
|
3
|
+
|
|
4
|
+
import type {Result} from '../result/types.js'
|
|
5
|
+
import type {PwmError, PwmOptions} from './types.js'
|
|
6
|
+
|
|
7
|
+
export class Pwm {
|
|
8
|
+
#native: InstanceType<typeof NativePwm>
|
|
9
|
+
|
|
10
|
+
constructor(pin: number, options: PwmOptions) {
|
|
11
|
+
this.#native = new NativePwm(pin, options.freq, options.duty ?? 0)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
duty(value?: number): Result<number, PwmError> {
|
|
15
|
+
return this.#native.duty(value)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
freq(value?: number): Result<number, PwmError> {
|
|
19
|
+
return this.#native.freq(value)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async fade(targetDuty: number, durationMs: number): Promise<Result<void, PwmError>> {
|
|
23
|
+
const result = this.#native.fade(targetDuty, durationMs)
|
|
24
|
+
if (!result.ok) return result
|
|
25
|
+
await result.value
|
|
26
|
+
return ok(undefined)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
end(): Result<void, PwmError> {
|
|
30
|
+
return this.#native.end()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type {Result} from '../result/types.js'
|
|
2
|
+
|
|
3
|
+
export interface PwmOptions {
|
|
4
|
+
/** Frequency in Hz */
|
|
5
|
+
freq: number
|
|
6
|
+
/** Initial duty cycle, 0.0–1.0. Defaults to 0. */
|
|
7
|
+
duty?: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type PwmError =
|
|
11
|
+
| {name: 'NoChannel'; message: string}
|
|
12
|
+
| {name: 'NoTimer'; message: string}
|
|
13
|
+
| {name: 'ConfigFailed'; message: string}
|
|
14
|
+
| {name: 'NotActive'}
|
|
15
|
+
| {name: 'DutyFailed'; message: string}
|
|
16
|
+
| {name: 'FreqFailed'; message: string}
|
|
17
|
+
| {name: 'FadeFailed'; message: string}
|
|
18
|
+
|
|
19
|
+
export declare class Pwm {
|
|
20
|
+
constructor(pin: number, options: PwmOptions)
|
|
21
|
+
/** Get or set duty cycle (0.0–1.0) */
|
|
22
|
+
duty(value?: number): Result<number, PwmError>
|
|
23
|
+
/** Get or set frequency in Hz */
|
|
24
|
+
freq(value?: number): Result<number, PwmError>
|
|
25
|
+
/** Hardware fade to target duty over duration. Returns promise with result. */
|
|
26
|
+
fade(targetDuty: number, durationMs: number): Promise<Result<void, PwmError>>
|
|
27
|
+
/** Stop PWM and release the channel */
|
|
28
|
+
end(): Result<void, PwmError>
|
|
29
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Byte-level protocol reader for mikrojs.
|
|
3
|
+
*
|
|
4
|
+
* Separate runtime module from `mikrojs/stream` so that apps which
|
|
5
|
+
* only need a buffered reader (a modem driver, a log framer, an HTTP
|
|
6
|
+
* body splitter) don't pay the load cost of the async-iterator
|
|
7
|
+
* transforms in `mikrojs/stream`. Both modules share the same
|
|
8
|
+
* single-consumer design philosophy; they're separated only for
|
|
9
|
+
* lazy-load efficiency.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A buffered byte reader over an async iterator of byte chunks.
|
|
14
|
+
*
|
|
15
|
+
* Wraps any `AsyncIterable<Uint8Array>` (typically `uart.read()`,
|
|
16
|
+
* eventually also TCP sockets) and exposes three primitives that
|
|
17
|
+
* share the same underlying byte buffer:
|
|
18
|
+
*
|
|
19
|
+
* - `readUntil(delimiter, {timeoutMs})` — pull chunks until the
|
|
20
|
+
* buffer contains `delimiter`, return everything before it and
|
|
21
|
+
* consume the delimiter. The AT-channel workhorse: passes
|
|
22
|
+
* `CRLF` to read a line, or `'> '` to wait for a prompt.
|
|
23
|
+
*
|
|
24
|
+
* - `readBytes(count, {timeoutMs})` — pull chunks until the buffer
|
|
25
|
+
* has at least `count` bytes, return the first `count` and keep
|
|
26
|
+
* the rest. Used for length-prefixed binary payloads (e.g.
|
|
27
|
+
* HTTP response bodies after `+SHREAD`).
|
|
28
|
+
*
|
|
29
|
+
* - `drain()` — discard any buffered bytes without touching the
|
|
30
|
+
* underlying iterator. Useful before sending a new command when
|
|
31
|
+
* stale bytes from a previous response might be left over.
|
|
32
|
+
*
|
|
33
|
+
* Both `readUntil` and `readBytes` throw `TimeoutError` (an Error
|
|
34
|
+
* with `name === 'TimeoutError'`) if the deadline expires before
|
|
35
|
+
* the request is satisfied, and `StreamClosed` (an Error with
|
|
36
|
+
* `name === 'StreamClosed'`) if the underlying iterator finishes
|
|
37
|
+
* before the request is satisfied. Callers may wrap those in their
|
|
38
|
+
* own Result type if they prefer structured error handling.
|
|
39
|
+
*
|
|
40
|
+
* Timeout semantics: when a pull races against a `setTimeout` and
|
|
41
|
+
* the timer wins, the pending `iter.next()` promise is kept so the
|
|
42
|
+
* NEXT pull resumes on the same chunk — bytes received during the
|
|
43
|
+
* timed-out window are not dropped. This matches the behavior of
|
|
44
|
+
* hand-written AT channels and is what you want for command retries.
|
|
45
|
+
*
|
|
46
|
+
* This reader is intentionally **single-consumer**. Interleaving
|
|
47
|
+
* concurrent `readUntil` / `readBytes` calls is not supported and
|
|
48
|
+
* not protected against — the caller is responsible for
|
|
49
|
+
* sequential ordering.
|
|
50
|
+
*/
|
|
51
|
+
export class BufferedReader {
|
|
52
|
+
readonly #source: AsyncIterator<Uint8Array>
|
|
53
|
+
#buffer: Uint8Array
|
|
54
|
+
#pending: Promise<IteratorResult<Uint8Array>> | null
|
|
55
|
+
|
|
56
|
+
constructor(source: AsyncIterable<Uint8Array>) {
|
|
57
|
+
this.#source = source[Symbol.asyncIterator]() as AsyncIterator<Uint8Array>
|
|
58
|
+
this.#buffer = new Uint8Array(0)
|
|
59
|
+
this.#pending = null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async readUntil(delimiter: Uint8Array, options: {timeoutMs: number}): Promise<Uint8Array> {
|
|
63
|
+
if (delimiter.length === 0) {
|
|
64
|
+
throw new RangeError('BufferedReader.readUntil: delimiter must be non-empty')
|
|
65
|
+
}
|
|
66
|
+
const deadline = Date.now() + options.timeoutMs
|
|
67
|
+
while (true) {
|
|
68
|
+
const idx = indexOfBytes(this.#buffer, delimiter)
|
|
69
|
+
if (idx !== -1) {
|
|
70
|
+
const out = this.#buffer.slice(0, idx)
|
|
71
|
+
this.#buffer = this.#buffer.slice(idx + delimiter.length)
|
|
72
|
+
return out
|
|
73
|
+
}
|
|
74
|
+
await this.#pull(deadline)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async readBytes(count: number, options: {timeoutMs: number}): Promise<Uint8Array> {
|
|
79
|
+
if (count < 0) {
|
|
80
|
+
throw new RangeError('BufferedReader.readBytes: count must be non-negative')
|
|
81
|
+
}
|
|
82
|
+
if (count === 0) return new Uint8Array(0)
|
|
83
|
+
const deadline = Date.now() + options.timeoutMs
|
|
84
|
+
while (this.#buffer.length < count) {
|
|
85
|
+
await this.#pull(deadline)
|
|
86
|
+
}
|
|
87
|
+
const out = this.#buffer.slice(0, count)
|
|
88
|
+
this.#buffer = this.#buffer.slice(count)
|
|
89
|
+
return out
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Discard buffered bytes. Leaves `#pending` alone so any in-flight
|
|
94
|
+
* `iter.next()` from a previously timed-out pull still gets
|
|
95
|
+
* consumed on the next read — we only want to drop stale bytes
|
|
96
|
+
* that were already delivered, not bytes the source hasn't sent
|
|
97
|
+
* yet.
|
|
98
|
+
*/
|
|
99
|
+
drain(): void {
|
|
100
|
+
this.#buffer = new Uint8Array(0)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async #pull(deadline: number): Promise<void> {
|
|
104
|
+
const remaining = deadline - Date.now()
|
|
105
|
+
if (remaining <= 0) {
|
|
106
|
+
const err = new Error(`BufferedReader: timeout waiting for bytes`)
|
|
107
|
+
err.name = 'TimeoutError'
|
|
108
|
+
throw err
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (this.#pending === null) {
|
|
112
|
+
this.#pending = this.#source.next()
|
|
113
|
+
}
|
|
114
|
+
const pending = this.#pending
|
|
115
|
+
|
|
116
|
+
let timerId: ReturnType<typeof setTimeout> | undefined
|
|
117
|
+
try {
|
|
118
|
+
const result = await Promise.race([
|
|
119
|
+
pending.then((v): {kind: 'value'; v: IteratorResult<Uint8Array>} => ({kind: 'value', v})),
|
|
120
|
+
new Promise<{kind: 'timeout'}>((resolve) => {
|
|
121
|
+
timerId = setTimeout(() => resolve({kind: 'timeout'}), remaining)
|
|
122
|
+
}),
|
|
123
|
+
])
|
|
124
|
+
if (result.kind === 'timeout') {
|
|
125
|
+
// Leave #pending intact so the next pull resumes on this chunk.
|
|
126
|
+
const err = new Error(`BufferedReader: timeout after ${remaining}ms waiting for bytes`)
|
|
127
|
+
err.name = 'TimeoutError'
|
|
128
|
+
throw err
|
|
129
|
+
}
|
|
130
|
+
this.#pending = null
|
|
131
|
+
if (result.v.done) {
|
|
132
|
+
const err = new Error('BufferedReader: stream closed before read satisfied')
|
|
133
|
+
err.name = 'StreamClosed'
|
|
134
|
+
throw err
|
|
135
|
+
}
|
|
136
|
+
this.#buffer = concatBytes(this.#buffer, result.v.value)
|
|
137
|
+
} finally {
|
|
138
|
+
if (timerId !== undefined) clearTimeout(timerId)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Find the first index of `needle` in `haystack`, or -1 if not found. */
|
|
144
|
+
function indexOfBytes(haystack: Uint8Array, needle: Uint8Array): number {
|
|
145
|
+
if (needle.length === 0) return 0
|
|
146
|
+
const limit = haystack.length - needle.length
|
|
147
|
+
for (let i = 0; i <= limit; i++) {
|
|
148
|
+
let ok = true
|
|
149
|
+
for (let j = 0; j < needle.length; j++) {
|
|
150
|
+
if (haystack[i + j] !== needle[j]) {
|
|
151
|
+
ok = false
|
|
152
|
+
break
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (ok) return i
|
|
156
|
+
}
|
|
157
|
+
return -1
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function concatBytes(a: Uint8Array, b: Uint8Array): Uint8Array {
|
|
161
|
+
if (a.length === 0) return b
|
|
162
|
+
if (b.length === 0) return a
|
|
163
|
+
const out = new Uint8Array(a.length + b.length)
|
|
164
|
+
out.set(a, 0)
|
|
165
|
+
out.set(b, a.length)
|
|
166
|
+
return out
|
|
167
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for `mikrojs/reader`.
|
|
3
|
+
*
|
|
4
|
+
* Runtime implementation lives in `./reader.ts` and is compiled to
|
|
5
|
+
* bytecode at firmware build time. This file is the declaration-only
|
|
6
|
+
* surface consumed by the `mikrojs/reader` re-export in
|
|
7
|
+
* `packages/mikrojs` and by user apps via their tsconfig.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A buffered byte reader over an async iterator of `Uint8Array`
|
|
12
|
+
* chunks. Offers three operations that share the same underlying
|
|
13
|
+
* buffer:
|
|
14
|
+
*
|
|
15
|
+
* - `readUntil(delimiter, {timeoutMs})` — return everything before
|
|
16
|
+
* the first occurrence of `delimiter`, consume the delimiter.
|
|
17
|
+
* - `readBytes(count, {timeoutMs})` — return exactly `count` bytes,
|
|
18
|
+
* pulling more source chunks as needed.
|
|
19
|
+
* - `drain()` — discard buffered bytes without affecting the
|
|
20
|
+
* underlying iterator.
|
|
21
|
+
*
|
|
22
|
+
* Throws an Error with `name === 'TimeoutError'` if the deadline
|
|
23
|
+
* expires before the request is satisfied, or `name === 'StreamClosed'`
|
|
24
|
+
* if the underlying iterator finishes before the request is satisfied.
|
|
25
|
+
*
|
|
26
|
+
* Single-consumer only: concurrent `readUntil` / `readBytes` calls on
|
|
27
|
+
* the same instance are unsupported.
|
|
28
|
+
*/
|
|
29
|
+
export declare class BufferedReader {
|
|
30
|
+
constructor(source: AsyncIterable<Uint8Array>)
|
|
31
|
+
readUntil(delimiter: Uint8Array, options: {timeoutMs: number}): Promise<Uint8Array>
|
|
32
|
+
readBytes(count: number, options: {timeoutMs: number}): Promise<Uint8Array>
|
|
33
|
+
drain(): void
|
|
34
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Host-side shim for `native:result` used only in vitest (Node) where the
|
|
2
|
+
// mikrojs C runtime isn't available. Keep in sync with mik_result.cpp.
|
|
3
|
+
|
|
4
|
+
const proto = {
|
|
5
|
+
map(this: {ok: boolean; value?: unknown; error?: unknown}, fn: (v: unknown) => unknown) {
|
|
6
|
+
return this.ok ? ok(fn(this.value)) : this
|
|
7
|
+
},
|
|
8
|
+
mapErr(this: {ok: boolean; value?: unknown; error?: unknown}, fn: (e: unknown) => unknown) {
|
|
9
|
+
return this.ok ? this : err(fn(this.error))
|
|
10
|
+
},
|
|
11
|
+
andThen(this: {ok: boolean; value?: unknown; error?: unknown}, fn: (v: unknown) => unknown) {
|
|
12
|
+
return this.ok ? fn(this.value) : this
|
|
13
|
+
},
|
|
14
|
+
match(
|
|
15
|
+
this: {ok: boolean; value?: unknown; error?: unknown},
|
|
16
|
+
handlers: {ok: (v: unknown) => unknown; err: (e: unknown) => unknown},
|
|
17
|
+
) {
|
|
18
|
+
return this.ok ? handlers.ok(this.value) : handlers.err(this.error)
|
|
19
|
+
},
|
|
20
|
+
orDefault(this: {ok: boolean; value?: unknown; error?: unknown}, defaultValue: unknown) {
|
|
21
|
+
return this.ok ? this.value : defaultValue
|
|
22
|
+
},
|
|
23
|
+
orPanic(this: {ok: boolean; value?: unknown; error?: unknown}, message: string) {
|
|
24
|
+
if (this.ok) return this.value
|
|
25
|
+
const panic = new Error(message)
|
|
26
|
+
panic.name = 'PanicError'
|
|
27
|
+
;(panic as Error & {cause?: unknown}).cause = this.error
|
|
28
|
+
throw panic
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function ok(value?: unknown): unknown {
|
|
33
|
+
const r = Object.create(proto)
|
|
34
|
+
r.ok = true
|
|
35
|
+
if (arguments.length > 0) r.value = value
|
|
36
|
+
return r
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function err(error: unknown): unknown {
|
|
40
|
+
const r = Object.create(proto)
|
|
41
|
+
r.ok = false
|
|
42
|
+
r.error = error
|
|
43
|
+
return r
|
|
44
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export {err, ok} from 'native:result'
|
|
2
|
+
|
|
3
|
+
export class PanicError extends Error {
|
|
4
|
+
constructor(message: string, options?: {cause?: unknown}) {
|
|
5
|
+
super(message, options)
|
|
6
|
+
this.name = 'PanicError'
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function defineError<D extends Record<string, (...args: any[]) => Record<string, unknown>>>(
|
|
11
|
+
_name: string,
|
|
12
|
+
variants: D,
|
|
13
|
+
): {[K in keyof D & string]: (...args: Parameters<D[K]>) => {name: K} & ReturnType<D[K]>} {
|
|
14
|
+
const constructors: Record<string, (...args: unknown[]) => Record<string, unknown>> = {}
|
|
15
|
+
for (const key in variants) {
|
|
16
|
+
const factory = variants[key]!
|
|
17
|
+
constructors[key] = (...args: unknown[]) => {
|
|
18
|
+
const fields = (factory as (...a: unknown[]) => Record<string, unknown>)(...args)
|
|
19
|
+
fields.name = key
|
|
20
|
+
return fields
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return constructors as unknown as {
|
|
24
|
+
[K in keyof D & string]: (...args: Parameters<D[K]>) => {name: K} & ReturnType<D[K]>
|
|
25
|
+
}
|
|
26
|
+
}
|