Handle dial failures with error tone and return to dial tone
When a dial command times out (e.g., due to SIP registration failure): - Play an error tone (fast busy signal) - Return to ready state (dial tone resumes) This provides feedback to the user when calls can't be placed. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2428afd3db
commit
bed1fa0eb8
16
src/phone.ts
16
src/phone.ts
|
|
@ -119,6 +119,11 @@ const startBaresip = async (phoneService: PhoneService, hook: GPIO.Input, ringer
|
||||||
phoneService.send({ type: "remote-hang-up" })
|
phoneService.send({ type: "remote-hang-up" })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
baresip.dialFailed.on(({ reason }) => {
|
||||||
|
log.error("🐻 dial failed:", reason)
|
||||||
|
phoneService.send({ type: "dial-failed" } as any)
|
||||||
|
})
|
||||||
|
|
||||||
baresip.connect().catch((error) => {
|
baresip.connect().catch((error) => {
|
||||||
log.error("🐻 connection error:", error)
|
log.error("🐻 connection error:", error)
|
||||||
phoneService.send({ type: "error", message: error.message })
|
phoneService.send({ type: "error", message: error.message })
|
||||||
|
|
@ -342,6 +347,16 @@ const answerCall = (ctx: PhoneContext) => {
|
||||||
ctx.baresip.accept()
|
ctx.baresip.accept()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const playDialFailedMessage = async () => {
|
||||||
|
log.error("📞 Call failed - playing error tone")
|
||||||
|
// Play fast busy signal (reorder tone)
|
||||||
|
const errorTone = await player.playTone([480, 620], 500)
|
||||||
|
await sleep(500)
|
||||||
|
errorTone.stop()
|
||||||
|
await player.playTone([480, 620], 500)
|
||||||
|
await sleep(500)
|
||||||
|
}
|
||||||
|
|
||||||
const makeCall = async (ctx: PhoneContext) => {
|
const makeCall = async (ctx: PhoneContext) => {
|
||||||
log(`Dialing number: ${ctx.numberDialed}`)
|
log(`Dialing number: ${ctx.numberDialed}`)
|
||||||
if (ctx.numberDialed === 1) {
|
if (ctx.numberDialed === 1) {
|
||||||
|
|
@ -457,6 +472,7 @@ const phoneMachine = createMachine(
|
||||||
t("hang-up", "idle")),
|
t("hang-up", "idle")),
|
||||||
outgoing: invoke(makeCall,
|
outgoing: invoke(makeCall,
|
||||||
t("answered", "connected"),
|
t("answered", "connected"),
|
||||||
|
t("dial-failed", "ready", a(playDialFailedMessage)),
|
||||||
t("hang-up", "idle", a(hangUp))),
|
t("hang-up", "idle", a(hangUp))),
|
||||||
aborted: state(
|
aborted: state(
|
||||||
t("hang-up", "idle")),
|
t("hang-up", "idle")),
|
||||||
|
|
|
||||||
19
src/sip.ts
19
src/sip.ts
|
|
@ -8,6 +8,7 @@ export class Baresip {
|
||||||
callEstablished = new Emitter<{ contact: string }>()
|
callEstablished = new Emitter<{ contact: string }>()
|
||||||
callReceived = new Emitter<{ contact: string }>()
|
callReceived = new Emitter<{ contact: string }>()
|
||||||
hungUp = new Emitter()
|
hungUp = new Emitter()
|
||||||
|
dialFailed = new Emitter<{ reason: string }>()
|
||||||
error = new Emitter<{ message: string; statusCode?: string; reason?: string }>()
|
error = new Emitter<{ message: string; statusCode?: string; reason?: string }>()
|
||||||
registrationSuccess = new Emitter()
|
registrationSuccess = new Emitter()
|
||||||
|
|
||||||
|
|
@ -39,8 +40,11 @@ export class Baresip {
|
||||||
executeCommand("a")
|
executeCommand("a")
|
||||||
}
|
}
|
||||||
|
|
||||||
dial(phoneNumber: string) {
|
async dial(phoneNumber: string) {
|
||||||
executeCommand(`d${phoneNumber}`)
|
const success = await executeCommand(`d${phoneNumber}`)
|
||||||
|
if (!success) {
|
||||||
|
this.dialFailed.emit({ reason: "Command timed out - registration may have failed" })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hangUp() {
|
hangUp() {
|
||||||
|
|
@ -52,6 +56,7 @@ export class Baresip {
|
||||||
this.callReceived.removeAllListeners()
|
this.callReceived.removeAllListeners()
|
||||||
this.hungUp.removeAllListeners()
|
this.hungUp.removeAllListeners()
|
||||||
this.registrationSuccess.removeAllListeners()
|
this.registrationSuccess.removeAllListeners()
|
||||||
|
this.dialFailed.removeAllListeners()
|
||||||
this.error.removeAllListeners()
|
this.error.removeAllListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,15 +118,21 @@ export class Baresip {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const executeCommand = async (command: string) => {
|
const executeCommand = async (command: string): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const url = new URL(`/?${command}`, "http://127.0.0.1:8000")
|
const url = new URL(`/?${command}`, "http://127.0.0.1:8000")
|
||||||
const response = await Bun.fetch(url)
|
const controller = new AbortController()
|
||||||
|
const timeout = setTimeout(() => controller.abort(), 10000)
|
||||||
|
|
||||||
|
const response = await fetch(url, { signal: controller.signal })
|
||||||
|
clearTimeout(timeout)
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Error executing command: ${response.statusText}`)
|
throw new Error(`Error executing command: ${response.statusText}`)
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("Failed to execute command:", error)
|
log.error("Failed to execute command:", error)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user