]>
Commit | Line | Data |
---|---|---|
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: | |
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 | ||
95416c1e MT |
129 | function 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. |
163 | EXIT_SIM_READY=0 | |
164 | EXIT_SIM_PIN=1 | |
165 | EXIT_SIM_PUK=2 | |
166 | EXIT_SIM_UNKNOWN=3 | |
167 | ||
168 | function 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 | ||
198 | function 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 | ||
208 | function modem_sim_locked() { | |
209 | modem_sim_unlocked $@ && return ${EXIT_FALSE} || return ${EXIT_TRUE} | |
210 | } | |
211 | ||
212 | function 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 |
242 | function 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" | |
278 | function 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 | ||
292 | function 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 | ||
306 | function 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 | ||
320 | function 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 | ||
330 | function 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 | ||
345 | function 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. | |
357 | EXIT_REG_REGISTERED_TO_HOME_NETWORK=0 | |
358 | EXIT_REG_NOT_REGISTERED_NOT_SEARCHING=1 | |
359 | EXIT_REG_NOT_REGISTERED_SEARCHING=2 | |
360 | EXIT_REG_REGISTRATION_DENIED=3 | |
361 | EXIT_REG_REGISTERED_ROAMING=4 | |
362 | EXIT_REG_UNKNOWN=5 | |
363 | ||
364 | function 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 | ||
417 | function 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 | ||
430 | function 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 | ||
446 | function __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 | ||
490 | function 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. | |
498 | EXIT_OPMODE_GSM=0 | |
499 | EXIT_OPMODE_COMPACTGSM=1 | |
500 | EXIT_OPMODE_GSM_WITH_EGPRS=2 | |
501 | EXIT_OPMODE_UMTS=3 | |
502 | EXIT_OPMODE_UMTS_WITH_HSDPA=4 | |
503 | EXIT_OPMODE_UMTS_WITH_HSUPA=5 | |
504 | EXIT_OPMODE_UMTS_WITH_HSDPA_AND_HSUPA=6 | |
505 | EXIT_OPMODE_UNKNOWN=7 | |
506 | ||
507 | function 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 | ||
547 | function __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 | ||
579 | function 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 | ||
599 | function 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 | } |