PageRenderTime 26ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/terminal.rb

https://github.com/lamikae/Ruby-GSM
Ruby | 292 lines | 167 code | 49 blank | 76 comment | 12 complexity | 8b1a71790a95068ef40fdcc76b846ebc MD5 | raw file
  1. module GSM
  2. # To add new features, create a new Kermit script and add an alias or a method.
  3. class Terminal
  4. file = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
  5. this_dir = File.dirname(File.expand_path(file))
  6. @@scripts = File.join(this_dir,'kermit')
  7. # the data is intercepted from STDOUT
  8. STDOUT.sync = true
  9. STDERR.sync = true
  10. # checks the device status and enters the PIN if the SIM is locked.
  11. def initialize
  12. end
  13. # static methods
  14. class << self
  15. private
  16. # script parameter is the filename of the script
  17. def exec_kermit(script)
  18. dir = @@scripts
  19. executable = dir+'/'+script
  20. #logger.debug "Executing Kermit script %s" % script
  21. response = exec_command( 'cd %s && %s' % [ dir, executable ] )
  22. raise NoResponseException unless response
  23. return response
  24. end
  25. # simulate a response from the device
  26. def exec_test(script)
  27. # test directory echoes plain TXT files that fake response from the device
  28. dir = File.join(@@scripts,'test')
  29. executable = File.join(dir,script)
  30. # if the script does not exist (truncate parameters for checking), read the input from a file
  31. # (name constructed from script name + parameters)
  32. unless File.exists?(executable.split(' ')[0]) then
  33. response_fn = '/resp/'+script.gsub(/.sh.*/,'').gsub(/\ /,'_').downcase
  34. ##logger.debug "File %s does not exist, reading input from file %s" % [script, response_fn]
  35. return exec_command( 'cat %s' % dir+response_fn )
  36. else
  37. ##logger.debug "Executing shell script %s" % script
  38. return exec_command( 'cd %s && %s' % [ dir, executable] )
  39. end
  40. end
  41. # this does the actual I/O
  42. def exec_command( cmd )
  43. #logger.debug 'Executing system command "%s"' % cmd
  44. response = ''
  45. IO::popen( cmd ) do |f|
  46. until f.eof?
  47. # copy buffer
  48. response << f.gets
  49. end
  50. end
  51. ##logger.debug "Response: %s" % response
  52. raise NoInputException if response.empty?
  53. return response
  54. end
  55. def set_cmgf(encoding)
  56. if encoding==:ascii
  57. return 1
  58. # elsif encoding==:pdu
  59. # return 0
  60. else
  61. #logger.warn "Unsupported encoding #{encoding}, using ASCII"
  62. return 1
  63. end
  64. end
  65. def this_method
  66. caller[0][/`([^']*)'/, 1]
  67. end
  68. protected
  69. def raise_cms_error(response)
  70. error_code = response[/ERROR: (.*)/,1].to_i
  71. raise CMSError, 'CMS ERROR %i: %s' % [error_code, CMSError.message(error_code)]
  72. end
  73. def raise_cme_error(response)
  74. raise CMEError, response[/\+(.*ERROR.*)/,1]
  75. end
  76. def raise_general_error(response)
  77. msg = response[/ERROR: (.*)/,1]
  78. raise DeviceNotFoundException, msg if msg[/ttyUSB/]
  79. raise msg
  80. end
  81. public
  82. def method_missing(method, *args, &block) # :dodoc:
  83. #logger.debug "Creating dynamic method #{method}"
  84. $GSM_SIMULATION ||= false # perform the action on a real device (HAXK)
  85. if File.exists?(File.join(@@scripts,"#{method}.ksc"))
  86. return meta( args, method )
  87. end
  88. end
  89. # runs a Kermit script by the name of the alias of the method parameter.
  90. # returns true if the response is plain "OK".
  91. # otherwise returns an Array with the response section; the response(s) are between +COMMAND: ..... OK
  92. # the regexp for this is not perfect so it may or may not fail.
  93. def meta(params=nil,method=this_method)
  94. # select the script suffix on the basis whether
  95. # the device is present or are we simulating a response
  96. suffix = ($GSM_SIMULATION ? '.sh' : '.ksc')
  97. # use a script with the same name as the caller
  98. script = "#{method}"+suffix
  99. # add parameters, accept an Array or a single value
  100. params = [params] unless params.is_a? Array
  101. script << ' "' + params.join('" "') + '"' if params.any?
  102. # execute script
  103. if $GSM_SIMULATION
  104. response = exec_test(script)
  105. else
  106. response = exec_kermit(script)
  107. end
  108. # raise an error if the device outputs error
  109. raise_cme_error(response) if response[/CME\ ERROR/]
  110. raise_cms_error(response) if response[/CMS\ ERROR/]
  111. raise_general_error(response) if response[/ERROR/]
  112. # parse output
  113. responses = []
  114. #STDERR.puts 'Original: ' + response.inspect
  115. #STDERR.puts " ** "
  116. # clean the response; remove all strings (separated by newline) that start with AT but do NOT have ':'
  117. response.gsub!(/^AT[^:]*\n/,'')
  118. # remove BOOT and RSSI strings
  119. response.gsub!(/\^BOOT.*\n|\^RSSI.*\n/,'')
  120. #STDERR.puts 'Cleaned: ' + response.inspect
  121. #STDERR.puts " ** "
  122. response.split(/^\+|^AT.+\nOK|\^BOOT/).each_with_index do |output,index|
  123. unless output.nil?
  124. responses << output unless output.empty?
  125. end
  126. end
  127. return responses.compact || true
  128. end
  129. ########################################################
  130. ### Device status checks
  131. # sends plain AT to the modem, expecting OK
  132. # returns true if successful, false otherwise
  133. def device_responds?
  134. begin
  135. # send plain AT command
  136. meta(nil,:at)
  137. rescue DeviceNotFoundException
  138. false
  139. end
  140. end
  141. # checks if the PIN has been entered correctly and the device is ready
  142. def pin_ok?
  143. response = meta(nil,:check_pin).first
  144. response[/READY/] ? true : false
  145. end
  146. # returns true if there is a carrier, false otherwise
  147. def carrier?
  148. return false if self.carrier.nil?
  149. return false if self.carrier.empty?
  150. return true
  151. end
  152. ##########################################################
  153. ### Kermit script output parsers
  154. # outputs the device manufacturer, model and the GSM number (IMEI)
  155. def device_id
  156. response = meta(nil,:info).first
  157. manufacturer = response[/^Manufacturer: (.*)/,1].strip
  158. model = response[/^Model: (.*)/,1].strip
  159. revision = response[/^Revision: (.*)/,1].strip
  160. imei = response[/^IMEI: (.*)/,1].strip
  161. return '%s %s, IMEI: %s' % [manufacturer.capitalize,model,imei]
  162. end
  163. # gets the carrier string.
  164. # returns nil if carrier is not detected.
  165. def carrier
  166. response = meta(nil,this_method).first
  167. return response[/.,.,"([^"]*)"/,1]
  168. end
  169. # lists the available messages.
  170. #
  171. # parameters:
  172. # 1 CMGL filter: "ALL", "REC READ", "REC UNREAD", "STO SENT", "STO UNSENT"
  173. # 2 CMGF mode (PDU / ASCII)
  174. # NOTE: listing marks the SMS as 'READ'
  175. def list_sms(cmgl,encoding=:ascii)
  176. #logger.info "Listing %s messages" % cmgl
  177. cmgf=set_cmgf(encoding)
  178. messages = []
  179. meta([cmgl,cmgf],this_method).each_with_index do |msg,index|
  180. begin
  181. messages << SMS.new(msg, encoding)
  182. rescue SMSParseException
  183. #logger.error $!.message
  184. end
  185. end
  186. return messages
  187. end
  188. # reads a specific SMS
  189. # parameters:
  190. # 1 index
  191. # 2 CMGF mode (PDU / ASCII)
  192. def read_sms(index,encoding=:ascii)
  193. #logger.info "Reading message %i in %s mode" % [index, encoding.to_s]
  194. cmgf=set_cmgf(encoding)
  195. msg = meta([index,cmgf],this_method).first
  196. begin
  197. SMS.new(msg, encoding)
  198. rescue SMSParseException
  199. #logger.error $!.message
  200. end
  201. end
  202. ##########################################################
  203. ### Aliases that return parsed device response
  204. ### or raise CMSError or CMEError, see meta.
  205. public
  206. # sends the PIN number to unlock the SIM
  207. # parameters:
  208. # 1 PIN
  209. alias :enter_pin :meta
  210. # removes a specific SMS
  211. # parameters:
  212. # 1 index
  213. alias :del_sms :meta
  214. # sends an ASCII-formatted SMS
  215. # parameters:
  216. # 1 GSM number (global, including +prefix)
  217. # 2 message
  218. alias :send_sms :meta
  219. end # static methods
  220. end
  221. class DeviceNotFoundException < Exception
  222. end
  223. class NoInputException < Exception
  224. end
  225. class CMSError < Exception
  226. def self.message(code)
  227. case code
  228. when 300 then 'ME Failure'
  229. when 302 then 'Operation not allowed'
  230. when 303 then 'Operation not supported'
  231. when 304 then 'Invalid PDU mode parameter'
  232. when 305 then 'Invalid text mode parameter'
  233. when 320 then 'memory failure'
  234. when 321 then 'invalid memory index'
  235. when 322 then 'memory full'
  236. when 330 then 'SCA unknown'
  237. when 500 then 'Unknown error'
  238. end
  239. end
  240. end
  241. class CMEError < Exception
  242. end
  243. end