📜 ⬆️ ⬇️

perl script producing voice dialing (notification) via usb modem huawei e1550

At one time, since I spent a lot of time on business trips, I purchased a wonderful toy - usb modem Huawei e1550. But the times of dashing youth have passed, and the need to use this device for its intended purpose has disappeared. So he was gathering dust on my shelf for several years. And I would have collected dust even further, but the task arose of making a warning system. Then I remembered the modem. Having considered the task set, he was forced to abandon the SMS alert in favor of voice dialing due to the inability to receive notification of SMS reading. Solutions based on Asterisk seemed somewhat cumbersome to me, and after reading the modem dock, I decided to write a dialer myself.

Reason for posting.

Despite the abundance of articles on working with USSD and SMS requests, I did not find any implementation of voice calls in scripting languages ​​(such as Perl, PHP, Node.js). I hope this article will be a good help for you.

Development environment

operating system: Linux
Distribution: openSUSE 12.3
Kernel: 3.7.10-1.16-desktop # 1 SMP PREEMPT Fri May 31 20:21:23 UTC 2013 (97c14ba) i686 i686 i386 GNU / Linux
Programming language: Perl
usb modem: Huawei e1550
')
A bit of theory.

In most Linux distributions, when you connect this modem, 3 usb interfaces are created in / dev. usually this:
/ dev / ttyUSB0 - modem command interface
/ dev / ttyUSB1 - voice (with voice mode turned on) modem interface
/ dev / ttyUSB2 - modem command interface. It differs from / dev / ttyUSB0 in that it can read not only modem responses to commands, but also service messages. Such as signal quality data, ^ CEND output and so on.

To start working with a modem, just open one of the command interfaces for reading and writing as a file.
To send a command to a modem, you need to write it to an open interface file.
To get the modem's response to this command, you need to read it from the open interface file.

Teams that can be served to the modem are AT commands
The commands for the Huawei e1550 modem and the answers that can be obtained are described in its specification:
HUAWEI CDMA Datacard Modem AT Command Interface Specification
HUAWEI UMTS Datacard Modem AT Command Interface Specification

In order to activate the voice functions of the modem, you must issue the command AT ^ CVOICE = 0
Voice functions will be activated until they are disabled with the command AT ^ CVOICE = 1

In order to start receiving / transmitting audio information to a modem, you need to switch the modem audio port operation mode with each command using the AT command ^ DDSETEX = 2

The audio data to be transmitted to the modem should be in the format:
digitization frequency: 8000 Hertz.
number of channels: 1 (mono).
Bit to digitize: 16 signed.

Audio data should be fed to the modem audio port in batches of 320 bytes every 0.02 seconds.

Upon completion of the call, the modem via the 2nd command interface provides information about the call in the form of a CEND message.
output format ^ CEND: call_index, duration, end_status, cc_cause
Where:
call_index - unique call identifier
duration - call duration in seconds
end_status - device status code after call termination
cc_cause - reason code of the call termination

So. Let's start.

The dialer will consist of 3 files:
huawey_voice_call.pl - directly the voice dialing script itself.
list.01.pl - file with subscribers data.
test.voice.raw - file with voice message recorded in the correct format.

Also at the end of the article 2 additional files will be presented:
cc_cause.pl - contains codes for call termination (cc_cause)
end_status.pl - contains the device status codes after the end of the call (end_status)

all files in one archive (laid out on your computer, sometimes the computer is turned off)

