#!/usr/bin/python3
# Dial-a-Verse - Peter G Wulfing 2025
# Instructables Dec 2025
# Features:
#		Variable Dial Tone
#		Smarter Dial End Detection
#		Voice clock from ICON version
#		Time-zone sensitive Voice Clock

import RPi.GPIO as GPIO
from time import sleep
import random
import simpleaudio as sa
import sys, os , csv , datetime
from datetime import timezone
from playsound3 import playsound
from playsound3 import AVAILABLE_BACKENDS, DEFAULT_BACKEND

print(AVAILABLE_BACKENDS)  # for example: ["gstreamer", "ffmpeg", ...]
print(DEFAULT_BACKEND)  # for example: "gstreamer"

debug = True
from contextlib import redirect_stdout

#with open('file', 'w') as sys.stdout:

#language="spa"
errlang = "eng"
original_stdout = sys.stdout

led1 = 8
led2 = 10

red = 8
grn = 10

tstpin = 16
state = 0

GPIO.setwarnings(False)			#disable warnings
GPIO.setmode(GPIO.BOARD)		#set pin numbering system
GPIO.setup(led1,GPIO.OUT)
GPIO.setup(tstpin,GPIO.OUT)
GPIO.setup(led2,GPIO.OUT)

hookpin =  37
dialpin =  13 # 32

#GPIO.setup(hookpin, GPIO.IN, pull_up_down=GPIO.PUD_UP) # board pin 37 GPIO 26
GPIO.setup(hookpin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) # board pin 37 GPIO 26
#GPIO.setup(hookpin, GPIO.IN, pull_up_down=GPIO.PUD_OFF) # board pin 37 GPIO 26
GPIO.setup(dialpin, GPIO.IN, pull_up_down=GPIO.PUD_UP) # board pin 32 GPIO 12

phonenumber = []
dialpulses = 0
timeout = 1500
#dialto = 50000 # end of dial timer
dcount = 0 
offhook = 1
onhk = 0

#States

onhook_st = 0
dialtone_st = 1
dial_st = 2
error_st = 3
searchplay_st = 4
setlang_st = 5
vclock_st = 6
busy_st = 7

with open('/proc/cpuinfo', 'r') as f:
	for line in f:
		if "Model" in line :
			model = line
# set end of dial timer accoring to RP Model - This will need to be adapted for other Rasp Pi models i.e. 3, 4 
if "Zero 2" in model :
	dialto = 110000
else :
	dialto = 50000
#
print(model + "dialto=" + str(dialto))
 

def dialpulse(pin) : #IRQ for counting dial pulses 
	global dialpulses 
	GPIO.output(tstpin, 1)  #Light on
	dialpulses = dialpulses + 1 
#	GPIO.output(tstpin, 0)

def onhook() :
	global dialpulses
	global phonenumber
	GPIO.remove_event_detect(dialpin)
	GPIO.remove_event_detect(hookpin)	
	GPIO.output(red, 1)  #RLight on
	GPIO.output(grn, 0)  #GLight off	
#	sa.stop_all()
	phonenumber = []
	dialpulses = 0 
	hook = GPIO.input(hookpin)
	print("O Hook",hook , flush=True)
	print("Operator Language: ",errlang)
	while hook == onhk :
		hook = GPIO.input(hookpin)
		sleep(0.2)
	return(dialtone_st)  #goto dialtone

def dialtone() :
	hookcnt = 0
	GPIO.remove_event_detect(dialpin)
	sa.stop_all()
	GPIO.output(red, 0)  #rled off 	
	dialtone_snd = sa.WaveObject.from_wave_file("/home/pi/phone/sounds/dial-tonethirtysec.wav")
	play_obj = dialtone_snd.play()
	dial = GPIO.input(dialpin)
	print("OFH", dial , flush = True)
	while play_obj.is_playing() and GPIO.input(dialpin) == 0 and hookcnt < 3 :
		if not GPIO.input(hookpin) == offhook :
			sleep(0.1)
			if not  GPIO.input(hookpin) == offhook :
				hookcnt += 1
				print("HKD",hookcnt)
			else: 
				hookcnt = 0
	if play_obj.is_playing() and GPIO.input(dialpin) == 1 :  #Offhook and dialtone playing... dial pulled back
		sa.stop_all()	
		return(dial_st) 
	elif (not play_obj.is_playing()) :
		return(error_st) # 30 sec dial tone timed out
	else :
		sa.stop_all()
		return(onhook_st) # hung up 		

