Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
13ad6d618This file is pinned to the selected release. Download the original to open it in your design tool.
import Modules from 'modules'
import { Robot, type Driver, type TTS, type Renderer } from 'robot'
import { RS30XDriver } from 'rs30x-driver'
import { SCServoDriver } from 'scservo-driver'
import { PWMServoDriver } from 'sg90-driver'
import { DynamixelDriver } from 'dynamixel-driver'
import { NoneDriver } from 'none-driver'
import { TTS as LocalTTS } from 'tts-local'
import { TTS as RemoteTTS } from 'tts-remote'
import { TTS as VoiceVoxTTS } from 'tts-voicevox'
import { TTS as VoiceVoxWebTTS } from 'tts-voicevox-web'
import { TTS as ElevenLabsTTS } from 'tts-elevenlabs'
import { TTS as OpenAITTS } from 'tts-openai'
import defaultMod, { type StackchanMod } from 'default-mods/mod'
import { Renderer as SimpleRenderer } from 'renderer-simple'
import { Renderer as ImageFaceRenderer } from 'renderer-image'
import { Renderer as DogFaceRenderer } from 'renderer-dog'
import { Renderer as SmallFaceRenderer } from 'renderer-small'
import { NetworkService } from 'network-service'
import Microphone from 'microphone'
import Tone from 'tone'
import { asyncWait } from 'stackchan-util'
import loadPreferences from 'loadPreference'
import Led from 'led'
import { StartupScreen } from 'startup-screen'
// wrapper button class for simulator
class SimButton {
#button
onChanged
constructor(button) {
const self = this
this.#button = new button({
onPush() {
self.onChanged?.()
},
})
}
read() {
return this.#button.read() ?? 1
}
}
function createRobot() {
const drivers = new Map<string, new (param: unknown) => Driver>([
['scservo', SCServoDriver],
['dynamixel', DynamixelDriver],
['pwm', PWMServoDriver],
['rs30x', RS30XDriver],
['none', NoneDriver],
])
const ttsEngines = new Map<string, new (param: unknown) => TTS>([
['local', LocalTTS],
['remote', RemoteTTS],
['voicevox', VoiceVoxTTS],
['voicevox-web', VoiceVoxWebTTS],
['elevenlabs', ElevenLabsTTS],
['openai', OpenAITTS],
])
const renderers = new Map<string, new (param: unknown) => Renderer>([
['dog', DogFaceRenderer],
['simple', SimpleRenderer],
['image', ImageFaceRenderer],
['small-face', SmallFaceRenderer],
])
const errors: string[] = []
// Servo Driver
const driverPrefs = loadPreferences('driver')
const driverKey = driverPrefs.type ?? 'scservo'
const Driver = drivers.get(driverKey)
// TTS
const ttsPrefs = loadPreferences('tts')
const ttsKey = ttsPrefs.type ?? 'local'
const TTS = ttsEngines.get(ttsKey)
// Renderer
const rendererPrefs = loadPreferences('renderer')
const rendererKey = rendererPrefs.type ?? 'simple'
const Renderer = renderers.get(rendererKey)
if (!Driver || !TTS || !Renderer) {
for (const [key, klass] of [
[driverKey, Driver],
[ttsKey, TTS],
[rendererKey, Renderer],
]) {
if (klass == null) {
errors.push(`type "${key}" does not exist`)
}
}
throw new Error(errors.join('\n'))
}
const driver = new Driver(driverPrefs)
const renderer = new Renderer(rendererPrefs)
const tts = new TTS(ttsPrefs)
const button = globalThis.button
// TODO(@meganetaaan): screen.touch does not exist under Commodetto context. Is this check necessary?
interface GlobalEnvironment {
screen?: {
touch?: unknown
}
device?: {
sensor?: {
Touch?: unknown
}
}
}
const _globalEnv = globalThis as unknown as GlobalEnvironment
// const TouchConstructor = _globalEnv.device?.sensor?.Touch
// const touch = TouchConstructor ? new Touch(TouchConstructor) : undefined
const touch = undefined
const microphone = Modules.has('embedded:io/audio/in') ? new Microphone() : undefined
const tone = new Tone({ volume: ttsPrefs.volume })
const modConfig = Modules.has('mod/config')
? (Modules.importNow('mod/config') as {
led?: Record<string, unknown>
})
: {}
const configLed = {
...loadPreferences('led'),
...(modConfig.led ?? {}),
}
const led = Object.fromEntries(
Object.entries(configLed).map(([key, ledConfig]) => [
key,
new Led(ledConfig as { pin: number; length?: number; order?: string }),
]),
)
return new Robot({
driver,
renderer,
tts,
button,
touch,
tone,
microphone,
led,
})
}
async function checkAndConnectWiFi() {
const wifiPrefs = loadPreferences('wifi')
if (wifiPrefs.ssid == null || wifiPrefs.password == null) {
return null
}
return new Promise<boolean>((resolve, reject) => {
globalThis.network = new NetworkService({
ssid: wifiPrefs.ssid,
password: wifiPrefs.password,
})
globalThis.network.connect(() => resolve(true), reject)
})
}
async function main(startupScreen: StartupScreen) {
startupScreen.setStatus('Preparing startup...')
if (globalThis.Host?.Button && !globalThis.button) {
globalThis.button = {
a: new SimButton(globalThis.Host.Button.a),
b: new SimButton(globalThis.Host.Button.b),
c: new SimButton(globalThis.Host.Button.c),
}
}
startupScreen.setStatus('Checking Wi-Fi settings...')
await asyncWait(100)
startupScreen.setStatus('Connecting to Wi-Fi...')
const wifiResult = await checkAndConnectWiFi().catch((msg) => {
startupScreen.setStatus(`Wi-Fi connection failed: ${String(msg)}`)
return 'failed'
})
if (wifiResult === true) {
startupScreen.setStatus('Wi-Fi connected')
} else if (wifiResult === null) {
startupScreen.setStatus('Wi-Fi skipped or unavailable')
}
startupScreen.setStatus('Loading mod...')
let { onRobotCreated, onLaunch } = defaultMod
if (Modules.has('mod')) {
const mod = Modules.importNow('mod') as StackchanMod
onRobotCreated = mod.onRobotCreated ?? onRobotCreated
onLaunch = mod.onLaunch ?? onLaunch
}
startupScreen.setStatus('Running launch hooks...')
const shouldRobotCreate = await (onLaunch?.() ?? true)
if (shouldRobotCreate) {
startupScreen.setStatus('Initializing robot...')
const robot = createRobot()
startupScreen.setStatus('Starting robot callbacks...')
await onRobotCreated?.(robot, globalThis.device)
startupScreen.setStatus('Startup complete')
} else {
startupScreen.setStatus('Startup menu is active')
}
}
const startupScreen = new StartupScreen()
main(startupScreen).catch((error) => {
startupScreen.showError(error)
trace(`[startup] Fatal: ${String(error)}\n`)
})