]>
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 | ||
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 | ||
395 | local mode format operator act | |
396 | read mode format operator act <<< "${output//,/ }" | |
397 | ||
398 | # Remove all " from the operator string. | |
399 | operator=${operator//\"/} | |
400 | ||
401 | print "${!argument}" | |
402 | return ${EXIT_OK} | |
403 | } | |
404 | ||
405 | function modem_get_network_operator() { | |
406 | local device=${1} | |
407 | assert isset device | |
408 | ||
409 | __modem_get_network_operator ${device} operator | |
410 | } | |
411 | ||
412 | # Exit codes of the network operator mode function. | |
413 | EXIT_OPMODE_GSM=0 | |
414 | EXIT_OPMODE_COMPACTGSM=1 | |
415 | EXIT_OPMODE_GSM_WITH_EGPRS=2 | |
416 | EXIT_OPMODE_UMTS=3 | |
417 | EXIT_OPMODE_UMTS_WITH_HSDPA=4 | |
418 | EXIT_OPMODE_UMTS_WITH_HSUPA=5 | |
419 | EXIT_OPMODE_UMTS_WITH_HSDPA_AND_HSUPA=6 | |
420 | EXIT_OPMODE_UNKNOWN=7 | |
421 | ||
422 | function modem_get_network_mode() { | |
423 | local device=${1} | |
424 | assert isset device | |
425 | ||
426 | local output | |
427 | output=$(__modem_get_network_operator ${device} act) | |
428 | assert_check_retval $? | |
429 | ||
430 | case "${output}" in | |
431 | 0) | |
432 | print "GSM" | |
433 | return ${EXIT_OPMODE_GSM} | |
434 | ;; | |
435 | 1) | |
436 | print "Compact GSM" | |
437 | return ${EXIT_OPMODE_COMPACTGSM} | |
438 | ;; | |
439 | 2) | |
440 | print "UMTS" | |
441 | return ${EXIT_OPMODE_UMTS} | |
442 | ;; | |
443 | 3) | |
444 | print "UMTS with HSDPA" | |
445 | return ${EXIT_OPMODE_UMTS_WITH_HSDPA} | |
446 | ;; | |
447 | 4) | |
448 | print "UMTS with HSUPA" | |
449 | return ${EXIT_OPMODE_UMTS_WITH_HSUPA} | |
450 | ;; | |
451 | 5) | |
452 | print "UMTS with HSDPA and HSUPA" | |
453 | return ${EXIT_OPMODE_UMTS_WITH_HSDPA_AND_HSUPA} | |
454 | ;; | |
455 | *) | |
456 | print "Unknown" | |
457 | return ${EXIT_OPMODE_UNKNOWN} | |
458 | ;; | |
459 | esac | |
460 | } | |
461 | ||
462 | function __modem_get_signal_quality() { | |
463 | local device=${1} | |
464 | assert isset device | |
465 | ||
466 | # Make sure the SIM card is unlocked for this operation. | |
467 | assert modem_sim_unlocked ${device} | |
468 | ||
469 | local argument=${2} | |
470 | assert isset argument | |
471 | ||
472 | local output | |
473 | output=$(modem_chat ${device} "AT+CSQ") | |
474 | assert_check_retval $? | |
475 | ||
476 | output=${output#*: } | |
477 | ||
478 | case "${output}" in | |
479 | *,*) | |
480 | local rssi=${output%,*} | |
481 | local ber=${output#*,} | |
482 | ||
483 | print "${!argument}" | |
484 | return ${EXIT_OK} | |
485 | ;; | |
486 | *) | |
487 | log ERROR "Unknown format for AT+CSQ: ${device}: ${output}" | |
488 | ;; | |
489 | esac | |
490 | ||
491 | return ${EXIT_ERROR} | |
492 | } | |
493 | ||
494 | function modem_get_signal_quality() { | |
495 | local device=${1} | |
496 | assert isset device | |
497 | ||
498 | local rssi | |
499 | rssi=$(__modem_get_signal_quality ${device} rssi) | |
500 | assert_check_retval $? | |
501 | ||
502 | isset rssi || return ${EXIT_ERROR} | |
503 | ||
504 | # 99 indicates an unknown signal strength. | |
505 | [ ${rssi} -eq 99 ] && return ${EXIT_UNKNOWN} | |
506 | ||
507 | local dbm=$(( ${rssi} * 2 )) | |
508 | dbm=$(( ${dbm} - 113 )) | |
509 | ||
510 | print "%d" "${dbm}" | |
511 | return ${EXIT_OK} | |
512 | } | |
513 | ||
514 | function modem_get_bit_error_rate() { | |
515 | local device=${1} | |
516 | assert isset device | |
517 | ||
518 | local ber | |
519 | ber=$(__modem_get_signal_quality ${device} ber) | |
520 | assert_check_retval $? | |
521 | ||
522 | isset ber || return ${EXIT_ERROR} | |
523 | ||
524 | # 99 indicates that the bit error rate could not be detected or | |
525 | # is unknown for some other reason. | |
526 | [ ${ber} -eq 99 ] && return ${EXIT_UNKNOWN} | |
527 | ||
528 | print "%d" "${ber}" | |
529 | return ${EXIT_OK} | |
530 | } |