2 ###############################################################################
4 # IPFire.org - A linux based firewall #
5 # Copyright (C) 2012 IPFire Network Development Team #
7 # This program is free software: you can redistribute it and/or modify #
8 # it under the terms of the GNU General Public License as published by #
9 # the Free Software Foundation, either version 3 of the License, or #
10 # (at your option) any later version. #
12 # This program is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 # GNU General Public License for more details. #
17 # You should have received a copy of the GNU General Public License #
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
20 ###############################################################################
22 # Exit codes from the chat(8) command:
34 while [ $# -gt 0 ]; do
37 timeout
=$
(cli_get_val
${1})
40 answer
=$
(cli_get_val
${1})
53 assert serial_exists
${device}
54 assert serial_is_unlocked
${device}
60 log DEBUG
"Sending command to ${device}: ${command}"
63 # This cannot be run with -x.
66 chat
-V -s -t ${timeout} "" "${command}" "${answer}" \
67 < ${device} > ${device} || exit $?
68 print # Print line feed.
69 ) 2>&1 | __modem_chat_process_output "${answer}" ${quiet}
71 local ret=${PIPESTATUS[0]}
73 # Return the exit code of the chat command.
79 # When the timeout condition hit, the expected string has not
80 # been received in the given amount of time.
81 ${CHAT_TIMEOUT}|${CHAT_ERROR})
86 return ${EXIT_CONF_ERROR}
90 log WARNING "Received unknown
exit code from chat
(8): ${ret}"
94 __modem_chat_process_output() {
98 if enabled quiet; then
99 # Just throw everything away.
107 while read -r line; do
108 # Also skip empty lines.
109 [ -n "${line}" ] || continue
111 # Ignore all volatile messages.
112 [ "${line:0:1}" = "^
" ] && continue
114 log DEBUG "Output
[${counter}]: ${line}"
115 counter=$(( ${counter} + 1 ))
117 # Skip the first line, because that's out command.
118 [ ${counter} -eq 1 ] && continue
120 # Filter out the expected answer.
121 [ "${line}" = "${answer}" ] && continue
133 # Sleep for $sleep seconds, to give
134 # the modem a moment to initialize itself.
137 while [ $# -gt 0 ]; do
140 sleep="$
(cli_get_val
"${1}")"
143 warning "Unrecognized argument
: ${1}"
148 assert isinteger sleep
150 log INFO "Initializing modem
${device}"
153 modem_chat "${device}" "${AT_INITIALIZE}"
156 if [ ${sleep} -gt 0 ]; then
161 # Exit codes of the sim_status function.
172 output=$(modem_chat ${device} "AT
+CPIN?
")
173 assert_check_retval $?
175 # Strip leading +CPIN: from the output.
180 log DEBUG "${device}'s SIM is unlocked or doesn't need a PIN.
"
181 return ${EXIT_SIM_READY}
184 log DEBUG "${device}'s SIM is waiting for a PIN."
185 return ${EXIT_SIM_PIN}
188 log DEBUG "${device}'s SIM is PUK locked.
"
189 return ${EXIT_SIM_PUK}
193 log WARNING "${device}: Invalid output of the AT
+CPIN?
command.
"
194 return ${EXIT_SIM_UNKNOWN}
197 modem_sim_unlocked() {
201 modem_sim_status "${device}"
204 [ ${ret} -eq ${EXIT_SIM_READY} ] && return ${EXIT_TRUE} || return ${EXIT_FALSE}
208 modem_sim_unlocked $@ && return ${EXIT_FALSE} || return ${EXIT_TRUE}
218 local command="AT
+CPIN
=${pin}"
221 if isset new_pin; then
222 command="${command},${new_pin}"
225 modem_chat --timeout=2 --quiet "${device}" "${command}"
230 log INFO "Successfully unlocked SIM card on
${device}.
"
233 log ERROR "Could not unlock SIM card on
${device}.
"
241 modem_sim_auto_unlock() {
248 # Get the current state the SIM card is in.
249 modem_sim_status "${device}" &>/dev/null
250 local sim_status_code=$?
252 case "${sim_status_code}" in
254 # Everything's fine. The SIM card is
259 # Try to unlock the device.
260 if modem_sim_unlock ${device} ${pin}; then
267 log ERROR "SIM card is PUK locked. Please unlock manually.
"
275 # Returns the vendor of the modem.
276 # For example: "huawei
"
277 modem_get_manufacturer() {
282 output=$(modem_chat ${device} "AT
+GMI
")
283 assert_check_retval $?
285 [ "${output:0:1}" = "+" ] && output=${output#*: }
286 output=${output//\"/}
296 output=$(modem_chat ${device} "AT
+GMM
")
297 assert_check_retval $?
299 [ "${output:0:1}" = "+" ] && output=${output#*: }
300 output=${output//\"/}
305 modem_get_software_version() {
310 output=$(modem_chat ${device} "AT
+GMR
")
311 assert_check_retval $?
313 [ "${output:0:1}" = "+" ] && output=${output#*: }
314 output=${output//\"/}
319 modem_get_sim_imsi() {
323 # Make sure the SIM card is unlocked for this operation.
324 assert modem_sim_unlocked ${device}
326 modem_chat ${device} "AT
+CIMI
"
329 modem_get_device_imei() {
334 output=$(modem_chat --timeout=1 ${device} "AT
+CGSN
") || assert_check_retval $?
337 if [ ${ret} -eq ${EXIT_OK} ]; then
348 # Check if the device can return it's IMEI.
349 # If not, it's probably a serial 56k modem or something
352 modem_get_device_imei ${device} &>/dev/null
355 # Exit codes of the network registration function.
356 EXIT_REG_REGISTERED_TO_HOME_NETWORK=0
357 EXIT_REG_NOT_REGISTERED_NOT_SEARCHING=1
358 EXIT_REG_NOT_REGISTERED_SEARCHING=2
359 EXIT_REG_REGISTRATION_DENIED=3
360 EXIT_REG_REGISTERED_ROAMING=4
363 modem_get_network_registration() {
367 # Make sure the SIM card is unlocked for this operation.
368 assert modem_sim_unlocked ${device}
371 output=$(modem_chat ${device} "AT
+CREG?
")
372 assert_check_retval $?
374 # Cut out unneeded parts of the message.
379 local status=${output%,*}
381 # The status variable must be zero.
382 [ ${status} -ne 0 ] && break
384 local stat=${output#*,}
387 return ${EXIT_REG_NOT_REGISTERED_NOT_SEARCHING}
390 return ${EXIT_REG_REGISTERED_TO_HOME_NETWORK}
393 return ${EXIT_REG_NOT_REGISTERED_SEARCHING}
396 return ${EXIT_REG_REGISTRATION_DENIED}
399 return ${EXIT_REG_REGISTERED_ROAMING}
402 return ${EXIT_REG_UNKNOWN}
408 # Apparently the output of the CREG? command was not in
409 # the right format. The modem will be tried to be set to the
412 modem_set_network_registration ${device} 0
413 modem_get_network_registration ${device}
416 modem_set_network_registration() {
420 # Make sure the SIM card is unlocked for this operation.
421 assert modem_sim_unlocked ${device}
426 modem_chat ${device} "AT
+CREG
=${mode}"
429 modem_scan_networks() {
433 # Make sure the SIM card is unlocked for this operation.
434 assert modem_sim_unlocked ${device}
437 output=$(modem_chat --timeout=60 ${device} "AT
+COPS
=?
")
438 assert_check_retval $?
442 # XXX the output is not very nice to parse.
445 __modem_get_network_operator() {
449 # Make sure the SIM card is unlocked for this operation.
450 assert modem_sim_unlocked ${device}
453 assert isset argument
456 output=$(modem_chat ${device} "AT
+COPS?
")
457 assert_check_retval $?
460 output=${output//,/ }
462 local arg mode format operator act
464 while read -r arg; do
473 operator="$
(strip
${arg})"
483 done <<< "$
(args
${output})"
489 modem_get_network_operator() {
493 __modem_get_network_operator ${device} operator
496 # Exit codes of the network operator mode function.
498 EXIT_OPMODE_COMPACTGSM=1
499 EXIT_OPMODE_GSM_WITH_EGPRS=2
501 EXIT_OPMODE_UMTS_WITH_HSDPA=4
502 EXIT_OPMODE_UMTS_WITH_HSUPA=5
503 EXIT_OPMODE_UMTS_WITH_HSDPA_AND_HSUPA=6
504 EXIT_OPMODE_UNKNOWN=7
506 modem_get_network_mode() {
511 output=$(__modem_get_network_operator ${device} act)
512 assert_check_retval $?
517 return ${EXIT_OPMODE_GSM}
521 return ${EXIT_OPMODE_COMPACTGSM}
525 return ${EXIT_OPMODE_UMTS}
528 print "UMTS with HSDPA
"
529 return ${EXIT_OPMODE_UMTS_WITH_HSDPA}
532 print "UMTS with HSUPA
"
533 return ${EXIT_OPMODE_UMTS_WITH_HSUPA}
536 print "UMTS with HSDPA and HSUPA
"
537 return ${EXIT_OPMODE_UMTS_WITH_HSDPA_AND_HSUPA}
541 return ${EXIT_OPMODE_UNKNOWN}
546 __modem_get_signal_quality() {
550 # Make sure the SIM card is unlocked for this operation.
551 assert modem_sim_unlocked ${device}
554 assert isset argument
557 output=$(modem_chat ${device} "AT
+CSQ
")
558 assert_check_retval $?
564 local rssi=${output%,*}
565 local ber=${output#*,}
571 log ERROR "Unknown format
for AT
+CSQ
: ${device}: ${output}"
578 modem_get_signal_quality() {
583 rssi=$(__modem_get_signal_quality ${device} rssi)
584 assert_check_retval $?
586 isset rssi || return ${EXIT_ERROR}
588 # 99 indicates an unknown signal strength.
589 [ ${rssi} -eq 99 ] && return ${EXIT_UNKNOWN}
591 local dbm=$(( ${rssi} * 2 ))
592 dbm=$(( ${dbm} - 113 ))
598 modem_get_bit_error_rate() {
603 ber=$(__modem_get_signal_quality ${device} ber)
604 assert_check_retval $?
606 isset ber || return ${EXIT_ERROR}
608 # 99 indicates that the bit error rate could not be detected or
609 # is unknown for some other reason.
610 [ ${ber} -eq 99 ] && return ${EXIT_UNKNOWN}
618 modem_ussd_send_command() {
626 local cleartext="false
"
629 while [ $# -gt 0 ]; do
635 timeout="$
(cli_get_val
"${1}")"
638 warning "Unrecognized argument
: ${1}"
644 local encoded_command="${command}"
645 if ! enabled cleartext; then
646 encoded_command="$
(modem_ussd_encode
"${command}")"
649 log INFO "Sending USSD
command '${command}' on
${device}"
651 local at_command="AT
+CUSD
=1,\"${encoded_command}\",${encoding}"
653 # Send the AT command and parse the output.
654 modem_chat --answer="nothing
" --timeout="${timeout}" \
655 "${device}" "${at_command}" | __modem_ussd_parse_output
657 local ret=${PIPESTATUS[1]}
661 __modem_ussd_parse_output() {
663 while read -r line; do
664 # Find the expected answer.
665 [ "${line:0:7}" = "+CUSD
: " ] || continue
672 local response_encoding
677 while read -r arg; do
680 response_type="${arg}"
686 response_encoding="${arg}"
692 section=$(( ${section} + 1 ))
693 done <<< "$
(args
${line})"
695 log DEBUG "USSD response
type: ${response_type}"
696 log DEBUG "USSD response encoding
: ${response_encoding}"
697 log DEBUG "USSD encoded response
: ${response}"
699 # If we got anything else than a response (type 0),
700 # we don't know how to handle that.
701 if [ "${response_type}" -ne "0" ]; then
705 # Decode the string if needed.
706 case "${response_encoding}" in
708 response="$
(modem_ussd_decode
"${response}")"
711 log DEBUG "USSD response
: ${response}"
720 modem_ussd_encode() {
724 local output buffer char
725 while read -r char; do
726 char="$
(char2bin
"${char}")"
727 char="$
(string_reverse
"${char:1:7}")"
729 buffer="${buffer}${char}"
730 done <<< "$
(string_split
"${string}")"
733 while [ ${pos} -lt ${#buffer} ]; do
734 char="$
(string_reverse
"${buffer:${pos}:${len}}")"
735 pos=$(( ${pos} + ${len} ))
737 char="$
(bin2hex
"${char}")"
738 output="${output}${char}"
741 # Make everything uppercase.
747 modem_ussd_decode() {
751 local buffer1 buffer2
755 while [ ${pos} -lt ${#string} ]; do
756 char="${string:${pos}:${len}}"
757 pos=$(( ${pos} + ${len} ))
759 char="$
(hex2bin
"${char}")"
760 char="$
(string_reverse
"${char}")"
761 buffer1="${buffer1}${char}"
768 while [ ${pos} -lt ${#buffer1} ]; do
769 char="${buffer1:${pos}:${len}}"
770 pos=$(( ${pos} + ${len} ))
772 buffer2="${buffer2}0${char}"
774 buffer2="${buffer2:1}"
776 # Reset pointers again.
780 while [ ${pos} -lt ${#buffer2} ]; do
781 char="${buffer2:${pos}:${len}}"
782 pos=$(( ${pos} + ${len} ))
784 char="$
(string_reverse
"${char}")"
785 char="$
(bin2char
"${char}")"
786 output="${output}${char}"