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