def waitdial() :
	while True :
		count = 0
		while GPIO.input(dialpin) == 0 :
			GPIO.output(led1,1)  #Light on
			count +=1
			print(count) 
			GPIO.output(led1,0)  #Light out
			if count > dialto :
				print("dcount",count)
				GPIO.output(led1,0)  #Light out
				return()
			

def dial() : #returns 3 digit number or error
	global dialpulses 
	global phonenumber
	global timeout
	global dialto
	global dcount
#	print("Dial",dialpulses, flush=True)
	GPIO.output(red, 0)  #rled off

#	sa.stop_all()
	time = 0
#	while GPIO.input(dialpin) == 0 : #Wait for dial pullback - first dial from dialtone already is
	while GPIO.input(dialpin) == 0 and GPIO.input(hookpin) == offhook: #Wait for dial pullback - first dial from dialtone already is
		time +=1
		sleep(0.01)
		if time > timeout :  # 15 sec to dial the next number
			print("TimeOut", flush=True)
			return(error_st) #waited too long for next dial
	if GPIO.input(hookpin) == onhk :
		return(onhook_st)
	GPIO.output(led2,1)  #GLight on
#	GPIO.add_event_detect(dialpin, GPIO.FALLING, callback=dialpulse, bouncetime=75)	#was 90
	GPIO.add_event_detect(dialpin, GPIO.RISING, callback=dialpulse, bouncetime=90)	#was 90
	dcount = 0 
	while dialpulses == 0 : # Wait for new dial 
		time +=1
		sleep(0.01)
		if time > timeout :  # 15 sec to dial the next number
			print("TimeOut", flush=True)
			return(error_st) #waited too long for next dial
#	GPIO.output(led1,1)  #Light on
	dpm = dialpulses
	while dcount < dialto :
		dcount +=1
		if dpm < dialpulses : # reset dcount when a new dial pulse comes in
			dpm = dialpulses
			dcount = 0


	GPIO.output(led2,0)  #Light off	
	GPIO.remove_event_detect(dialpin)
	if GPIO.input(hookpin) == onhk :
		sleep(0.2)
		if GPIO.input(hookpin) == onhk :
			return(onhook_st)
	if dialpulses > 9 :
		dialpulses = dialpulses - 10
	if debug :
		print("DD",len(phonenumber) + 1, dialpulses,time, flush=True)
	phonenumber.append(dialpulses)
	dialpulses = 0
	if len(phonenumber) < 3 :
		return(dial_st)
	else :
		return(searchplay_st )

def error() :
	print("ERROR!", flush=True)
	return(busy_st)

def busy() :
	busy_snd = sa.WaveObject.from_wave_file("/home/pi/phone/sounds/phone-busy-tone.wav")
	play_obj = busy_snd.play()
	hook = GPIO.input(hookpin)
	print("Busy",hook, flush=True)
	while hook == offhook and play_obj.is_playing() :
		hook = GPIO.input(hookpin)
		sleep(0.2)	
	if play_obj.is_playing() : # dt still playing we got a hangup
		stop_obj = play_obj.stop()
		sa.stop_all()
		return(onhook_st)
	else :
		return(busy_st)


def playorhang(play_obj) :
#	hook = GPIO.input(hookpin)
	hookcnt = 0
	while hookcnt < 2 and play_obj.is_playing() :  #hangup delay in case caused by noise
		sleep(0.2)
		if not  GPIO.input(hookpin) == offhook :
			hookcnt += 1
			if debug == True :
				print("POHHK",hookcnt)
		else: 
			hookcnt = 0
	if play_obj.is_playing() : # dt still playing we got a hangup
		stop_obj = play_obj.stop()
		return()
	else :			#play ended 

		return()	