consider huawey_voice_call.pl
  1. #! / usr / bin / perl
  2. # connect the Time :: HiRes module and import
  3. # in the current namespace function sleep
  4. # feature of this function - the ability to specify
  5. # delay less than second
  6. use Time :: HiRes qw ( sleep ) ;
  7. # connect cc_cause.pl file containing Disconnect cause codes
  8. # These codes show the reason for ending the call
  9. # Not used in this script, but for writing
  10. # full-fledged dialers need to analyze this
  11. # parameter in the ^ CEND output: call_index, duration, end_status, cc_cause
  12. # my% cc_cause = do 'cc_cause.pl';
  13. # connect the end_status.pl file containing Call endind cause codes
  14. # These codes show the status of the device after the end of the call
  15. # Not used in this script, but for writing
  16. # full-fledged dialers need to analyze this
  17. # parameter in the ^ CEND output: call_index, duration, end_status, cc_cause
  18. # my% end_status = do 'end_status.pl';
  19. # For information:
  20. # CEND type messages are issued by the modem at call completion
  21. # and contain information about the call, the reason for ending the call
  22. # and the status of the device.
  23. # output format ^ CEND: call_index, duration, end_status, cc_cause
  24. # where:
  25. # call_index - unique call identifier
  26. # duration - call duration in seconds
  27. # end_status - device status code after call termination
  28. # cc_cause - call completion reason code
  29. # when connecting a modem to a computer with OS Linux
  30. # 3 usb interfaces are created for data exchange with a modem
  31. # usually this:
  32. # / dev / ttyUSB0 - modem command interface
  33. # / dev / ttyUSB1 - voice (with voice mode turned on) modem interface
  34. # / dev / ttyUSB2 - modem command interface. Different from / dev / ttyUSB0 topics
  35. # that it is possible to read not only modem responses to commands, but also service ones
  36. # posts. Such as signal quality data, ^ CEND output, etc.
  37. # specify the port for sending sound to the modem
  38. $ VOICE_PORT = "/ dev / ttyUSB1" ;
  39. # specify the port for submitting commands to the modem
  40. $ COMMAND_PORT = "/ dev / ttyUSB2" ;
  41. # set to:
  42. # 0 - to disable debug output
  43. # 1 - to enable debug output
  44. $ VERBOSE = 1 ;
  45. # Open the command port of the modem for reading and writing
  46. open my $ SENDPORT , '+ <' , $ COMMAND_PORT or die "Can't open '$ COMMAND_PORT': $! \ n " ;
  47. # Open the modem voice port for reading and writing
  48. # reading audio stream from the port in this program is not used
  49. # but nothing prevents you from turning this script into an answering machine for example
  50. open my $ SENDPORT_WAV , '+ <' , $ VOICE_PORT or die "Can't open '$ VOICE_PORT': $! \ n " ;
  51. # connect the list.01.pl file containing subscriber data
  52. my @user_list = do 'list.01.pl' ;
  53. # call the call function, which passes 2 parameters:
  54. # 1st - file name with voice message
  55. # 2 - subscriber data array
  56. call_list ( "test.voice.raw" , \ @user_list ) ;
  57. # at the end of the call we close all open files / ports
  58. exit_call ( ) ;
  59. # this function calls subscribers on the list
  60. sub call_list {
  61. # get the name of the file with the voice message
  62. my $ l_file = shift ;
  63. # get link to list with subscriber data
  64. my $ l_list = shift ;
  65. # load data from file with voice message
  66. my $ l_voice = load_voice ( $ l_file ) ;
  67. # This cycle runs through the list of subscribers
  68. # and trying to dial
  69. foreach $ l_info ( @ { $ l_list } ) {
  70. # call the function of calling to the subscriber
  71. my $ l_msg = call_one ( $ l_info , $ l_voice ) ;
  72. # Display the received message
  73. print $ l_msg ;
  74. # before calling the next caller
  75. # wait for 3 seconds.
  76. sleep 3 ;
  77. }
  78. }
  79. # this function tries to call the specified number
  80. # and if successful - broadcast voice message
  81. sub call_one {
  82. my $ l_info = shift ; # HASH with current subscriber data
  83. my $ l_bufer = shift ; # array with 320 byte chunks of the head message
  84. # this command turns on voice mode in modem
  85. # Once you turn it on you can delete / zaremarit
  86. # this command. The modem will remember the state.
  87. #at_send ('AT ^ CVOICE = 0');
  88. # give the modem a command to dial the number $ l_info -> {phone}
  89. # and expect a response from the modem:
  90. # OK - the call was successful
  91. # NO CARRIER - no connection to the cellular network
  92. my $ l_rec = at_send ( "ATD $ l_info -> {phone};" , qr / ( OK | NO CARRIER ) / ) ;
  93. # in case the dialing did not happen - exit the function and return the corresponding message
  94. Return "Subscriber $ l_info -> {name} [$ l_info -> {phone}] is not notified. NETWORK \ n " if $ l_rec eq 'NO CARRIER' ;
  95. # we expect the subscriber to pick up the phone
  96. # CONN: .... - the subscriber picked up the phone
  97. # CEND: .... - the subscriber is unavailable, busy or dropped a call
  98. $ l_rec = at_rec ( qr / \ ^ (CONN \: 1 \, 0 | CEND \:) / ) ;
  99. # in case the subscriber did not pick up the phone, we exit the function and return the corresponding message
  100. Return "The $ l_info -> {name} [$ l_info -> {phone}] subscriber is not notified. NOT AVAILABLE or RESET \ n " if $ l_rec eq 'CEND:' ;
  101. # switch the modem to the mode of receiving / transmitting voice
  102. # OK - the switch was successful
  103. # ERROR - switching failed
  104. # CEND: .... - the subscriber is unavailable, busy or dropped a call
  105. $ l_rec = at_send ( 'AT ^ DDSETEX = 2' , qr / ( OK | ERROR | CEND \ :) / ) ;
  106. # in case you failed to switch to voice mode or the subscriber did not pick up
  107. # tube - exit the function and return the corresponding message
  108. Return "The subscriber $ l_info -> {name} [$ l_info -> {phone}] is not notified. NOT AVAILABLE or RESET \ n " if $ l_rec ne 'OK' ;
  109. # If you get here, then the call is established and the subscriber picked up the phone
  110. # Sound should be transmitted to the modem in portions of 320 bytes every 0.02 seconds.
  111. # Set the service variable $ | In unit it disables buffering.
  112. # Thus, the data in the audio port will be sent immediately.
  113. $ | = 1 ;
  114. # Buffer loop with 320 byte voice message chunks
  115. foreach my $ c ( @ { $ l_bufer } ) {
  116. # Record the next piece in the voice port of the modem
  117. syswrite $ SENDPORT_WAV , $ c , 320 ;
  118. # We expect 0.02 seconds before continuing the cycle
  119. sleep ( 0.02 ) ;
  120. }
  121. # Hang up.
  122. at_send ( 'AT + CHUP' ) ;
  123. # We return a message about a successful alert.
  124. return "Subscriber $ l_info -> {name} [$ l_info -> {phone}] SUCCESSFULLY AVAILABLE \ n " ;
  125. }
  126. # This function loads a voice message into an array of 320 byte chunks.
  127. # takes 1 parameter - file name
  128. # audio data format - pcm, mono, 8000 Hertz, 16 bits, signed
  129. sub load_voice {
  130. my $ l_file_name = shift ;
  131. my $ l_fh = new IO :: File "<$ l_file_name" or die "Cannot open $ l_file_name: $!" ;
  132. binmode ( $ l_fh ) ;
  133. my @l_bufer = ( ) ;
  134. while ( read ( $ l_fh , $ l_bufer [ $ i ] , 320 ) ) { $ i ++; }
  135. close $ l_fh ;
  136. return \ @l_bufer ;
  137. }
  138. # This function sends a command to the modem's command port.
  139. # and waiting for the response specified in the regular expression
  140. # takes 2 parameters:
  141. # 1st - team
  142. # 2 - Regular Expression describing the options for the expected answers (OK by default)
  143. sub at_send {
  144. my $ l_cmd = shift ;
  145. my $ l_rx = shift || qr / (OK) / ;
  146. print $ SENDPORT "$ l_cmd \ r " ;
  147. print "SEND: [$ l_cmd] \ n " if $ VERBOSE ;
  148. return at_rec ( $ l_rx ) ;
  149. }
  150. # This function expects a response from the modem specified in the regular expression
  151. # takes 1 parameter - a regular expression describing the options for expected answers (OK by default)
  152. sub at_rec {
  153. my $ l_rx = shift || qr / ok / ;
  154. my $ recive = '' ;
  155. while ( ! ( $ recive = ~ $ l_rx ) ) {
  156. $ recive = < $ SENDPORT >;
  157. $ recive = ~ s / [ \ n \ r ] + // msg ;
  158. print "RECIVE: [$ recive] \ n " if $ VERBOSE && $ recive ;
  159. }
  160. $ recive = ~ $ l_rx ;
  161. print "END RECIVE: [$ recive] [$ 1] [$ l_rx] \ n " if $ VERBOSE ;
  162. return $ 1 ;
  163. }
  164. # This function closes the previously open ports of the modem.
  165. sub exit_call {
  166. print "ANNOUNCEMENT IS COMPLETED \ n " ;
  167. close $ SENDPORT_WAV ;
  168. at_send ( 'AT + CHUP' ) ;
  169. close $ SENDPORT ;
  170. }


