This commit is contained in:
Corey Johnson 2025-11-21 10:59:40 -08:00
parent 28186bc0ce
commit c07cb297e3
8 changed files with 48 additions and 27 deletions

View File

@ -1 +1 @@
<sip:yellow@probablycorey.sip.twilio.com;transport=tls>;auth_pass=zgm-kwx2bug5hwf3YGF;unregister_on_exit=yes;regint=300 <sip:yellow@probablycorey.sip.twilio.com;transport=tls>;auth_pass=zgm-kwx2bug5hwf3YGF;unregister_on_exit=yes

View File

@ -51,13 +51,17 @@ module stun.so
module turn.so module turn.so
module ice.so module ice.so
# STUN Server
stun_host stun.l.google.com
stun_port 19302
module httpd.so module httpd.so
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
# Temporary Modules (loaded then unloaded) # Temporary Modules (loaded then unloaded)
module_tmp uuid.so module uuid.so
module_tmp account.so module account.so
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------

View File

@ -8,6 +8,13 @@ const PHONE_SERVICE_FILE = "/etc/systemd/system/phone.service"
export const setupServices = async (installDir: string) => { export const setupServices = async (installDir: string) => {
console.log("\nInstalling systemd services...") console.log("\nInstalling systemd services...")
// Detect user from environment or use default
// SUDO_USER is set when running with sudo, which is what we want
const serviceUser = process.env.SERVICE_USER || process.env.SUDO_USER || process.env.USER || "corey"
const userUid = await $`id -u ${serviceUser}`.text().then((s) => s.trim())
console.log(`Setting up services for user: ${serviceUser} (UID: ${userUid})`)
// Find where bun is installed // Find where bun is installed
const bunPath = await $`which bun` const bunPath = await $`which bun`
.quiet() .quiet()
@ -61,7 +68,7 @@ WantedBy=multi-user.target
writeFileSync(WEB_SERVICE_FILE, webServiceContent, "utf8") writeFileSync(WEB_SERVICE_FILE, webServiceContent, "utf8")
console.log("✓ Created phone-web.service") console.log("✓ Created phone-web.service")
// Create phone service // Create phone service (system service with environment variables for audio access)
const phoneServiceContent = `[Unit] const phoneServiceContent = `[Unit]
Description=Phone Application Description=Phone Application
After=network.target sound.target After=network.target sound.target
@ -69,7 +76,10 @@ Requires=sound.target
[Service] [Service]
Type=simple Type=simple
User=corey User=${serviceUser}
Group=audio
Environment=XDG_RUNTIME_DIR=/run/user/${userUid}
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${userUid}/bus
ExecStart=${bunPath} ${installDir}/src/main.ts ExecStart=${bunPath} ${installDir}/src/main.ts
WorkingDirectory=${installDir} WorkingDirectory=${installDir}
EnvironmentFile=${installDir}/.env EnvironmentFile=${installDir}/.env
@ -90,9 +100,9 @@ WantedBy=multi-user.target
await $`systemctl enable phone.service` await $`systemctl enable phone.service`
console.log("✓ Services enabled") console.log("✓ Services enabled")
console.log("\nStarting the services...") console.log("\nRestarting the services...")
await $`systemctl start phone-ap.service` await $`systemctl restart phone-ap.service`
await $`systemctl start phone-web.service` await $`systemctl restart phone-web.service`
await $`systemctl start phone.service` await $`systemctl restart phone.service`
console.log("✓ Services started") console.log("✓ Services restarted")
} }

View File

@ -14,7 +14,8 @@ if (process.getuid && process.getuid() !== 0) {
} }
// Get install directory from argument or use default // Get install directory from argument or use default
const INSTALL_DIR = process.argv[2] || "/home/corey/phone" const defaultUser = process.env.USER || "corey"
const INSTALL_DIR = process.argv[2] || `/home/${defaultUser}/phone`
console.log(`Install directory: ${INSTALL_DIR}`) console.log(`Install directory: ${INSTALL_DIR}`)

View File

