]> git.ipfire.org Git - people/stevee/network.git/blame - functions.modem
modem: Wait a moment after initializing.
[people/stevee/network.git] / functions.modem
CommitLineData
6c74a64c
MT
1#!/bin/bash
2###############################################################################
3# #
4# IPFire.org - A linux based firewall #
5# Copyright (C) 2012 IPFire Network Development Team #
6# #
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. #
11# #
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. #
16# #
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/>. #
19# #
20###############################################################################
21
22# Exit codes from the chat(8) command:
23CHAT_OK=0
24CHAT_INVALID=1
25CHAT_ERROR=2
26CHAT_TIMEOUT=3
27
28function modem_chat() {
29 local answer="OK"
30 local device
31 local timeout=2
32 local quiet="false"
33
34 while [ $# -gt 0 ]; do
35 case "${1}" in
36 --timeout=*)
37 timeout=$(cli_get_val ${1})
38 ;;
39 --answer=*)
40 answer=$(cli_get_val ${1})
41 ;;
42 --quiet)
43 quiet="true"
44 ;;
45 *)
46 device=${1}
47 shift; break
48 ;;
49 esac
50 shift
51 done
52
53 assert serial_exists ${device}
54 assert serial_is_unlocked ${device}
55 assert isset answer
56 assert isset timeout
57
58 local command=$@
59
60 log DEBUG "Sending command to ${device}: ${command}"
61
62 (
63 # This cannot be run with -x.
64 set +x 2>/dev/null
65
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}
70
71 local ret=${PIPESTATUS[0]}
72
73 # Return the exit code of the chat command.
74 case "${ret}" in
75 ${CHAT_OK})
76 return ${EXIT_OK}
77 ;;
78
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})
82 return ${EXIT_ERROR}
83 ;;
84
85 ${CHAT_INVALID})
86 return ${EXIT_CONF_ERROR}
87 ;;
88 esac
89
90 log WARNING "Received unknown exit code from chat(8): ${ret}"
91 return ${EXIT_ERROR}
92}
93
94function __modem_chat_process_output() {
95 local answer=${1}
96 local quiet=${2}
97
98 if enabled quiet; then
99 # Just throw everything away.
100 cat >/dev/null
101 return ${EXIT_OK}
102 fi
103
104 local counter=0
105
106 local line
107 while read line; do
108 log DEBUG "Output[${counter}]: ${line}"
109
110 # Also skip empty lines.
111 [ -n "${line}" ] || continue
112
113 # Ignore all volatile messages.
114 [ "${line:0:1}" = "^" ] && continue
115
116 counter=$(( ${counter} + 1 ))
117
118 # Skip the first line, because that's out command.
119 [ ${counter} -eq 1 ] && continue
120
121 # Filter out the expected answer.
122 [ "${line}" = "${answer}" ] && continue
123
124 # Print the rest.
125 print "${line}"
126 done
127}
128
95416c1e
MT
129function modem_initialize() {
130 local device="${1}"
131 assert isset device
3255c894
MT
132 shift
133
134 # Sleep for $sleep seconds, to give
135 # the modem a moment to initialize itself.
136 local sleep=1
137
138 while [ $# -gt 0 ]; do
139 case "${1}" in
140 --sleep=*)
141 sleep="$(cli_get_val "${1}")"
142 ;;
143 *)
144 warning "Unrecognized argument: ${1}"
145 ;;
146 esac
147 shift
148 done
149 assert isinteger sleep
95416c1e
MT
150
151 log INFO "Initializing modem ${device}"
152
153 # Reset the modem.
154 modem_chat "${device}" "${AT_INITIALIZE}"
3255c894
MT
155
156 # Wait...
157 if [ ${sleep} -gt 0 ]; then
158 sleep ${sleep}
159 fi
95416c1e
MT
160}
161
6c74a64c
MT
162# Exit codes of the sim_status function.
163EXIT_SIM_READY=0
164EXIT_SIM_PIN=1
165EXIT_SIM_PUK=2
166EXIT_SIM_UNKNOWN=3
167
168function modem_sim_status() {
169 local device=${1}
170 assert isset device
171
172 local output
173 output=$(modem_chat ${device} "AT+CPIN?")
174 assert_check_retval $?
175
176 # Strip leading +CPIN: from the output.
177 output=${output#*: }
178
179 case "${output}" in
180 "READY")
181 log DEBUG "${device}'s SIM is unlocked or doesn't need a PIN."
182 return ${EXIT_SIM_READY}
183 ;;
184 "SIM PIN")
185 log DEBUG "${device}'s SIM is waiting for a PIN."
186 return ${EXIT_SIM_PIN}
187 ;;
188 "SIM PUK")
189 log DEBUG "${device}'s SIM is PUK locked."
190 return ${EXIT_SIM_PUK}
191 ;;
192 esac
193
194 log WARNING "${device}: Invalid output of the AT+CPIN? command."
195 return ${EXIT_SIM_UNKNOWN}
196}
197
198function modem_sim_unlocked() {
199 local device=${1}
200 assert isset device
201
202 modem_sim_status "${device}"
203 local ret=$?
204
205 [ ${ret} -eq ${EXIT_SIM_READY} ] && return ${EXIT_TRUE} || return ${EXIT_FALSE}
206}
207
208function modem_sim_locked() {
209 modem_sim_unlocked $@ && return ${EXIT_FALSE} || return ${EXIT_TRUE}
210}
211
212function modem_sim_unlock() {
213 local device=${1}
214 assert isset device
215
216 local pin=${2}
217 assert isset pin
218
219 local command="AT+CPIN=${pin}"
220
221 local new_pin=${3}
222 if isset new_pin; then
223 command="${command},${new_pin}"
224 fi
225
226 modem_chat --timeout=2 --quiet "${device}" "${command}"
227
228 local ret=$?
229 case "${ret}" in
230 ${EXIT_OK})
231 log INFO "Successfully unlocked SIM card on ${device}."
232 ;;
233 *)
234 log ERROR "Could not unlock SIM card on ${device}."
235 ret=${EXIT_ERROR}
236 ;;
237 esac
238
239 return ${ret}
240}
241
95416c1e
MT
242function modem_sim_auto_unlock() {
243 local device="${1}"
244 assert isset device
245
246 local pin="${2}"
247 assert isset pin
248
249 # Get the current state the SIM card is in.
250 modem_sim_status "${device}" &>/dev/null
251 local sim_status_code=$?
252
253 case "${sim_status_code}" in
254 ${EXIT_SIM_READY})
255 # Everything's fine. The SIM card is
256 # already unlocked.
257 return ${EXIT_OK}
258 ;;
259 ${EXIT_SIM_PIN})
260 # Try to unlock the device.
261 if modem_sim_unlock ${device} ${pin}; then
262 return ${EXIT_OK}
263 else
264 return ${EXIT_ERROR}
265 fi
266 ;;
267 ${EXIT_SIM_PUK})
268 log ERROR "SIM card is PUK locked. Please unlock manually."
269 return ${EXIT_ERROR}
270 ;;
271 esac
272
273 return ${EXIT_ERROR}
274}
275
6c74a64c
MT
276# Returns the vendor of the modem.
277# For example: "huawei"
278function modem_get_manufacturer() {
279 local device=${1}
280 assert isset device
281
282 local output
283 output=$(modem_chat ${device} "AT+GMI")
284 assert_check_retval $?
285
286 [ "${output:0:1}" = "+" ] && output=${output#*: }
287 output=${output//\"/}
288
289 print "${output}"
290}
291
292function modem_get_model() {
293 local device=${1}
294 assert isset device
295
296 local output
297 output=$(modem_chat ${device} "AT+GMM")
298 assert_check_retval $?
299
300 [ "${output:0:1}" = "+" ] && output=${output#*: }
301 output=${output//\"/}
302
303 print "${output}"
304}
305
306function modem_get_software_version() {
307 local device=${1}
308 assert isset device
309
310 local output
311 output=$(modem_chat ${device} "AT+GMR")
312 assert_check_retval $?
313
314 [ "${output:0:1}" = "+" ] && output=${output#*: }
315 output=${output//\"/}
316
317 print "${output}"
318}
319
320function modem_get_sim_imsi() {
321 local device=${1}
322 assert isset device
323
324 # Make sure the SIM card is unlocked for this operation.
325 assert modem_sim_unlocked ${device}
326
327 modem_chat ${device} "AT+CIMI"
328}
329
330function modem_get_device_imei() {
331 local device=${1}
332 assert isset device
333
334 local output
335 output=$(modem_chat --timeout=1 ${device} "AT+CGSN") || assert_check_retval $?
336 local ret=$?
337
338 if [ ${ret} -eq ${EXIT_OK} ]; then
339 print "${output}"
340 fi
341
342 return ${ret}
343}
344
345function modem_is_mobile() {
346 local device=${1}
347 assert isset device
348
349 # Check if the device can return it's IMEI.
350 # If not, it's probably a serial 56k modem or something
351 # in that category.
352
353 modem_get_device_imei ${device} &>/dev/null
354}
355
356# Exit codes of the network registration function.
357EXIT_REG_REGISTERED_TO_HOME_NETWORK=0
358EXIT_REG_NOT_REGISTERED_NOT_SEARCHING=1
359EXIT_REG_NOT_REGISTERED_SEARCHING=2
360EXIT_REG_REGISTRATION_DENIED=3
361EXIT_REG_REGISTERED_ROAMING=4
362EXIT_REG_UNKNOWN=5
363
364function modem_get_network_registration() {
365 local device=${1}
366 assert isset device
367
368 # Make sure the SIM card is unlocked for this operation.
369 assert modem_sim_unlocked ${device}
370
371 local output
372 output=$(modem_chat ${device} "AT+CREG?")
373 assert_check_retval $?
374
375 # Cut out unneeded parts of the message.
376 output=${output#*: }
377
378 case "${output}" in
379 [0-2],[0-5])
380 local status=${output%,*}
381
382 # The status variable must be zero.
383 [ ${status} -ne 0 ] && break
384
385 local stat=${output#*,}
386 case "${stat}" in
387 0)
388 return ${EXIT_REG_NOT_REGISTERED_NOT_SEARCHING}
389 ;;
390 1)
391 return ${EXIT_REG_REGISTERED_TO_HOME_NETWORK}
392 ;;
393 2)
394 return ${EXIT_REG_NOT_REGISTERED_SEARCHING}
395 ;;
396 3)
397 return ${EXIT_REG_REGISTRATION_DENIED}
398 ;;
399 5)
400 return ${EXIT_REG_REGISTERED_ROAMING}
401 ;;
402 *)
403 return ${EXIT_REG_UNKNOWN}
404 ;;
405 esac
406 ;;
407 esac
408
409 # Apparently the output of the CREG? command was not in
410 # the right format. The modem will be tried to be set to the
411 # right mode.
412
413 modem_set_network_registration ${device} 0
414 modem_get_network_registration ${device}
415}
416
417function modem_set_network_registration() {
418 local device=${1}
419 assert isset device
420
421 # Make sure the SIM card is unlocked for this operation.
422 assert modem_sim_unlocked ${device}
423
424 local mode=${2}
425 assert isset mode
426
427 modem_chat ${device} "AT+CREG=${mode}"
428}
429
430function modem_scan_networks() {
431 local device=${1}
432 assert isset device
433
434 # Make sure the SIM card is unlocked for this operation.
435 assert modem_sim_unlocked ${device}
436
437 local output
438 output=$(modem_chat --timeout=60 ${device} "AT+COPS=?")
439 assert_check_retval $?
440
441 output=${output#*: }
442
443 # XXX the output is not very nice to parse.
444}
445
446function __modem_get_network_operator() {
447 local device=${1}
448 assert isset device
449
450 # Make sure the SIM card is unlocked for this operation.
451 assert modem_sim_unlocked ${device}
452
453 local argument=${2}
454 assert isset argument
455
456 local output
457 output=$(modem_chat ${device} "AT+COPS?")
458 assert_check_retval $?
459
460 output=${output#*: }
c5feadb0
MT
461 output=${output//,/ }
462
463 local arg mode format operator act
464 local i=0
465 while read -r arg; do
466 case "${i}" in
467 0)
468 mode="${arg}"
469 ;;
470 1)
471 format="${arg}"
472 ;;
473 2)
474 operator="$(strip ${arg})"
475 ;;
476 3)
477 act="${arg}"
478 ;;
479 *)
480 break
481 ;;
482 esac
483 i="$(( ${i} + 1 ))"
484 done <<< "$(args ${output})"
6c74a64c
MT
485
486 print "${!argument}"
487 return ${EXIT_OK}
488}
489
490function modem_get_network_operator() {
491 local device=${1}
492 assert isset device
493
494 __modem_get_network_operator ${device} operator
495}
496
497# Exit codes of the network operator mode function.
498EXIT_OPMODE_GSM=0
499EXIT_OPMODE_COMPACTGSM=1
500EXIT_OPMODE_GSM_WITH_EGPRS=2
501EXIT_OPMODE_UMTS=3
502EXIT_OPMODE_UMTS_WITH_HSDPA=4
503EXIT_OPMODE_UMTS_WITH_HSUPA=5
504EXIT_OPMODE_UMTS_WITH_HSDPA_AND_HSUPA=6
505EXIT_OPMODE_UNKNOWN=7
506
507function modem_get_network_mode() {
508 local device=${1}
509 assert isset device
510
511 local output
512 output=$(__modem_get_network_operator ${device} act)
513 assert_check_retval $?
514
515 case "${output}" in
516 0)
517 print "GSM"
518 return ${EXIT_OPMODE_GSM}
519 ;;
520 1)
521 print "Compact GSM"
522 return ${EXIT_OPMODE_COMPACTGSM}
523 ;;
524 2)
525 print "UMTS"
526 return ${EXIT_OPMODE_UMTS}
527 ;;
528 3)
529 print "UMTS with HSDPA"
530 return ${EXIT_OPMODE_UMTS_WITH_HSDPA}
531 ;;
532 4)
533 print "UMTS with HSUPA"
534 return ${EXIT_OPMODE_UMTS_WITH_HSUPA}
535 ;;
536 5)
537 print "UMTS with HSDPA and HSUPA"
538 return ${EXIT_OPMODE_UMTS_WITH_HSDPA_AND_HSUPA}
539 ;;
540 *)
541 print "Unknown"
542 return ${EXIT_OPMODE_UNKNOWN}
543 ;;
544 esac
545}
546
547function __modem_get_signal_quality() {
548 local device=${1}
549 assert isset device
550
551 # Make sure the SIM card is unlocked for this operation.
552 assert modem_sim_unlocked ${device}
553
554 local argument=${2}
555 assert isset argument
556
557 local output
558 output=$(modem_chat ${device} "AT+CSQ")
559 assert_check_retval $?
560
561 output=${output#*: }
562
563 case "${output}" in
564 *,*)
565 local rssi=${output%,*}
566 local ber=${output#*,}
567
568 print "${!argument}"
569 return ${EXIT_OK}
570 ;;
571 *)
572 log ERROR "Unknown format for AT+CSQ: ${device}: ${output}"
573 ;;
574 esac
575
576 return ${EXIT_ERROR}
577}
578
579function modem_get_signal_quality() {
580 local device=${1}
581 assert isset device
582
583 local rssi
584 rssi=$(__modem_get_signal_quality ${device} rssi)
585 assert_check_retval $?
586
587 isset rssi || return ${EXIT_ERROR}
588
589 # 99 indicates an unknown signal strength.
590 [ ${rssi} -eq 99 ] && return ${EXIT_UNKNOWN}
591
592 local dbm=$(( ${rssi} * 2 ))
593 dbm=$(( ${dbm} - 113 ))
594
595 print "%d" "${dbm}"
596 return ${EXIT_OK}
597}
598
599function modem_get_bit_error_rate() {
600 local device=${1}
601 assert isset device
602
603 local ber
604 ber=$(__modem_get_signal_quality ${device} ber)
605 assert_check_retval $?
606
607 isset ber || return ${EXIT_ERROR}
608
609 # 99 indicates that the bit error rate could not be detected or
610 # is unknown for some other reason.
611 [ ${ber} -eq 99 ] && return ${EXIT_UNKNOWN}
612
613 print "%d" "${ber}"
614 return ${EXIT_OK}
615}