In part I we examined the modem to verify that it supported voice. If so, we took a note about the voice data format that we would use. In the second part, we prepared a wave file and implemented a piece of code in C# to be used by HomeZIX to read the wave file into a buffer. Now, it’s time to put things together to send out that buffer as an audio stream over the phone line to a designated phone number.
In our example here, we are connecting to a voice modem via COM10. It is an old one we got from a local store. We have prepared a wave file: “Houston 8 bit Mono 8kHz.wav” which has the formats: PCM/uncompressed, 8 bits per sample, single channel (mono), 8000 samples per second. We dropped into the HomeZIX’s workplace a virtual switch to control when to start calling the number and playing the wave file. Of course, it’s only for demonstration purposes. In real use, it should call the number and choose appropriate wave file to play according to particular events such as: motion detected, temperature too high, door open, etc…
Download the script to import the C# script to HomeZIX and take a moment to go through the implementation:
Initialize
Expand|Select|Wrap|Line Numbers
- public void Initialize()
- { // Required function. DO NOT remove or change the name.
- // Called once when the script start executing.
- string fileName = "C:\\houston 8kHz Mono 8.wav";
- if (!System.IO.File.Exists(fileName))
- {
- Debug("Wave file not found.");
- return;
- }
- System.IO.FileStream strm = new System.IO.FileStream(fileName, System.IO.FileMode.Open);
- System.IO.BinaryReader rdr = new System.IO.BinaryReader(strm);
- Wave.WAVEFORMATEX wfmt = new Wave.WAVEFORMATEX();
- wfmt.SeekTo(strm);
- // Read in the WAVEFORMATEX structure and attempt to open the
- // device for playback.
- wfmt.Read(rdr);
- Debug("Wave file information:");
- Debug("Wave file encoding: " + wfmt.wFormatTag.ToString());
- Debug("Channels: " + wfmt.nChannels.ToString());
- Debug("Bits per Sample: " + wfmt.wBitsPerSample.ToString());
- Debug("Sampling rate: " + wfmt.nSamplesPerSec.ToString());
- if ((wfmt.wFormatTag == 1)&&(wfmt.nChannels == 1) && (wfmt.wBitsPerSample == 8) && (wfmt.nSamplesPerSec == 8000))
- {
- uint dataLength = (uint)(rdr.BaseStream.Length - Wave.WAVEFORMATEX.WF_OFFSET_DATA);
- m_whdr = new Wave.WAVEHDR();
- m_whdr.Read(rdr, dataLength, wfmt.nBlockAlign);
- Debug("Wave file data has been read successfully.");
- } else
- {
- Debug("Unsupported wave file.");
- Debug("This example supports only [PCM/Uncompressed], [Mono], [8 bits per sample], [8kHz samples per second] wave files.");
- }
- rdr.BaseStream.Close();
- rdr.Close();
- rdr = null;
- //Initializing COM port
- m_serialPort = new System.IO.Ports.SerialPort();
- m_serialPort.PortName = "COM10";
- m_serialPort.BaudRate = 115200;
- m_serialPort.DataBits = 8;
- m_serialPort.StopBits = System.IO.Ports.StopBits .One;
- m_serialPort.Parity = System.IO.Ports.Parity.None;
- m_serialPort.Handshake = System.IO.Ports.Handshake.None;
- m_serialPort.DtrEnable = true;
- try
- {
- m_serialPort.Open();
- Debug("COM port is ready.");
- } catch
- {
- Debug("Error opening the COM port.");
- }
- m_talking = false;
- }
Execute
This function detects if the virtual switch has been turned on. If so, it starts the calling process. Because this function is called every second, we don’t want to monopolize the processing time of the main program by processing the modem connection here. Instead, we spawn another thread to do that job.
Expand|Select|Wrap|Line Numbers
- public void Execute()
- { // Required function. DO NOT remove or change the name.
- // Called every second.
- int status = GetStatus("Virtual switch..0.0");
- if ((m_virtualSwitchPreviousState == 0) &&(status > 0))
- {
- if (!m_talking)
- {
- m_talking = true;
- m_phoneThread = new System.Threading.Thread(new System.Threading.ThreadStart(WaveToPhone));
- m_phoneThread.Start();
- }
- m_virtualSwitchPreviousState = status;
- }
- if ((m_virtualSwitchPreviousState > 0) &&(status == 0))
- {
- m_talking = false;
- m_virtualSwitchPreviousState = status;
- }
- }
Preparing the modem: These are commands to put the modem in voice mode with our selected voice data encoding format. When a command has been sent to the modem, the script waits for the response. We didn’t check the response here since we know it works (from going through Part I.) We simply print out the debug string. The last steps of this phase is to call the number (“ATDT number”) and switch the modem to the sending voice data mode (“AT+VTX”)
Expand|Select|Wrap|Line Numbers
- private void WaveToPhone()
- {
- if ((m_serialPort != null)&&(m_serialPort.IsOpen))
- {
- SendCommand("ATZ");
- GetResponse(1, true);
- SendCommand("AT+FCLASS=8");
- GetResponse(1, true);
- SendCommand("AT+VSM=128,8000");
- GetResponse(1, true);
- SendCommand("ATDT" + m_phoneNumber);
- GetResponse(60, false);
- SendCommand("AT+VTX");
- System.Threading.Thread.Sleep(500);
- SendVoiceData();
- System.Threading.Thread.Sleep(1000);
- SendVoiceData();
- System.Threading.Thread.Sleep(1000);
- SendVoiceData();
- System.Threading.Thread.Sleep(1000);
- byte [] terminator = new byte [2];
- terminator[0] = 0x10;
- terminator[1] = 0x03;
- m_serialPort.Write(terminator, 0, 2);
- SendCommand("ATH");
- }
- m_talking = false;
- }
In the Initialize function, we already read the data into the buffer from a wave file. It’s time now to transfer that buffer as an audio stream to the modem. However, because the modem likely cannot handle large amount of data at once, we send data in chunks of 1024 bytes each. This greatly improves the total throughput ensuring smooth voice on the other end. In our example there, we also repeat the same data buffer 3 times.
Expand|Select|Wrap|Line Numbers
- private void SendVoiceData()
- {
- if ((m_whdr == null)||(!m_serialPort.IsOpen)) return;
- int offset = 0;
- int blockSize = 1024;
- int dataLength = m_whdr.data.Length;
- if (dataLength == 0) return;
- try
- {
- Debug("Start sending WAVE data...");
- while (true)
- {
- m_serialPort.Write(m_whdr.data, offset, blockSize);
- if (blockSize < 1024) break;
- offset += blockSize;
- if (dataLength - offset < 1024) blockSize = dataLength - offset;
- }
- Debug("Done sending WAVE data.");
- } catch
- {
- Debug("Error sending WAVE data.");
- }
- }
Once the wave data has been sent, we terminate the connection by: sending 0x10 0x03 to the modem to switch off the sending mode, and then hang up the phone by sending “ATH”. Note that if you plan to have multiple scripts handling different wave files for different events, you will have to close the COM port here as well.