@ -2,8 +2,9 @@
import { $ } from "bun" import { $ } from "bun"
const defaultUser = process.env.USER ?? "corey"
const PI_HOST = process.env.PI_HOST ?? "phone.local" const PI_HOST = process.env.PI_HOST ?? "phone.local"
const PI_DIR = process.env.PI_DIR ?? "/home/corey/phone" const PI_DIR = process.env.PI_DIR ?? `/home/${defaultUser}/phone`
// Parse command line arguments // Parse command line arguments
const shouldBootstrap = process.argv.includes("--bootstrap") const shouldBootstrap = process.argv.includes("--bootstrap")

View File

@ -3,7 +3,8 @@
import { setupServices } from "./bootstrap-services" import { setupServices } from "./bootstrap-services"
// Get install directory from argument or use default // Get install directory from argument or use default
const INSTALL_DIR = process.argv[2] || "/home/corey/phone" const defaultUser = process.env.USER || "corey"
const INSTALL_DIR = process.argv[2] || `/home/${defaultUser}/phone`
console.log(`Setting up services for: ${INSTALL_DIR}`) console.log(`Setting up services for: ${INSTALL_DIR}`)

View File

@ -46,7 +46,7 @@ export const runPhone = async (agentId: string, agentKey: string) => {
using rotaryInUse = gpio.input(22, { pull: "up", debounce: 3 }) using rotaryInUse = gpio.input(22, { pull: "up", debounce: 3 })
using rotaryNumber = gpio.input(23, { pull: "up", debounce: 3 }) using rotaryNumber = gpio.input(23, { pull: "up", debounce: 3 })
await Buzz.setVolume(0.4) await Buzz.setVolume(0.2)
log(`📞 Phone is ${hook.value ? "off hook" : "on hook"}`) log(`📞 Phone is ${hook.value ? "off hook" : "on hook"}`)
playStartRing(ringer) playStartRing(ringer)

View File

@ -1,6 +1,7 @@
import Buzz from "../buzz/index.ts" import Buzz from "../buzz/index.ts"
import { join } from "path" import { join } from "path"
import { random } from "./index.ts" import { random } from "./index.ts"
import { log } from "console"
export class WaitingSounds { export class WaitingSounds {
typingPlayback?: Buzz.Playback typingPlayback?: Buzz.Playback
@ -38,39 +39,42 @@ export class WaitingSounds {
const playedSounds = new Set<string>() const playedSounds = new Set<string>()
let dir: SoundDir | undefined let dir: SoundDir | undefined
return new Promise<void>(async (resolve) => { return new Promise<void>(async (resolve) => {
// Don't start playing speaking sounds until the operator stream has been silent for a bit
while (operatorStream.bufferEmptyFor < 1500) { while (operatorStream.bufferEmptyFor < 1500) {
await Bun.sleep(100) await Bun.sleep(100)
} }
do { do {
const lastSoundDir = dir const lastSoundDir = dir
const value = Math.random() * 100
if (lastSoundDir === "body-noises") { if (lastSoundDir === "body-noises") {
dir = "apology" dir = "apology"
} else if (value > 99 && !lastSoundDir) {
dir = "body-noises"
} else if (value > 75 && !lastSoundDir) {
dir = "stalling"
} else { } else {
dir = undefined // sleep for 4-6 seconds
await Bun.sleep(1000) await Bun.sleep(4000 + Math.random() * 2000)
const value = Math.random() * 100
if (value > 95) {
dir = "body-noises"
} else {
dir = "stalling"
}
} }
if (dir) { const speakingSound = getSound(dir, Array.from(playedSounds))
const speakingSound = getSound(dir, Array.from(playedSounds)) this.speakingPlayback = await this.player.play(speakingSound)
this.speakingPlayback = await this.player.play(speakingSound) playedSounds.add(speakingSound)
playedSounds.add(speakingSound) await this.speakingPlayback.finished()
await this.speakingPlayback.finished()
}
} while (this.typingPlayback) } while (this.typingPlayback)
resolve() resolve()
}) })
} }
async stop() { async stop() {
log(`🛑 Stopping waiting sounds. Has typingPlayback: ${!!this.typingPlayback}`)
if (!this.typingPlayback) return if (!this.typingPlayback) return
await Promise.all([this.typingPlayback.stop(), this.speakingPlayback?.finished()]) await Promise.all([this.typingPlayback.stop(), this.speakingPlayback?.finished()])
log("🛑 Waiting sounds stopped")
this.typingPlayback = undefined this.typingPlayback = undefined
} }
} }