This commit is contained in:
Corey Johnson 2025-11-19 13:19:18 -08:00
parent dc902565f3
commit 781ad51d9b
4 changed files with 129 additions and 128 deletions

View File

@ -5,10 +5,12 @@ import { sleep } from "bun"
import { processStderr, processStdout } from "./utils/stdio" import { processStderr, processStdout } from "./utils/stdio"
import Buzz from "./buzz" import Buzz from "./buzz"
import { join } from "path" import { join } from "path"
import { GPIO } from "./pins" import GPIO from "./pins"
import { Agent } from "./agent" import { Agent } from "./agent"
import { searchWeb } from "./agent/tools" import { searchWeb } from "./agent/tools"
// TODO: Kill baresip process on exit
type CancelableTask = () => void type CancelableTask = () => void
type PhoneContext = { type PhoneContext = {
@ -80,58 +82,58 @@ if (!agentId) {
await startPhone(agentId, apiKey) await startPhone(agentId, apiKey)
// log.info("📞 GPIO inputs initialized") const startBaresip = async (hook: GPIO.InputPin) => {
// const baresipConfig = join(import.meta.dir, "..", "baresip")
// const baresip = new Baresip(["/usr/bin/baresip", "-v", "-f", baresipConfig])
// // const baresipConfig = join(import.meta.dir, "..", "baresip") // baresip.registrationSuccess.connect(async () => {
// // const baresip = new Baresip(["/usr/bin/baresip", "-v", "-f", baresipConfig]) // log.info("🐻 server connected")
// const result = await gpio.get(pins.hook)
// if (result.state === "low") {
// phoneService.send({ type: "initialized" })
// } else {
// phoneService.send({ type: "pick_up" })
// }
// })
// // baresip.registrationSuccess.connect(async () => { // baresip.callReceived.connect(({ contact }) => {
// // log.info("🐻 server connected") // log.info(`🐻 incoming call from ${contact}`)
// // const result = await gpio.get(pins.hook) // phoneService.send({ type: "incoming_call", from: contact })
// // if (result.state === "low") { // })
// // phoneService.send({ type: "initialized" })
// // } else {
// // phoneService.send({ type: "pick_up" })
// // }
// // })
// // baresip.callReceived.connect(({ contact }) => { // baresip.callEstablished.connect(({ contact }) => {
// // log.info(`🐻 incoming call from ${contact}`) // log.info(`🐻 call established with ${contact}`)
// // phoneService.send({ type: "incoming_call", from: contact }) // phoneService.send({ type: "answered" })
// // }) // })
// // baresip.callEstablished.connect(({ contact }) => { // baresip.hungUp.connect(() => {
// // log.info(`🐻 call established with ${contact}`) // log.info("🐻 call hung up")
// // phoneService.send({ type: "answered" }) // phoneService.send({ type: "remote_hang_up" })
// // }) // })
// // baresip.hungUp.connect(() => { // baresip.connect().catch((error) => {
// // log.info("🐻 call hung up") // log.error("🐻 connection error:", error)
// // phoneService.send({ type: "remote_hang_up" }) // phoneService.send({ type: "error", message: error.message })
// // }) // })
// // baresip.connect().catch((error) => { // baresip.error.connect(async ({ message }) => {
// // log.error("🐻 connection error:", error) // log.error("🐻 error:", message)
// // phoneService.send({ type: "error", message: error.message }) // phoneService.send({ type: "error", message })
// // }) // for (let i = 0; i < 4; i++) {
// await ring(500)
// await sleep(250)
// }
// process.exit(1)
// })
// // baresip.error.connect(async ({ message }) => { // const agent = new Agent({
// // log.error("🐻 error:", message) // agentId,
// // phoneService.send({ type: "error", message }) // apiKey,
// // for (let i = 0; i < 4; i++) { // tools: {
// // await ring(500) // search_web: (args: { query: string }) => searchWeb(args.query),
// // await sleep(250) // },
// // } // })
// // process.exit(1) }
// // })
// const agent = new Agent({
// agentId,
// apiKey,
// tools: {
// search_web: (args: { query: string }) => searchWeb(args.query),
// },
// })
// handleAgentEvents(agent) // handleAgentEvents(agent)

View File

@ -1,65 +0,0 @@
import { readdir } from "node:fs/promises"
import { gpiod, cstr } from "./ffi"
import { Output } from "./output"
import { Input } from "./input"
import { ChipNotFoundError } from "./errors"
import type { OutputOptions, InputOptions, ChipInfo } from "./types"
export class GPIO {
#chipPath: string
constructor(options?: { chip?: string }) {
this.#chipPath = options?.chip ?? "/dev/gpiochip0"
}
output(pin: number, options?: OutputOptions): Output {
return new Output(this.#chipPath, pin, options)
}
input(pin: number, options?: InputOptions): Input {
return new Input(this.#chipPath, pin, options)
}
async listChips(): Promise<ChipInfo[]> {
const chips: ChipInfo[] = []
try {
const files = await readdir("/dev")
const chipFiles = files.filter((f) => f.startsWith("gpiochip"))
for (const file of chipFiles) {
const path = `/dev/${file}`
try {
const chip = gpiod.gpiod_chip_open(cstr(path))
if (!chip) continue
const info = gpiod.gpiod_chip_get_info(chip)
if (!info) {
gpiod.gpiod_chip_close(chip)
continue
}
const name = gpiod.gpiod_chip_info_get_name(info)
const label = gpiod.gpiod_chip_info_get_label(info)
const numLines = gpiod.gpiod_chip_info_get_num_lines(info)
chips.push({
path,
name: String(name || ""),
label: String(label || ""),
numLines: Number(numLines),
})
gpiod.gpiod_chip_close(chip)
} catch {
continue
}
}
} catch {
// /dev might not be accessible, return empty array
}
return chips
}
}

View File

@ -1,17 +1,87 @@
export { GPIO } from "./gpio" import { readdir } from "node:fs/promises"
export { import { gpiod, cstr } from "./ffi"
import { Output } from "./output"
import { Input } from "./input"
import type * as Type from "./types"
import {
GPIOError, GPIOError,
PermissionError, PermissionError,
PinInUseError, PinInUseError,
ChipNotFoundError, ChipNotFoundError,
InvalidConfigError, InvalidConfigError,
} from "./errors" } from "./errors"
export type {
InputOptions, class GPIO {
OutputOptions, #chipPath: string
InputEvent,
InputGroupEvent, constructor(options?: { chip?: string }) {
ChipInfo, this.#chipPath = options?.chip ?? "/dev/gpiochip0"
PullMode, }
EdgeMode,
} from "./types" output(pin: number, options?: Type.OutputOptions): Output {
return new Output(this.#chipPath, pin, options)
}
input(pin: number, options?: Type.InputOptions): Input {
return new Input(this.#chipPath, pin, options)
}
async listChips(): Promise<Type.ChipInfo[]> {
const chips: Type.ChipInfo[] = []
try {
const files = await readdir("/dev")
const chipFiles = files.filter((f) => f.startsWith("gpiochip"))
for (const file of chipFiles) {
const path = `/dev/${file}`
try {
const chip = gpiod.gpiod_chip_open(cstr(path))
if (!chip) continue
const info = gpiod.gpiod_chip_get_info(chip)
if (!info) {
gpiod.gpiod_chip_close(chip)
continue
}
const name = gpiod.gpiod_chip_info_get_name(info)
const label = gpiod.gpiod_chip_info_get_label(info)
const numLines = gpiod.gpiod_chip_info_get_num_lines(info)
chips.push({
path,
name: String(name || ""),
label: String(label || ""),
numLines: Number(numLines),
})
gpiod.gpiod_chip_close(chip)
} catch {
continue
}
}
} catch {
// /dev might not be accessible, return empty array
}
return chips
}
static Error = GPIOError
static PermissionError = PermissionError
static PinInUseError = PinInUseError
static ChipNotFoundError = ChipNotFoundError
static InvalidConfigError = InvalidConfigError
}
namespace GPIO {
export type PullMode = Type.PullMode
export type EdgeMode = Type.EdgeMode
export type InputOptions = Type.InputOptions
export type OutputOptions = Type.OutputOptions
export type InputEvent = Type.InputEvent
}
export default GPIO

View File

@ -17,15 +17,9 @@ export type InputEvent = {
timestamp: bigint // nanoseconds timestamp: bigint // nanoseconds
} }
export type InputGroupEvent<T extends string = string> = InputEvent & {
pin: T // name of the pin that fired
}
export type ChipInfo = { export type ChipInfo = {
path: string path: string
name: string name: string
label: string label: string
numLines: number numLines: number
} }
export type PinConfig = Record<string, { offset: number; pull: PullMode }>