consider list.01.pl
# List of subscribers.
# This is an array of hash arrays in which each entry contains
# subscriber data:
# phone - subscriber's phone
# name - name of the subscriber
# It is also possible to store other subscriber data
(
{ phone => '+79111234567' , name => 'Petrov Petr Petrovich' } ,
{ phone => '+79117654321' , name => 'Vasilyev Vasily Vasilyevich' }
) ;


Consider test.voice.raw
To create this file, Audacity audio editor was used as shown in the pictures:

image

image

image

image

image

I also cite additional files cc_cause.pl and end_status.pl. They are not used in the presented version of the script, but in the case of refinement will be useful.

cc_cause.pl
# disconnect cause (cc)
# English http://www.eversoft.net/dcc.html
# in Russian http://ru.wikipedia.org/wiki/Q.931
# mana by huawei
# HUAWEI CDMA Datacard Modem AT Command Interface Specification
# "http://www.letswireless.com.cn/asp_bin/downfile/2009929121443234.pdf"
#
# HUAWEI UMTS Datacard Modem AT Command Interface Specification
# "http://www.net139.com/UploadFile/menu/HUAWEI%20UMTS%20Datacard%20Modem%20AT%20Command%20Interface%20Specification_V2.3.pdf"
(
'1' => 'UNASSIGNED_CAUSE' ,
'3' => 'NO_ROUTE_TO_DEST' ,
'6' => 'CHANNEL_UNACCEPTABLE' ,
'8' => 'OPERATOR_DETERMINED_BARRING' ,
'16' => 'NORMAL_CALL_CLEARING' ,
'17' => 'USER_BUSY' ,
'18' => 'NO_USER_RESPONDING' ,
'19' => 'USER_ALERTING_NO_ANSWER' ,
'21' => 'CALL_REJECTED' ,
'22' => 'NUMBER_CHANGED' ,
'26' => 'NON_SELECTED_USER_CLEARING' ,
'27' => 'DESTINATION_OUT_OF_ORDER' ,
'28' => 'INVALID_NUMBER_FORMAT' ,
'29' => 'FACILITY_REJECTED' ,
'30' => 'RESPONSE_TO_STATUS_ENQUIRY' ,
'31' => 'NORMAL_UNSPECIFIED' ,
'34' => 'NO_CIRCUIT_CHANNEL_AVAILABLE' ,
'38' => 'NETWORK_OUT_OF_ORDER' ,
'41' => 'TEMPORARY_FAILURE' ,
'42' => 'SWITCHING_EQUIPMENT_CONGESTION' ,
'43' => 'ACCESS_INFORMATION_DISCARDED' ,
'44' => 'REQUESTED_CIRCUIT_CHANNEL_NOT_AVAILABLE' ,
'47' => 'RESOURCES_UNAVAILABLE_UNSPECIFIED' ,
'49' => 'QUALITY_OF_SERVICE_UNAVAILABLE' ,
'50' => 'REQUESTED_FACILITY_NOT_SUBSCRIBED' ,
'55' => 'INCOMING_CALL_BARRED_WITHIN_CUG' ,
'57' => 'BEARER_CAPABILITY_NOT_AUTHORISED' ,
'58' => 'BEARER_CAPABILITY_NOT_PRESENTLY_AVAILABLE' ,
'63' => 'SERVICE_OR_OPTION_NOT_AVAILABLE' ,
'65' => 'BEARER_SERVICE_NOT_IMPLEMENTED' ,
'68' => 'ACM_GEQ_ACMMAX' ,
'69' => 'REQUESTED_FACILITY_NOT_IMPLEMENTED' ,
'70' => 'ONLY_RESTRICTED_DIGITAL_INFO_BC_AVAILABLE' ,
'79' => 'SERVICE_OR_OPTION_NOT_IMPLEMENTED' ,
'81' => 'INVALID_TRANSACTION_ID_VALUE' ,
'87' => 'USER_NOT_MEMBER_OF_CUG' ,
'88' => 'INCOMPATIBLE_DESTINATION' ,
'91' => 'INVALID_TRANSIT_NETWORK_SELECTION' ,
'95' => 'SEMANTICALLY_INCORRECT_MESSAGE' ,
'96' => 'INVALID_MANDATORY_INFORMATION' ,
'97' => 'MESSAGE_TYPE_NON_EXISTENT' ,
'98' => 'MESSAGE_TYPE_NOT_COMPATIBLE_WITH_PROT_STATE' ,
'99' => 'IE_NON_EXISTENT_OR_NOT_IMPLEMENTED' ,
'100' => 'CONDITIONAL_IE_ERROR' ,
'101' => 'MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE' ,
'102' => 'RECOVERY_ON_TIMER_EXPIRY' ,
'111' => 'PROTOCOL_ERROR_UNSPECIFIED' ,
'127' => 'INTERWORKING_UNSPECIFIED' ,
'160' => 'REJ_UNSPECIFIED' ,
'161' => 'AS_REJ_RR_REL_IND' ,
'162' => 'AS_REJ_RR_RANDOM_ACCESS_FAILURE' ,
'163' => 'AS_REJ_RRC_REL_IND' ,
'164' => 'AS_REJ_RRC_CLOSE_SESSION_IND' ,
'165' => 'AS_REJ_RRC_OPEN_SESSION_FAILURE' ,
'166' => 'AS_REJ_LOW_LEVEL_FAIL' ,
'167' => 'AS_REJ_LOW_LEVEL_FAIL_REDIAL_NOT_ALLOWD' ,
'168' => 'MM_REJ_INVALID_SIM' ,
'169' => 'MM_REJ_NO_SERVICE' ,
'170' => 'MM_REJ_TIMER_T3230_EXP' ,
'171' => 'MM_REJ_NO_CELL_AVAILABLE' ,
'172' => 'MM_REJ_WRONG_STATE' ,
'173' => 'MM_REJ_ACCESS_CLASS_BLOCKED' ,
'174' => 'ABORT_MSG_RECEIVED' ,
'175' => 'OTHER_CAUSE' ,
'176' => 'CNM_REJ_TIMER_T303_EXP' ,
'177' => 'CNM_REJ_NO_RESOURCES' ,
'178' => 'CNM_MM_REL_PENDING' ,
'179' => 'CNM_INVALID_USER_DATA'
) ;


