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