A Network Text-To-Speech Voice Announcer which allows any networked UDP nodes to send text messages for speaking by this SAM TTS node.
A small and neat self-contained 'dual-core' device can be made using a Wemos dual base adapter, allowing two D1 Mini's to sit side by side.
You will need to cut the interconnecting tracks for TX, RX and Reset.
The trick is to ensure both devices do not output on the same gpio pins so those pins can remain interconnected.
Only the SAM Speaker firmware needs to use gpio2 for its speaker output.
And SAM's gpio0 Busy output signal can conveniently be interconnected directly to the Annex gpio0 Busy interrupt input - so no wire is even needed.
We cannot prevent ESP8266 devices from outputting serial info at bootup.
So rather than feed SAM RX input from our Annex TX output (which would generate unwanted bootup noise) it is better to send using serial2gpio5.
This (purple) wire link is the only actual wiring mod needed on the dual base adapter, and allows us to send only those messages we wish to speak, and when we want it to.
It also leaves the Annex hardware serial free to be used as a voice test input - whereby any text sent from a serial console connected to the Annex USB is received on the hardware serial port and passed straight out through software serial2 gpio5 to be read on SAM RX and spoken audio output on SAM gpio2.
The script includes a circular FIFO (First In, First Out) array to ensure new incoming messages are queued to prevent loss of previous messages.
The Annex Network Node module uses EasyNet to receive UDP text messages sent to it by other network nodes, which it then forwards over serial2 to be spoken by the SAM Speaker module.
The EasyNet message format is: TargetNodeName Instruction optional data (name and instruction are not case sensitive, data remains as-is).
This application uses nodename SAM to recognise the instruction SAY followed by "the message string within guotes"
The node name is easily changed, and so can instructions names in the local instructionslist$ if their corresponding branch is similarly renamed.
In this case, SAM will also recognise the instruction IP which will cause it to branch to the IP subroutine to announce its local IP address.
It will also recognise and respond to the REPLY instruction, eg: ALL REPLY will cause EasyNet nodes to return a response message.
So it is not actually essential to address the node by its unique node name, because EasyNet nodes will also respond to the collective name of "All", or any of the names that are present in their Groupname" (which can contain multiple group names), or it will also respond to its local IP address.
To test the dual device for correct operation, use the Toolkit UDP console and send a message, eg: All Say "Hello world" or SAM SAY "Hi, I am Sam"
(don't forget to ensure the UDP console is set to the correct subnet and port)
Any networked nodes can then send similar appropriate messages whenever their sensors are triggered by something which needs to be verbally announced.
Basic:
'SAM Voice Announcer - Networked Text 2 Speech using EasyNet and Circular FIFO queue for SAM Speaker TTS firmware installed on a 2nd module nodename$ = "SAM"
groupname$ = "Voice\Announcer\Speakers\TTS\Speech"
localIP$ = WORD$(IP$,1)
netIP$ = WORD$(localIP$,1,".") + "." + WORD$(localIP$,2,".") + "." + WORD$(localIP$,3,".") + "."
nodeIP$ = WORD$(localIP$,4,".")
udpport = 5001 'change to suit your own preference, but don't forget to do the same for all nodes
if nodename$ = "" then nodename$ = "Node" + nodeIP$
udp.begin(udpport)
onudp udpRX
udpmsg$ = "" 'variable to hold incoming UDP message
instructionslist$ = ucase$("Reply Say IP ") 'List of Subdir branches available to action as remote instructions
instruction$ = ""
buffersize = 20 'needs to be one more than the max message queue size
dim q$(buffersize)
qitem$ = "" 'queue data variable
qsize = 0 'size of queue
qfront = 0
qback = 0
serial.mode 115200 'Hardware serial
serial2.mode 115200, 5, 4 'Software serialled_pin = 13 'Optional Busy/Ready status LEDpin.mode led_pin, outputready = 0 'Ready state of Busy pin signalbusy_pin = 0 'modules hardware Busy signalpin.mode busy_pin, input, pullupinterrupt busy_pin, busyonserial serialinqitem$ = "SAM, Text 2 Speech Voice Announcer."gosub say'pause 3000gosub IPtimer0 2000, dQwaitsay:qpushgosub dQreturnIP:qitem$ = word$(ip$,1)qitem$ = "This I P address is " + replace$(qitem$,"."," dot ")gosub sayreturndQ:'if pin(busy_pin) = ready then qitem$ = "" if qsize > 0 then qpull if qitem$ <> "" then print2 qitem$ endif'endifreturnudpRX:udpmsg$ = udp.read$target$ = ucase$(word$(udpmsg$,1)) 'Target is always first word of message - may be NodeName or GroupName or "ALL" or localIP addrif (target$=localIP$) OR (target$=ucase$(nodename$)) OR (target$=ucase$(groupname$)) OR (instr(ucase$(groupname$),target$)>0) OR (target$="ALL") then '"Target Name or IP matched" instruction$ = trim$(ucase$(word$(udpmsg$,2))) 'Instruction is always second word of messagewlog udpmsg$ if word.find(ucase$(instructionslist$),instruction$) > 0 then 'check if a recognised as valid on local instructionslist$ qitem$ = word$(udpmsg$,2,|"|) gosub instruction$ 'branch to action the corresponding instruction subroutine else udp.reply udpmsg$ + " INSTRUCTION NOT RECOGNISED" endif 'word.find instruction$endifreturnserialin:print2 serial.input$returnbusy:if pin(busy_pin) = ready then pin(led_pin) = 0 gosub dQelse pin(led_pin) = 1endifreturnsub qpushif qsize + 1 >= buffersize then print2 "ERROR: Queue is full" endelse q$(qback) = qitem$ ' Push item to back of q qback = (qback + 1) mod buffersize ' Adjust the back pointerendifqsize = (qback - qfront) mod buffersizeend subsub qpullif qsize < 1 then print2 "ERROR: Queue is empty" endelse qitem$ = q$(qfront) ' Pull item from front of q qfront = (qfront + 1) mod (buffersize) ' Adjust the front pointerendifqsize = (qback - qfront) mod buffersizeend subREPLY:udp.reply "Reply from " + Nodename$return---------------- end -----------------
HINT:
SAM translates English text into audio speech, plus it understands four punctuation marks: the hyphen (-), comma (,), period (.), and question mark (?).
The hyphen (-) serves to mark clause boundaries by inserting a short pause in the speech.
The comma marks phrase boundaries and inserts a longer pause approximately double that of the hyphen.
The period adds a pause and also causes the pitch to fall.
The question-mark also adds a pause, but it causes the pitch to rise.
You can sprinkle those punctuation marks liberally in your text to make it pronounce better and sound more to your liking.