def playorhangv(sound) : # Same as above, but tailored to VLC palyer 
#	hook = GPIO.input(hookpin)
	hookcnt = 0
#	while hookcnt < 3 and play_obj.is_playing() :  #hangup delay in case caused by noise
	while hookcnt < 2 and sound.is_alive() :  #hangup delay in case caused by noise
		sleep(0.1)
		if not  GPIO.input(hookpin) == offhook :
			hookcnt += 1
			if debug == True :
				print("VHK",hookcnt)
		else: 
			hookcnt = 0
	sound.stop()
	return()


def setlang() : #This is for the ICON version which plays hundreds of languages
#	global phonenumber
	global errlangs
	global language 
	global ss
	langset = ''
#	ss = "".join(str(x) for x in phonenumber)

	print("Setlang Dialed #" + ss, flush=True) 
	with open('langnames_codes.csv', newline='') as csvfile: 
		langs = reader = csv.DictReader(csvfile)
		for row in langs :
			if ss == row['num4'] :
				langset = row['iso']
				break
	if langset != '' :
		language = langset 
		errlang = row['errlang']
		beep_snd = sa.WaveObject.from_wave_file("/home/pi/phone/sounds/dlangs/"+ language + ".wav")
		play_obj = beep_snd.play()
		play_obj.wait_done()
		return(onhook_st)
	else :
		return(error_st)
		
def saynum(ss) :
#	global ss
	global errlang
	for num in ss :
		deb_digit = sa.WaveObject.from_wave_file("/home/pi/phone/sounds/vclock/"+errlang+"_"+num+".wav")
		play_obj = deb_digit.play()
		play_obj.wait_done()
	return()


def searchplay() :
	global phonenumber
	global debug
	global errlang
	searchString = "".join(str(x) for x in phonenumber)
	print("Dialed #", searchString, flush=True)
	phonenumber = []
	found = 0
# searchfor and find a file in a directory
	if searchString in "099_setdebug" :
#		debug = not debug
		debug = True
		print("DB ",debug, flush=True)
		beep = playsound("/home/pi/phone/sounds/beep.wav")
		beep.wait()
		return(onhook_st)
	if searchString in "098_setdebug" :
		debug = False
		print("DB ",debug, flush=True)
		return(onhook_st)
	if searchString in "097_seteng" :
		errlang="eng"
		print("Operator language: ",errlang)
		dly_snd = sa.WaveObject.from_wave_file("/home/pi/phone/sounds/onesec.wav")
		play_obj = dly_snd.play()
		play_obj.wait_done()
		lang_snd = sa.WaveObject.from_wave_file("/home/pi/phone/sounds/dlangs/" + errlang + ".wav")
		play_obj = lang_snd.play()
		play_obj.wait_done()
		return(error_st)
	if searchString in "096_setspa" :
		errlang="spa"
		print("Operator language: ",errlang)
		dly_snd = sa.WaveObject.from_wave_file("/home/pi/phone/sounds/onesec.wav")
		play_obj = dly_snd.play()
		play_obj.wait_done()
		lang_snd = sa.WaveObject.from_wave_file("/home/pi/phone/sounds/dlangs/" + errlang + ".wav")
		play_obj = lang_snd.play()
		play_obj.wait_done()
		return(error_st)
	if searchString in "900_" :  # Dial 900 to hear the talking clock
		return(vclock_st)
#
		
	directory = os.listdir("/home/pi/www/Shared/audio/")
	for fname in directory: # change directory as needed
		if searchString in fname:
			found = 1
			ring_snd = sa.WaveObject.from_wave_file("/home/pi/phone/sounds/ring-tone4.wav")
			play_obj = ring_snd.play()
#			ring_snd = playsound("/home/pi/phone/sounds/ring-tone4.wav", block=False)
			rng_dly = random.randrange(3,5)  #random delay for call pickup
			print("dly",rng_dly)
