]> git.ipfire.org Git - people/stevee/network.git/blob - functions.modem
modem: Fix getting provider information.
[people/stevee/network.git] / functions.modem
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:
23 CHAT_OK=0
24 CHAT_INVALID=1
25 CHAT_ERROR=2
26 CHAT_TIMEOUT=3
27
28 function 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
94 function __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
129 # Exit codes of the sim_status function.
130 EXIT_SIM_READY=0
131 EXIT_SIM_PIN=1
132 EXIT_SIM_PUK=2
133 EXIT_SIM_UNKNOWN=3
134
135 function modem_sim_status() {
136 local device=${1}
137 assert isset device
138
139 local output
140 output=$(modem_chat ${device} "AT+CPIN?")
141 assert_check_retval $?
142
143 # Strip leading +CPIN: from the output.
144 output=${output#*: }
145
146 case "${output}" in
147 "READY")
148 log DEBUG "${device}'s SIM is unlocked or doesn't need a PIN."
149 return ${EXIT_SIM_READY}
150 ;;
151 "SIM PIN")
152 log DEBUG "${device}'s SIM is waiting for a PIN."
153 return ${EXIT_SIM_PIN}
154 ;;
155 "SIM PUK")
156 log DEBUG "${device}'s SIM is PUK locked."
157 return ${EXIT_SIM_PUK}
158 ;;
159 esac
160
161 log WARNING "${device}: Invalid output of the AT+CPIN? command."
162 return ${EXIT_SIM_UNKNOWN}
163 }
164
165 function modem_sim_unlocked() {
166 local device=${1}
167 assert isset device
168
169 modem_sim_status "${device}"
170 local ret=$?
171
172 [ ${ret} -eq ${EXIT_SIM_READY} ] && return ${EXIT_TRUE} || return ${EXIT_FALSE}
173 }
174
175 function modem_sim_locked() {
176 modem_sim_unlocked $@ && return ${EXIT_FALSE} || return ${EXIT_TRUE}
177 }
178
179 function modem_sim_unlock() {
180 local device=${1}
181 assert isset device
182
183 local pin=${2}
184 assert isset pin
185
186 local command="AT+CPIN=${pin}"
187
188 local new_pin=${3}
189 if isset new_pin; then
190 command="${command},${new_pin}"
191 fi
192
193 modem_chat --timeout=2 --quiet "${device}" "${command}"
194
195 local ret=$?
196 case "${ret}" in
197 ${EXIT_OK})
198 log INFO "Successfully unlocked SIM card on ${device}."
199 ;;
200 *)
201 log ERROR "Could not unlock SIM card on ${device}."
202 ret=${EXIT_ERROR}
203 ;;
204 esac
205
206 return ${ret}
207 }
208
209 # Returns the vendor of the modem.
210 # For example: "huawei"
211 function modem_get_manufacturer() {
212 local device=${1}
213 assert isset device
214
215 local output
216 output=$(modem_chat ${device} "AT+GMI")
217 assert_check_retval $?
218
219 [ "${output:0:1}" = "+" ] && output=${output#*: }
220 output=${output//\"/}
221
222 print "${output}"
223 }
224
225 function modem_get_model() {
226 local device=${1}
227 assert isset device
228
229 local output
230 output=$(modem_chat ${device} "AT+GMM")
231 assert_check_retval $?
232
233 [ "${output:0:1}" = "+" ] && output=${output#*: }
234 output=${output//\"/}
235
236 print "${output}"
237 }
238
239 function modem_get_software_version() {
240 local device=${1}
241 assert isset device
242
243 local output
244 output=$(modem_chat ${device} "AT+GMR")
245 assert_check_retval $?
246
247 [ "${output:0:1}" = "+" ] && output=${output#*: }
248 output=${output//\"/}
249
250 print "${output}"
251 }
252
253 function modem_get_sim_imsi() {
254 local device=${1}
255 assert isset device
256
257 # Make sure the SIM card is unlocked for this operation.
258 assert modem_sim_unlocked ${device}
259
260 modem_chat ${device} "AT+CIMI"
261 }
262
263 function modem_get_device_imei() {
264 local device=${1}
265 assert isset device
266
267 local output
268 output=$(modem_chat --timeout=1 ${device} "AT+CGSN") || assert_check_retval $?
269 local ret=$?
270
271 if [ ${ret} -eq ${EXIT_OK} ]; then
272 print "${output}"
273 fi
274
275 return ${ret}
276 }
277
278 function modem_is_mobile() {
279 local device=${1}
280 assert isset device
281
282 # Check if the device can return it's IMEI.
283 # If not, it's probably a serial 56k modem or something
284 # in that category.
285
286 modem_get_device_imei ${device} &>/dev/null
287 }
288
289 # Exit codes of the network registration function.
290 EXIT_REG_REGISTERED_TO_HOME_NETWORK=0
291 EXIT_REG_NOT_REGISTERED_NOT_SEARCHING=1
292 EXIT_REG_NOT_REGISTERED_SEARCHING=2
293 EXIT_REG_REGISTRATION_DENIED=3
294 EXIT_REG_REGISTERED_ROAMING=4
295 EXIT_REG_UNKNOWN=5
296
297 function modem_get_network_registration() {
298 local device=${1}
299 assert isset device
300
301 # Make sure the SIM card is unlocked for this operation.
302 assert modem_sim_unlocked ${device}
303
304 local output
305 output=$(modem_chat ${device} "AT+CREG?")
306 assert_check_retval $?
307
308 # Cut out unneeded parts of the message.
309 output=${output#*: }
310
311 case "${output}" in
312 [0-2],[0-5])
313 local status=${output%,*}
314
315 # The status variable must be zero.
316 [ ${status} -ne 0 ] && break
317
318 local stat=${output#*,}
319 case "${stat}" in
320 0)
321 return ${EXIT_REG_NOT_REGISTERED_NOT_SEARCHING}
322 ;;
323 1)
324 return ${EXIT_REG_REGISTERED_TO_HOME_NETWORK}
325 ;;
326 2)
327 return ${EXIT_REG_NOT_REGISTERED_SEARCHING}
328 ;;
329 3)
330 return ${EXIT_REG_REGISTRATION_DENIED}
331 ;;
332 5)
333 return ${EXIT_REG_REGISTERED_ROAMING}
334 ;;
335 *)
336 return ${EXIT_REG_UNKNOWN}
337 ;;
338 esac
339 ;;
340 esac
341
342 # Apparently the output of the CREG? command was not in
343 # the right format. The modem will be tried to be set to the
344 # right mode.
345
346 modem_set_network_registration ${device} 0
347 modem_get_network_registration ${device}
348 }
349
350 function modem_set_network_registration() {
351 local device=${1}
352 assert isset device
353
354 # Make sure the SIM card is unlocked for this operation.
355 assert modem_sim_unlocked ${device}
356
357 local mode=${2}
358 assert isset mode
359
360 modem_chat ${device} "AT+CREG=${mode}"
361 }
362
363 function modem_scan_networks() {
364 local device=${1}
365 assert isset device
366
367 # Make sure the SIM card is unlocked for this operation.
368 assert modem_sim_unlocked ${device}
369
370 local output
371 output=$(modem_chat --timeout=60 ${device} "AT+COPS=?")
372 assert_check_retval $?
373
374 output=${output#*: }
375
376 # XXX the output is not very nice to parse.
377 }
378
379 function __modem_get_network_operator() {
380 local device=${1}
381 assert isset device
382
383 # Make sure the SIM card is unlocked for this operation.
384 assert modem_sim_unlocked ${device}
385
386 local argument=${2}
387 assert isset argument
388
389 local output
390 output=$(modem_chat ${device} "AT+COPS?")
391 assert_check_retval $?
392
393 output=${output#*: }
394 output=${output//,/ }
395
396 local arg mode format operator act
397 local i=0
398 while read -r arg; do
399 case "${i}" in
400 0)
401 mode="${arg}"
402 ;;
403 1)
404 format="${arg}"
405 ;;
406 2)
407 operator="$(strip ${arg})"
408 ;;
409 3)
410 act="${arg}"
411 ;;
412 *)
413 break
414 ;;
415 esac
416 i="$(( ${i} + 1 ))"
417 done <<< "$(args ${output})"
418
419 print "${!argument}"
420 return ${EXIT_OK}
421 }
422
423 function modem_get_network_operator() {
424 local device=${1}
425 assert isset device
426
427 __modem_get_network_operator ${device} operator
428 }
429
430 # Exit codes of the network operator mode function.
431 EXIT_OPMODE_GSM=0
432 EXIT_OPMODE_COMPACTGSM=1
433 EXIT_OPMODE_GSM_WITH_EGPRS=2
434 EXIT_OPMODE_UMTS=3
435 EXIT_OPMODE_UMTS_WITH_HSDPA=4
436 EXIT_OPMODE_UMTS_WITH_HSUPA=5
437 EXIT_OPMODE_UMTS_WITH_HSDPA_AND_HSUPA=6
438 EXIT_OPMODE_UNKNOWN=7
439
440 function modem_get_network_mode() {
441 local device=${1}
442 assert isset device
443
444 local output
445 output=$(__modem_get_network_operator ${device} act)
446 assert_check_retval $?
447
448 case "${output}" in
449 0)
450 print "GSM"
451 return ${EXIT_OPMODE_GSM}
452 ;;
453 1)
454 print "Compact GSM"
455 return ${EXIT_OPMODE_COMPACTGSM}
456 ;;
457 2)
458 print "UMTS"
459 return ${EXIT_OPMODE_UMTS}
460 ;;
461 3)
462 print "UMTS with HSDPA"
463 return ${EXIT_OPMODE_UMTS_WITH_HSDPA}
464 ;;
465 4)
466 print "UMTS with HSUPA"
467 return ${EXIT_OPMODE_UMTS_WITH_HSUPA}
468 ;;
469 5)
470 print "UMTS with HSDPA and HSUPA"
471 return ${EXIT_OPMODE_UMTS_WITH_HSDPA_AND_HSUPA}
472 ;;
473 *)
474 print "Unknown"
475 return ${EXIT_OPMODE_UNKNOWN}
476 ;;
477 esac
478 }
479
480 function __modem_get_signal_quality() {
481 local device=${1}
482 assert isset device
483
484 # Make sure the SIM card is unlocked for this operation.
485 assert modem_sim_unlocked ${device}
486
487 local argument=${2}
488 assert isset argument
489
490 local output
491 output=$(modem_chat ${device} "AT+CSQ")
492 assert_check_retval $?
493
494 output=${output#*: }
495
496 case "${output}" in
497 *,*)
498 local rssi=${output%,*}
499 local ber=${output#*,}
500
501 print "${!argument}"
502 return ${EXIT_OK}
503 ;;
504 *)
505 log ERROR "Unknown format for AT+CSQ: ${device}: ${output}"
506 ;;
507 esac
508
509 return ${EXIT_ERROR}
510 }
511
512 function modem_get_signal_quality() {
513 local device=${1}
514 assert isset device
515
516 local rssi
517 rssi=$(__modem_get_signal_quality ${device} rssi)
518 assert_check_retval $?
519
520 isset rssi || return ${EXIT_ERROR}
521
522 # 99 indicates an unknown signal strength.
523 [ ${rssi} -eq 99 ] && return ${EXIT_UNKNOWN}
524
525 local dbm=$(( ${rssi} * 2 ))
526 dbm=$(( ${dbm} - 113 ))
527
528 print "%d" "${dbm}"
529 return ${EXIT_OK}
530 }
531
532 function modem_get_bit_error_rate() {
533 local device=${1}
534 assert isset device
535
536 local ber
537 ber=$(__modem_get_signal_quality ${device} ber)
538 assert_check_retval $?
539
540 isset ber || return ${EXIT_ERROR}
541
542 # 99 indicates that the bit error rate could not be detected or
543 # is unknown for some other reason.
544 [ ${ber} -eq 99 ] && return ${EXIT_UNKNOWN}
545
546 print "%d" "${ber}"
547 return ${EXIT_OK}
548 }