Module: SerialModem
- Extended by:
- HelperClasses::DPuts, SerialModem
- Includes:
- HelperClasses, HelperClasses::DPuts
- Included in:
- SerialModem
- Defined in:
- lib/serial_modem.rb
Constant Summary collapse
- DEBUG_LVL =
1
Instance Attribute Summary collapse
-
#serial_sms ⇒ Object
Returns the value of attribute serial_sms.
-
#serial_sms_new ⇒ Object
Returns the value of attribute serial_sms_new.
-
#serial_ussd_new ⇒ Object
Returns the value of attribute serial_ussd_new.
Instance Method Summary collapse
- #attached? ⇒ Boolean
- #check_presence ⇒ Object
- #get_operator ⇒ Object
- #init_modem ⇒ Object
- #interpret_serial_reply ⇒ Object
- #kill ⇒ Object
- #modem_send(str, reply = true, lock: true) ⇒ Object
- #modem_send_array(cmds) ⇒ Object
- #pdu_to_ussd(str) ⇒ Object
- #read_reply(wait = nil, lock: true) ⇒ Object
- #reload_option ⇒ Object
- #save_modem ⇒ Object
- #set_connection_type(net, modem = :e303) ⇒ Object
- #setup_modem(dev = nil) ⇒ Object
- #setup_tty ⇒ Object
- #sms_delete(number) ⇒ Object
- #sms_new(id, flag, number, date, msg, unknown = nil) ⇒ Object
- #sms_scan(force = false) ⇒ Object
- #sms_send(number, msg) ⇒ Object
- #start_serial_thread ⇒ Object
- #switch_to_hilink ⇒ Object
- #traffic_statistics ⇒ Object
- #ussd_close ⇒ Object
- #ussd_fetch(str) ⇒ Object
- #ussd_received(str) ⇒ Object
- #ussd_send(str) ⇒ Object
- #ussd_send_now ⇒ Object
- #ussd_store_result(str) ⇒ Object
- #ussd_to_pdu(str) ⇒ Object
Instance Attribute Details
#serial_sms ⇒ Object
Returns the value of attribute serial_sms.
6 7 8 |
# File 'lib/serial_modem.rb', line 6 def serial_sms @serial_sms end |
#serial_sms_new ⇒ Object
Returns the value of attribute serial_sms_new.
6 7 8 |
# File 'lib/serial_modem.rb', line 6 def serial_sms_new @serial_sms_new end |
#serial_ussd_new ⇒ Object
Returns the value of attribute serial_ussd_new.
6 7 8 |
# File 'lib/serial_modem.rb', line 6 def serial_ussd_new @serial_ussd_new end |
Instance Method Details
#attached? ⇒ Boolean
469 470 471 |
# File 'lib/serial_modem.rb', line 469 def attached? @serial_sp != nil end |
#check_presence ⇒ Object
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 |
# File 'lib/serial_modem.rb', line 340 def check_presence @serial_mutex.synchronize { @serial_tty.to_s.length > 0 and File.exists?(@serial_tty) and return System.exists?('lsusb') or return case lsusb = System.run_str('lsusb') when /19d2:fff1/ log_msg :SerialModem, 'Found CDMA-modem with ttyUSB0-ttyUSB4' @serial_tty_error = '/dev/ttyUSB5' @serial_tty = '/dev/ttyUSB1' @ussd_add = '' @serial_eats_sms = true when /12d1:1506/, /12d1:14ac/, /12d1:1c05/, /12d1:1001/ log_msg :SerialModem, 'Found 3G-modem with ttyUSB0-ttyUSB2' @serial_tty_error = '/dev/ttyUSB3' @serial_tty = '/dev/ttyUSB2' @ussd_add = (lsusb =~ /12d1:14ac/) ? ',15' : '' @serial_eats_sms = true when /airtel-modem/ log_msg :SerialModem, 'Found 3G-modem with ttyUSB0-ttyUSB4' @serial_tty_error = '/dev/ttyUSB5' @serial_tty = '/dev/ttyUSB4' @ussd_add = '' else #puts caller.join("\n") @serial_tty = @serial_tty_error = nil end dputs(2) { "serial_tty is #{@serial_tty.inspect} and exists " + "#{File.exists?(@serial_tty.to_s)}" } if @serial_tty_error && File.exists?(@serial_tty_error) log_msg :SerialModem, 'resetting modem' reload_option end } end |
#get_operator ⇒ Object
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 |
# File 'lib/serial_modem.rb', line 272 def get_operator modem_send_array([['AT+COPS=3,0', 'OK'], ['AT+COPS?', 'OK']]) (1..6).each { if @serial_codes.has_key? 'COPS' return '' if @serial_codes['COPS'] == '0' @serial_eats_sms and modem_send('AT+CNMI=0,0,0,0,0', 'OK') op = @serial_codes['COPS'].scan(/".*?"|[^",]\s*|,,/)[2].gsub(/"/, '') dputs(2) { "Found operator-string #{op}" } return op end sleep 0.5 } return '' end |
#init_modem ⇒ Object
299 300 301 302 303 304 305 306 307 |
# File 'lib/serial_modem.rb', line 299 def init_modem %w( ATZ AT+CNMI=0,0,0,0,0 AT+CPMS="SM","SM","SM" AT+CFUN=1 AT+CMGF=1 ).each { |at| modem_send(at, 'OK') } @serial_eats_sms and modem_send('AT+CNMI=0,0,0,0,0', 'OK') set_connection_type '3g' end |
#interpret_serial_reply ⇒ Object
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/serial_modem.rb', line 67 def interpret_serial_reply ret = [] @serial_replies.each { |s| dputs(3) { s } } while m = @serial_replies.shift @serial_debug and dputs_func dputs(3) { "Reply: #{m}" } next if (m == '' || m =~ /^\^/) ret.push m if m =~ /\+[\w]{4}: / code, msg = m[1..4], m[7..-1] dputs(3) { "found code #{code.inspect} - #{msg.inspect}" } @serial_codes[code] = msg case code when /CMGL/ # Typical input from the modem: # "0,\"REC UNREAD\",\"+23599836457\",,\"15/04/08,17:12:21+04\"" # Output desired: # ["0", "REC UNREAD", "+23599836457", "", "15/04/08,17:12:21+04"] id, flag, number, unknown, date = msg.scan(/"(.*?)"|([^",]+)\s*|,,/m).collect { |a, b| a.to_s + b.to_s } msg = [] # Read all lines up to an empty line or a +CMGL: which indicates # a new message while @serial_replies[0] && @serial_replies[0] != '' && !(@serial_replies[0] =~ /^\+CMGL:/) msg.push @serial_replies.shift end # If we finish with an empty line, delete it and the 'OK' that follows if @serial_replies[0] == '' @serial_replies.shift(2) end ret.push msg.join("\n") sms_new(id, flag, number, date, ret.last, unknown) when /CUSD/ if pdu = msg.match(/.*\"(.*)\".*/) ussd_received(pdu_to_ussd(pdu[1])) elsif msg == '2' #log_msg :serialmodem, 'Closed USSD.' #ussd_received('') #ussd_close else log_msg :serialmodem, "Unknown: CUSD - #{msg}" end when /CMTI/ if msg =~ /^.ME.,/ dputs(3) { "I think I got a new message: #{msg}" } @serial_sms_autoscan_last = Time.now - @serial_sms_autoscan else log_msg :serialmodem, "Unknown: CMTI - #{msg}" end # Probably a message or so - '+CMTI: "ME",0' is a new message end end end end |
#kill ⇒ Object
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 |
# File 'lib/serial_modem.rb', line 452 def kill #dputs_func #puts "Killed by \n" + caller.join("\n") if @serial_thread if @serial_thread.alive? dputs(3) { 'Killing thread' } @serial_thread.kill dputs(3) { 'Joining thread' } @serial_thread.join dputs(3) { 'Thread joined' } end end @serial_sp and @serial_sp.close dputs(1) { 'SerialModem killed' } @serial_sp = nil end |
#modem_send(str, reply = true, lock: true) ⇒ Object
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/serial_modem.rb', line 133 def modem_send(str, reply = true, lock: true) return unless @serial_sp @serial_debug and dputs_func dputs(3) { "Sending string #{str} to modem" } lock and @serial_mutex.lock begin @serial_sp.write("#{str}\r\n") rescue Errno::EIO => e log_msg :SerialModem, "Couldn't write to device" kill return rescue Errno::ENODEV => e log_msg :SerialModem, 'Device is not here anymore' kill return end read_reply(reply, lock: false) lock and @serial_mutex.unlock end |
#modem_send_array(cmds) ⇒ Object
153 154 155 156 157 158 159 |
# File 'lib/serial_modem.rb', line 153 def modem_send_array(cmds) @serial_mutex.synchronize { cmds.each { |str, reply=true| modem_send(str, reply, lock: false) } } end |
#pdu_to_ussd(str) ⇒ Object
174 175 176 177 |
# File 'lib/serial_modem.rb', line 174 def pdu_to_ussd(str) [str].pack('H*').unpack('b*').join.scan(/.{7}/). map { |s| [s+'0'].pack('b*') }.join end |
#read_reply(wait = nil, lock: true) ⇒ Object
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/serial_modem.rb', line 44 def read_reply(wait = nil, lock: true) @serial_debug and dputs_func raise IOError.new('NoModemHere') unless @serial_sp begin lock and @serial_mutex.lock while !@serial_sp.eof? || wait begin @serial_replies.push rep = @serial_sp.readline.chomp break if rep == wait rescue EOFError => e dputs(4) { 'Waited for string, but got nothing' } break end end lock and @serial_mutex.unlock ret = interpret_serial_reply rescue IOError => e raise e end ret end |
#reload_option ⇒ Object
440 441 442 443 444 445 446 447 448 449 450 |
# File 'lib/serial_modem.rb', line 440 def reload_option @serial_sp and @serial_sp.close @serial_sp = nil dputs(1) { 'Trying to reload modem-driver - killing and reloading' } %w(chat ppp).each { |pro| System.run_str("killall -9 #{pro}") } %w(rmmod modprobe).each { |cmd| System.run_str("#{cmd} option") } end |
#save_modem ⇒ Object
165 166 167 |
# File 'lib/serial_modem.rb', line 165 def save_modem modem_send('AT^U2DIAG=0', 'OK') end |
#set_connection_type(net, modem = :e303) ⇒ Object
288 289 290 291 292 293 |
# File 'lib/serial_modem.rb', line 288 def set_connection_type(net, modem = :e303) # According to https://wiki.archlinux.org/index.php/3G_and_GPRS_modems_with_pppd cmds = {e303: {c3go: '14,2,3FFFFFFF,0,2', c3g: '2,2,3FFFFFFF,0,2', c2go: '13,1,3FFFFFFF,0,2', c2g: '2,1,3FFFFFFF,0,2'}} modem_send "AT^SYSCFG=#{cmds[modem]["c#{net}".to_sym]}", 'OK' end |
#setup_modem(dev = nil) ⇒ Object
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/serial_modem.rb', line 13 def setup_modem(dev = nil) # @serial_debug = true @serial_debug = false @serial_tty = @serial_tty_error = @serial_sp = nil @serial_replies = [] @serial_codes = {} @serial_sms = {} # TODO: once serialmodem == class, change this into Observer @serial_sms_new = [] @serial_sms_new_list = [] @serial_sms_autoscan = 20 @serial_sms_autoscan_last = Time.now @serial_ussd = [] @serial_ussd_last = Time.now @serial_ussd_timeout = 30 @serial_ussd_results = [] @serial_ussd_results_max = 100 @serial_ussd_sent = 0 @serial_ussd_sent_max = 5 @serial_ussd_send_next = false # TODO: once serialmodem == class, change this into Observer @serial_ussd_new = [] @serial_ussd_new_list = [] @serial_mutex = Mutex.new # Some Huawei-modems eat SMS once they send a +CMTI-message - this # turns off the CMTI-messages which slows down incoming SMS detection @serial_eats_sms = false setup_tty dp "tty is #{@serial_tty}" end |
#setup_tty ⇒ Object
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/serial_modem.rb', line 309 def setup_tty check_presence @serial_mutex.synchronize { if !@serial_sp && @serial_tty if File.exists? @serial_tty dputs(2) { 'setting up SerialPort' } @serial_sp = SerialPort.new(@serial_tty, 115200) @serial_sp.read_timeout = 500 end elsif @serial_sp && (!@serial_tty||(@serial_tty && !File.exists?(@serial_tty))) dputs(2) { 'disconnecting modem' } kill end } if @serial_sp dputs(2) { 'initialising modem' } init_modem start_serial_thread if !@serial_sp dputs(2) { 'Lost serial-connection while initialising - killing and reloading' } kill reload_option return false end dputs(2) { 'finished connecting' } end return @serial_sp != nil end |
#sms_delete(number) ⇒ Object
263 264 265 266 267 268 269 270 |
# File 'lib/serial_modem.rb', line 263 def sms_delete(number) dputs(3) { "Asking to delete #{number} from #{@serial_sms.inspect}" } if @serial_sms.has_key? number.to_s dputs(3) { "Deleting #{number}" } modem_send("AT+CMGD=#{number}", 'OK') @serial_sms.delete number.to_s end end |
#sms_new(id, flag, number, date, msg, unknown = nil) ⇒ Object
124 125 126 127 128 129 130 131 |
# File 'lib/serial_modem.rb', line 124 def sms_new(id, flag, number, date, msg, unknown = nil) sms = {flag: flag, number: number, unknown: unknown, date: date, msg: msg, id: id} @serial_sms[id.to_s] = sms log_msg :SerialModem, "New SMS: #{sms.inspect}" @serial_sms_new_list.push(sms) sms end |
#sms_scan(force = false) ⇒ Object
251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/serial_modem.rb', line 251 def sms_scan(force = false) if force || (@serial_sms_autoscan > 0 && Time.now - @serial_sms_autoscan_last > @serial_sms_autoscan) dputs(3) { 'Auto-scanning sms' } @serial_sms_autoscan_last = Time.now req = [['AT+CMGF=1', 'OK'], ['AT+CMGL="ALL"', 'OK']] @serial_eats_sms and req.push(['AT+CNMI=0,0,0,0,0', 'OK']) modem_send_array(req) end end |
#sms_send(number, msg) ⇒ Object
244 245 246 247 248 249 |
# File 'lib/serial_modem.rb', line 244 def sms_send(number, msg) log_msg :SerialModem, "Sending SMS --#{msg.inspect}-- to --#{number.inspect}--" modem_send_array([['AT+CMGF=1', 'OK'], ["AT+CMGS=\"#{number}\""], ["#{msg}\x1a", 'OK']]) end |
#start_serial_thread ⇒ Object
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 |
# File 'lib/serial_modem.rb', line 375 def start_serial_thread @serial_thread = Thread.new { # dputs_func dputs(2) { 'Thread started' } while @serial_sp do begin dputs(3) { 'Reading out modem' } read_reply dputs(4) { (Time.now - @serial_ussd_last).to_s } if @serial_ussd_send_next @serial_ussd_send_next = false ussd_send_now elsif ((Time.now - @serial_ussd_last > @serial_ussd_timeout) && (@serial_ussd.length > 0)) || @serial_ussd_send_next if (@serial_ussd_sent += 1) <= @serial_ussd_sent_max log_msg :SerialModem, "Re-sending #{@serial_ussd.first} for #{@serial_ussd_sent}" ussd_send_now else log_msg :SerialModem, "Discarding #{@serial_ussd.first}" @serial_ussd.shift @serial_ussd_sent = 0 end end sms_scan # Check for any new sms and call attached methods while (sms = @serial_sms_new_list.shift) do rescue_all do if sms._flag =~ /unread/i @serial_sms_new.each { |s| s.call(sms) } else dputs(2) { "Already read sms: #{sms}" } end sms_delete(sms._id) end end # Check for any new ussds and call attached methods while (ussd = @serial_ussd_new_list.shift) do code, str = ussd @serial_ussd_new.each { |s| s.call(code, str) } end sleep 0.5 rescue IOError log_msg :SerialModem, 'IOError - killing modem' kill return rescue Exception => e dputs(0) { "#{e.inspect}" } dputs(0) { "#{e.to_s}" } e.backtrace.each { |l| dputs(0) { l } } end dputs(5) { 'Finished' } end dputs(0) { '@serial_sp disappeared - quitting' } } end |
#switch_to_hilink ⇒ Object
161 162 163 |
# File 'lib/serial_modem.rb', line 161 def switch_to_hilink modem_send('AT^U2DIAG=119', 'OK') end |
#traffic_statistics ⇒ Object
295 296 297 |
# File 'lib/serial_modem.rb', line 295 def traffic_statistics end |
#ussd_close ⇒ Object
194 195 196 197 |
# File 'lib/serial_modem.rb', line 194 def ussd_close modem_send("AT+CUSD=2#{@ussd_add}", 'OK') @serial_ussd.length > 0 and ussd_send_now end |
#ussd_fetch(str) ⇒ Object
237 238 239 240 241 242 |
# File 'lib/serial_modem.rb', line 237 def ussd_fetch(str) return nil unless @serial_ussd_results dputs(3) { "Fetching str #{str} - #{@serial_ussd_results.inspect}" } res = @serial_ussd_results.reverse.find { |u| u._code == str } res ? res._result : nil end |
#ussd_received(str) ⇒ Object
231 232 233 234 235 |
# File 'lib/serial_modem.rb', line 231 def ussd_received(str) code = ussd_store_result(str) dputs(2) { "Got result for #{code}: -#{str}-" } @serial_ussd_new_list.push([code, str]) end |
#ussd_send(str) ⇒ Object
199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/serial_modem.rb', line 199 def ussd_send(str) # dputs_func if str.class == String dputs(3) { "Sending ussd-code #{str}" } @serial_ussd.push str @serial_ussd.length == 1 and ussd_send_now elsif str.class == Array dputs(3) { "Sending menu-command #{str}" } @serial_ussd.concat str @serial_ussd.push nil @serial_ussd.length == str.length + 1 and ussd_send_now end @serial_ussd_sent = 0 end |
#ussd_send_now ⇒ Object
179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/serial_modem.rb', line 179 def ussd_send_now return unless @serial_ussd.length > 0 str_send = @serial_ussd.first @serial_ussd_last = Time.now if str_send #log_msg :SerialModem, "Sending ussd-string #{str_send} with add of #{@ussd_add} "+ #"and queue #{@serial_ussd}" modem_send("AT+CUSD=1,\"#{ussd_to_pdu(str_send)}\"#{@ussd_add}", 'OK') else dputs(2) { 'Sending ussd-close' } @serial_ussd.shift ussd_close end end |
#ussd_store_result(str) ⇒ Object
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/serial_modem.rb', line 214 def ussd_store_result(str) if @serial_ussd.length > 0 code = @serial_ussd.shift dputs(2) { "Got USSD-reply for #{code}: #{str}" } @serial_ussd_results.push(time: Time.now.strftime('%H:%M'), code: code, result: str) @serial_ussd_results.shift([0, @serial_ussd_results.length - @serial_ussd_results_max].max) @serial_ussd_send_next = true @serial_ussd_sent = 0 code else #log_msg :serialmodem, "Got unasked code #{str}" 'unknown' end end |
#ussd_to_pdu(str) ⇒ Object
169 170 171 172 |
# File 'lib/serial_modem.rb', line 169 def ussd_to_pdu(str) str.unpack('b*').join.scan(/.{8}/).map { |s| s[0..6] }.join. scan(/.{1,8}/).map { |s| [s].pack('b*').unpack('H*')[0].upcase }.join end |