#			sound = playsound("/home/pi/www/Shared/audio/" + fname, block=False)
			sleep(rng_dly)
			sa.stop_all()			
			print("Play:", "/home/pi/www/Shared/audio/" + fname)
			sound = playsound("/home/pi/www/Shared/audio/" + fname, block=False)
			playorhangv(sound)
			sound.stop()
			if GPIO.input(hookpin) == onhook :
				return(onhook_st)
			no_snd = sa.WaveObject.from_wave_file("/home/pi/phone/sounds/thirty_sec_silence.wav")
			play_obj = no_snd.play()
#			no_snd = playsound("/home/pi/phone/sounds/thirty_sec_silence.wav")
			playorhang(play_obj)
			return(onhook_st)		#if they don't hang up during the 30 sec, return to onhook anyway
#			play_obj.wait_done()
#			return(onhook_st)
	if found == 0 :
		print('Number not found', flush=True)
		if debug :
			saynum(searchString)
#			for num in searchString :
#				deb_digit = sa.WaveObject.from_wave_file("/home/pi/phone/sounds/num"+num+".wav")
#				play_obj = deb_digit.play()
#				play_obj.wait_done()
		nocomplete_snd = sa.WaveObject.from_wave_file("/home/pi/phone/sounds/"+errlang+"_nocomplete.wav")
		play_obj = nocomplete_snd.play()
#		ncomplete_snd = playsound("/home/pi/phone/sounds/nocomplete.wav")
		playorhang(play_obj)
		return(busy_st)
	hook = GPIO.input(hookpin)
	while hook == offhook :  #after success, wait in silence until hangup
		hook = GPIO.input(hookpin)
	return(onhook_st)

def vclock() :
	global errlang
	#timestr = now.strftime("%Y-%m-%d %H:%M:%S")
	now = datetime.datetime.now()
	
	hrs = now.strftime("%H")
	hrs = str(int(hrs))
	mins = now.strftime("%M")
	mins = str(int(mins))
	secs = now.strftime("%S")
	secs = str(int(secs))
	if hrs == 1 :
		hrs_snd = sa.WaveObject.from_wave_file("/home/pi/phone/sounds/vclock/"+errlang+"_hr.wav")
	else :
		hrs_snd = sa.WaveObject.from_wave_file("/home/pi/phone/sounds/vclock/"+errlang+"_hrs.wav")
	if mins == 1:
		min_snd = sa.WaveObject.from_wave_file("/home/pi/phone/sounds/vclock/"+errlang+"_min.wav")
	else :
		min_snd = sa.WaveObject.from_wave_file("/home/pi/phone/sounds/vclock/"+errlang+"_mins.wav")
#	sec_snd = sa.WaveObject.from_wave_file("/home/pi/phone/sounds/vclock/"+errlang+"_sec.wav")
	print("VCLOCK", hrs, mins, secs)
	timhr_snd = sa.WaveObject.from_wave_file("/home/pi/phone/sounds/vclock/"+errlang+"_"+hrs+".wav")
	play_obj = timhr_snd.play()
	play_obj.wait_done()
	play_obj = hrs_snd.play()
	play_obj.wait_done()
	timmin_snd = sa.WaveObject.from_wave_file("/home/pi/phone/sounds/vclock/"+errlang+"_"+mins+".wav")
	play_obj = timmin_snd.play()
	play_obj.wait_done()
	play_obj = min_snd.play() 
	play_obj.wait_done()
	if GPIO.input(hookpin) == onhk :
		return(onhook_st)
	else :
		return(vclock_st)

while True :
	GPIO.output(led1, 0)  #Light off
	hook = GPIO.input(hookpin)
	if hook == onhk :
		state = onhook_st
	if state == onhook_st :
		state = onhook();
	if state == dialtone_st :
		state = dialtone()
	if state == dial_st :
		state = dial()
	if state == error_st :
		state = error()
	if state == searchplay_st :
		state = searchplay()
	if state == setlang_st:
		state = setlang()
	if state == vclock_st:
		state = vclock()	
	if state == busy_st :
		state = busy()