end_status.pl
# codes Call ending cause codes
# mana by huawei
#
# HUAWEI CDMA Datacard Modem AT Command Interface Specification
# "http://www.letswireless.com.cn/asp_bin/downfile/2009929121443234.pdf"
#
# HUAWEI UMTS Datacard Modem AT Command Interface Specification
# "http://www.net139.com/UploadFile/menu/HUAWEI%20UMTS%20Datacard%20Modem%20AT%20Command%20Interface%20Specification_V2.3.pdf"
(
'0' => 'The board is offline.' ,
'21' => 'Board is out of service.' ,
'22' => 'Call is ended normally.' ,
'23' => 'Call is interrupted by BS.' ,
'24' => 'BS record is received during a call.' ,
'25' => 'BS releases a call.' ,
'26' => 'BS rejects the current SO service.' ,
'27' => 'There is an incoming BS call.' ,
'28' => 'received alert stop from BS.' ,
'29' => 'Call is ended normally.' ,
'30' => 'received end activation - OTASP call.' ,
'31' => 'MC ends call initiation or call.' ,
'34' => 'RUIM is not available.' ,
'99' => 'NDSS error.' ,
'100' => 'rxd' , cc_cause ' ,
'101' => 'After a MS initiates a call, the network fails to respond.' ,
'102' => 'MS rejects an incoming call.' ,
'103' => 'A call is rejected during the put-through process.' ,
'104' => 'The release is from the details, check' ,
'105' => 'The phone fee is used up.' ,
'106' => 'The MS is out of the service'
) ;


In conclusion.

This version of the voice alert script does not claim to be complete and correct implementation, but is only a demonstration, and for serious use it can and should be improved. It is necessary to add more serious processing of CEND states, implement the conditions of repeated dialing to subscribers, if it was not possible to notify the first time. You can also make a web interface that includes a task scheduler, a subscriber list editor, report generation, and more.

I hope that this article will be in demand and useful for you, and I will also try to continue to post interesting and useful articles.

Source: https://habr.com/ru/post/192930/


All Articles