gts 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in gts.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Milan Novota
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Gts
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'gts'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install gts
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/bin/gts ADDED
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "gts"
4
+ require "optparse"
5
+
6
+ options = { :port => 2222, :address => "0.0.0.0" }
7
+
8
+ opt_parser = OptionParser.new do |opt|
9
+ opt.banner = "GPS Tracking Server, v.#{Gts::VERSION}, (c) 2012, Milan Novota" +
10
+ "\nUsage: gts [OPTIONS]"
11
+ opt.separator ""
12
+ opt.separator "Options"
13
+
14
+ opt.on("-a","--address IP_ADDRESS","on what address should server run") do |address|
15
+ options[:address] = address
16
+ end
17
+
18
+ opt.on("-p","--port PORT_NUMBER","on what port should server run") do |port|
19
+ options[:port] = port
20
+ end
21
+
22
+ opt.on("-d","--daemon","run in daemon mode?") do
23
+ options[:daemon] = true
24
+ end
25
+
26
+ opt.on("-o","--output FILENAME","output file name; if given csv file with captured data is created") do |filename|
27
+ options[:output_file] = filename
28
+ end
29
+
30
+ opt.on("-x","--debug","show debugging information") do
31
+ options[:debug] = true
32
+ end
33
+
34
+ opt.on("-h","--help","help") do
35
+ puts opt_parser
36
+ exit
37
+ end
38
+
39
+ end
40
+
41
+ opt_parser.parse!
42
+
43
+ puts "Starting GTS #{Gts::VERSION} on address #{options[:address]} and port #{options[:port]}"
44
+ if options[:debug]
45
+ Gts.set_log_level :debug
46
+ puts "Debugging level logs enabled!"
47
+ end
48
+ puts "Known devices: " + Gts.registered_handlers.map{|k,v| [k] }.join(", ")
49
+
50
+ Signal.trap("INT") { Gts::Server.stop! }
51
+ Signal.trap("TERM") { Gts::Server.stop! }
52
+
53
+ Gts.server = Gts::Server
54
+ Gts.server.start!(:address => options[:address], :port => options[:port], :output_file => options[:output_file])
data/gts.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/gts/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Milan Novota"]
6
+ gem.email = ["milan.novota@gmail.com"]
7
+ gem.description = ["GPS Tracking Server"]
8
+ gem.summary = ["GPS Tracking Server"]
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "gts"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Gts::VERSION
17
+
18
+ gem.add_dependency "eventmachine"
19
+ gem.add_dependency "json"
20
+ gem.add_dependency "awesome_print"
21
+ gem.add_development_dependency "rspec"
22
+
23
+ end
@@ -0,0 +1,21 @@
1
+ module Gts
2
+
3
+ class AbstractGPSTrackerHandler
4
+
5
+ class CantParseGPSData < StandardError; end
6
+
7
+ def self.register!
8
+ Gts.register_handler(self)
9
+ end
10
+
11
+ def parse(raw_data)
12
+ raise "Abstract!"
13
+ end
14
+
15
+ def devices
16
+ raise "Abstract!"
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,35 @@
1
+ require "gts/command_parser"
2
+
3
+ module Gts
4
+
5
+ class Command
6
+
7
+ class CommandError < StandardError; end
8
+ class UnknownCommand < CommandError; end
9
+
10
+ @@known_commands = {}
11
+ attr_reader :args
12
+
13
+ def self.register(name, handler)
14
+ @@known_commands[name] = handler
15
+ end
16
+
17
+ def self.execute(command_str)
18
+ parser = Gts::CommandParser.new
19
+ command_name, @args = *parser.parse(command_str)
20
+ command_handler(command_name.to_sym).execute
21
+ end
22
+
23
+ private
24
+
25
+ def self.command_handler(command_name)
26
+ @handler = @@known_commands[command_name]
27
+ raise UnknownCommand if !@handler
28
+ @handler.new
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+
35
+ Dir[File.dirname(__FILE__) +"/commands/*.rb"].each {|file| require file }
@@ -0,0 +1,25 @@
1
+ module Gts
2
+
3
+ class CommandParser
4
+
5
+ attr_reader :command, :args
6
+
7
+ def iniialize
8
+ @command = nil
9
+ @args = []
10
+ end
11
+
12
+ # zatial vieme parsovat iba primitivne veci
13
+ # status
14
+ # last_data
15
+ # known_imeis
16
+ # uptime
17
+ def parse(str)
18
+ command, *@args = str.split(/\s+/)
19
+ @command = command.to_sym
20
+ [@command, @args]
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,16 @@
1
+ require "gts/command"
2
+
3
+ module Gts
4
+
5
+ class ListKnownDevicesCommand < Command
6
+
7
+ Gts::Command.register :list_known_devices, self
8
+ Gts::Command.register :ld, self
9
+
10
+ def execute
11
+ Gts.server.list_known_devices
12
+ end
13
+
14
+ end
15
+
16
+ end
@@ -0,0 +1,15 @@
1
+ require "gts/command"
2
+
3
+ module Gts
4
+
5
+ class UptimeCommand < Command
6
+
7
+ Gts::Command.register :uptime, self
8
+
9
+ def execute
10
+ Gts.server.uptime
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,70 @@
1
+ module Gts
2
+
3
+ class TK102Handler < AbstractGPSTrackerHandler
4
+
5
+ def parse(raw_data)
6
+ raw_data = raw_data.gsub( /(^[\s\t\r\n]+|[\s\t\r\n]+$)/, '' )
7
+ attrs = raw_data.split(",")
8
+ raise CantParseGPSData if attrs.size != 18 || attrs[2] != "GPRMC"
9
+ datetime = parse_datetime attrs[0]
10
+ begin
11
+ phone = PhoneSystem::PhoneNumber.new(attrs[1]).long
12
+ rescue PhoneSystem::PhoneSystemError
13
+ phone = attrs[1]
14
+ end
15
+ gps_time = attrs[3].scan(/(\d{2})(\d{2})(\d{2})\.(\d{3})/).first
16
+ gps_time = "#{gps_time[0]}:#{gps_time[1]}:#{gps_time[2]}.#{gps_time[3]}"
17
+ gps_date = attrs[11].scan(/([0-9]{2})([0-9]{2})([0-9]{2})/).first
18
+ gps_year = Time.now.year.to_s[0..1] + gps_date[2]
19
+ gps_date = "#{gps_year}-#{gps_date[1]}-#{gps_date[0]}"
20
+ {
21
+ :raw => raw_data,
22
+ :datetime => datetime,
23
+ :phone => phone,
24
+ :gps_date => gps_date,
25
+ :gps_time => gps_time,
26
+ :gps_signal => (attrs[15] == 'F' ? 'full' : 'low'),
27
+ :gps_fix => (attrs[4] == 'A' ? 'active' : 'invalid'),
28
+ :lat => convert_nmea_coordinates(attrs[5], attrs[6]),
29
+ :lng => convert_nmea_coordinates(attrs[7], attrs[8]),
30
+ :bearing => attrs[10].to_i,
31
+ :speed_knots => (attrs[9].to_f * 1000 ).round / 1000,
32
+ :speed_kmh => ( attrs[9].to_f * 1.852 * 1000 ).round / 1000,
33
+ :speed_mph => ( attrs[9].to_f * 1.151 * 1000 ).round / 1000,
34
+ :imei => attrs[16].gsub('imei:', '')
35
+ }
36
+ end
37
+
38
+ def self.devices
39
+ %w( TK-102 TK-102B )
40
+ end
41
+
42
+ private
43
+
44
+ # Vrati rozumnejsiu stringovu verziu casu
45
+ def parse_datetime(str)
46
+ parts = str.scan(/.{2}/)
47
+ year = Time.now.year.to_s[0..1] + parts[0]
48
+ month = parts[1]
49
+ day = parts[2]
50
+ hour = parts[3]
51
+ minute = parts[3]
52
+ "#{year}-#{month}-#{day} #{hour}:#{minute}"
53
+ end
54
+
55
+
56
+ def convert_nmea_coordinates(one, two)
57
+ minutes = one[-7..-1]
58
+ degrees = one.gsub(minutes, "")
59
+ one = degrees.to_i + minutes.to_f/60
60
+ if two == "S" || two == "W"
61
+ one = -one
62
+ end
63
+ one
64
+ end
65
+
66
+ end
67
+
68
+ end
69
+
70
+ Gts::TK102Handler.register!
data/lib/gts/server.rb ADDED
@@ -0,0 +1,173 @@
1
+ require 'eventmachine'
2
+ require 'open-uri'
3
+ require 'json'
4
+ require 'gts'
5
+
6
+ DEFAULT_IMEI_HANDLERS = { "359585017718724" => "TK-102" } # IMEI nasej TK-102jky
7
+ DEFAULT_IMEI_HANDLER_SOURCE = nil
8
+
9
+ module Gts
10
+
11
+ module Server # TCP server
12
+
13
+ @@tracker_requests_count = 0
14
+ @@started_at = nil
15
+
16
+ def logger
17
+ Gts.logger
18
+ end
19
+
20
+ def self.logger
21
+ Gts.logger
22
+ end
23
+
24
+ class GtsError < StandardError; end;
25
+ class CantDetermineIMEI < GtsError; end;
26
+ class DontKnowHowToHandleThisDevice < GtsError; end;
27
+
28
+ def initialize
29
+ @@imei_handlers ||= DEFAULT_IMEI_HANDLERS
30
+ @@imei_handlers_source ||= DEFAULT_IMEI_HANDLER_SOURCE
31
+ @@device_handlers = Gts.registered_handlers
32
+ @@started_at ||= Time.now
33
+ load_imei_handlers!
34
+ end
35
+
36
+ def post_init
37
+ logger.info "New connection from #{client_ip_address}"
38
+ end
39
+
40
+ def receive_data data
41
+ if data =~ /^%/
42
+ handle_command(data.gsub(/^%\s*?/, ''))
43
+ else
44
+ handle_tracker_data(data)
45
+ end
46
+ end
47
+
48
+ def self.start!(opts={})
49
+ @@address = opts[:address]
50
+ @@port = opts[:port]
51
+ @@output_file = opts[:output_file]
52
+ EventMachine::run {
53
+ EventMachine::start_server @@address, @@port, self
54
+ puts "Server started. Press Ctrl+C to stop."
55
+ puts "Hunting for data..."
56
+ }
57
+ self
58
+ end
59
+
60
+ def self.stop!
61
+ EventMachine::stop
62
+ puts "Bye!"
63
+ end
64
+
65
+ def self.uptime
66
+ sum = Time.now - @@started_at
67
+ days = (sum / (3600 * 24)).to_i
68
+ hours = (sum / 3600 - (days * (3600 * 24))).to_i
69
+ minutes = (sum / 60 - hours * 60).to_i
70
+ seconds = (sum % 60).to_i
71
+ "#{days}d #{hours}h #{minutes}m #{seconds}s"
72
+ end
73
+
74
+ def self.list_known_devices
75
+ @@imei_handlers.map{ |k,v| "#{k}: #{v}" }.join "\n"
76
+ end
77
+
78
+ private
79
+
80
+ def handle_command(command)
81
+ begin
82
+ command = command.strip
83
+ output = Gts::Command.execute(command)
84
+ send_data output + "\n"
85
+ logger.info "Executed '#{command}' [from #{client_ip_address}]"
86
+ logger.debug "Command output:\n#{output}"
87
+ rescue Gts::Command::UnknownCommand
88
+ send_data "Error: Unknown command\n"
89
+ logger.error "Unknown command '#{command}' [from #{client_ip_address}]"
90
+ end
91
+ end
92
+
93
+ def handle_tracker_data(data)
94
+ begin
95
+ parsed_data = parse_data(data)
96
+ logger.info "Got correct data from #{client_ip_address}"
97
+ formatted_data = parsed_data.map{|k,v| "\t#{k}: #{v}\n" }.join
98
+ logger.debug "Parsed data:\n" + formatted_data
99
+ log_data_to_csv_file(parsed_data)
100
+ rescue GtsError => e
101
+ logger.error "Got incorrect data from #{client_ip_address}. Exception: #{e.to_s}"
102
+ logger.debug "Incorrect data: #{data}"
103
+ end
104
+ close_connection
105
+ end
106
+
107
+
108
+ def client_ip_address
109
+ get_peername[2,6].unpack('nC4')[1,4].join(".")
110
+ end
111
+
112
+ def log_data_to_csv_file(parsed_data)
113
+ if @@output_file
114
+ if !File.exists?(@@output_file)
115
+ File.open(@@output_file, "w") do |f|
116
+ f.puts parsed_data.keys.map{ |k| k.to_s }.sort.join("\t") # hlavicka
117
+ end
118
+ end
119
+ File.open(@@output_file, "a") do |f|
120
+ pd = parsed_data.keys.map{ |k| k.to_s }.sort.map{|k| parsed_data[k.to_sym]}.join("\t")
121
+ f.puts pd
122
+ end
123
+ end
124
+ end
125
+
126
+ def parse_data(data)
127
+ imei = get_imei(data)
128
+ if !known_imei?(imei)
129
+ logger.info "Unknown IMEI, reloading IMEI handlers index."
130
+ load_imei_handlers!
131
+ end
132
+ if known_imei?(imei)
133
+ handler = determine_handler(imei).new
134
+ handler.parse(data)
135
+ else
136
+ raise DontKnowHowToHandleThisDevice, "Don't know what handler to use for IMEI# #{imei}"
137
+ end
138
+ end
139
+
140
+ def known_imei?(imei)
141
+ !@@imei_handlers[imei].nil?
142
+ end
143
+
144
+ def determine_handler(imei)
145
+ imei_handler_str = @@imei_handlers[imei]
146
+ @@device_handlers[imei_handler_str]
147
+ end
148
+
149
+ # mozno by sme mohli priamo hadat typ zariadenia ked uz hadame imei,
150
+ # no imei_handlers tabulka a imei je bezpecnejsia cesta
151
+ def get_imei(data)
152
+ if data =~ /imei:(\d{15}),/
153
+ return $1
154
+ else
155
+ raise CantDetermineIMEI
156
+ end
157
+ end
158
+
159
+ def load_imei_handlers!
160
+ src = @@imei_handlers_source
161
+ @@imei_handlers = {} unless @@imei_handlers.is_a? Hash
162
+ return if src.nil? || src == ""
163
+ if File.exists?(src)
164
+ json = File.open(src).read
165
+ else
166
+ json = open(src).read
167
+ end
168
+ @@imei_handlers.merge!(JSON.parse(json))
169
+ end
170
+
171
+ end
172
+
173
+ end
@@ -0,0 +1,67 @@
1
+ 36:
2
+ country: Maďarsko
3
+ area_codes:
4
+ 1: Budapešť
5
+ 48:
6
+ country: Poľsko
7
+ area_codes:
8
+ 22: Varšava
9
+ 420:
10
+ country: Česká republika
11
+ area_codes:
12
+ 2: Praha
13
+ 5: Brno
14
+ 421:
15
+ country: Slovensko
16
+ format: ^(\+|0)[\d\s\(\)-\/]+$
17
+ min_size: 3
18
+ max_size: 9
19
+ area_codes:
20
+ 2: Bratislava
21
+ 31: Dunajská Streda
22
+ 32: Trenčín
23
+ 33: Trnava
24
+ 34: Senica
25
+ 35: Nové Zámky
26
+ 36: Levice
27
+ 37: Nitra
28
+ 38: Topoľčany
29
+ 41: Žilina
30
+ 42: Považská Bystrica
31
+ 43: Martin
32
+ 44: Liptovský Mikuláš
33
+ 45: Zvolen
34
+ 46: Prievidza
35
+ 47: Lučenec
36
+ 48: Banská Bystrica
37
+ 51: Prešov
38
+ 52: Poprad
39
+ 53: Spišská Nová Ves
40
+ 54: Bardejov
41
+ 55: Košice
42
+ 56: Michalovce
43
+ 57: Humenné
44
+ 58: Rožňava
45
+ 901: T-Mobile Slovensko
46
+ 902: T-Mobile Slovensko
47
+ 903: T-Mobile Slovensko
48
+ 904: T-Mobile Slovensko
49
+ 905: Orange Slovensko
50
+ 906: Orange Slovensko (VPN)
51
+ 907: Orange Slovensko
52
+ 908: Orange Slovensko
53
+ 910: T-Mobile Slovensko
54
+ 911: T-Mobile Slovensko
55
+ 912: T-Mobile Slovensko (CVPN)
56
+ 913: Unient Communications
57
+ 914: T-Mobile Slovensko (UMTS)
58
+ 915: Orange Slovensko
59
+ 916: Orange Slovensko (VPN)
60
+ 917: Orange Slovensko (UMTS)
61
+ 918: Orange Slovensko
62
+ 919: Orange Slovensko
63
+ 940: Telefónica O2 Slovakia
64
+ 944: Telefónica O2 Slovakia
65
+ 948: Telefónica O2 Slovakia
66
+ 949: Telefónica O2 Slovakia
67
+ 959: ŽSR (železničná mobilná sieť)
@@ -0,0 +1,239 @@
1
+ require "yaml"
2
+
3
+ module PhoneSystem
4
+
5
+ # Exceptions
6
+ class PhoneSystemError < StandardError; end
7
+ class CannotParsePhoneNumberString < PhoneSystemError; end
8
+ class CannotDetermineCountryCode < CannotParsePhoneNumberString; end
9
+ class AreaCodeNotValidForGivenCountry < CannotParsePhoneNumberString; end
10
+ class NumberTooShort < CannotParsePhoneNumberString; end
11
+ class NumberTooLong < CannotParsePhoneNumberString; end
12
+
13
+ # For future use, not implemented, yet
14
+ class PhoneNode; end
15
+ class PhoneCountryNode < PhoneNode; end
16
+ class PhoneAreaNode < PhoneNode; end
17
+
18
+ class Parser
19
+
20
+ @@phone_codes_file_path = File.join(File.dirname(__FILE__), "phone_codes.yml")
21
+ @@phone_codes = YAML.load(File.read(@@phone_codes_file_path))
22
+
23
+ def parse(pstring, options)
24
+ self.class.parse(pstring, options)
25
+ end
26
+
27
+ # Return hash, that represents valid international phone number with
28
+ # country code, area code, number, country, area/operator.
29
+ #
30
+ # String that do not begin with "+" or "0" are considered invalid.
31
+ # because we want to get absolute phone numbers, not relative, ie.
32
+ # the output will contain country code, area/mobile operator code and number.
33
+ #
34
+ # Country code is by default set to 421 (Slovakia), so we can leave it
35
+ # when inputting Slovak phone numbers and just start with area code.
36
+ #
37
+ # Constraints:
38
+ # - the number that do not start with "+" or "0" are considered invalid
39
+ # (since we are not able to determine the area/operator code).
40
+ # - the country code MUST be separated from the rest of the number by means
41
+ # of " " or "-".
42
+ #
43
+ # Arguments:
44
+ # - pstring : string with phone number to parse
45
+ # options:
46
+ # - country_code : implicit country phone code, if none found
47
+ # in pstring, defaults to 421 (Slovakia)
48
+ # - strict : if set to true, the pstring is checked against the list of
49
+ # area/operator codes found in phone_codes.yml. If none of the area/operator
50
+ # code matches pstring, exception is raised. Use only if you have complete
51
+ # list of the area/operator codes for given country. Defaults to false.
52
+ def self.parse(pstring, options = {})
53
+
54
+ options = {
55
+ :country_code => 421, # Slovakia
56
+ :strict => false
57
+ }.merge(options)
58
+
59
+ text_number = pstring
60
+ # if it starts with "+" or "00" then the country code should be given and we will
61
+ # try to get it.
62
+ if pstring =~ /^(\+|00)/
63
+ cc_match = pstring.match(/^(?:\+|00)(\d*)[ -\(]+/)
64
+ unless cc_match
65
+ then raise(CannotParsePhoneNumberString, "The string \"#{text_number}\" does not seem to be valid international phone number."); end
66
+ pstring = pstring.match(/^[\+0\d]*[ -\(]+(.*)$/)[1] # chop the country code off
67
+ country_code = cc_match[1].to_i
68
+ else
69
+ # if the country code is not set, fall to default
70
+ # and chop the first zero, so the area/operator code will be at the first place
71
+ unless options[:country_code]
72
+ then raise(CannotDetermineCountryCode, "The string \"#{text_number}\" does not seem to be valid phone number. Cannot determine country code."); end
73
+ pstring = pstring.sub("0","") # remove the leading zero, to get the area code to the first place
74
+ country_code = options[:country_code]
75
+ end
76
+
77
+ # do some cleaning - remove all the remaining characters, that are not digits
78
+ pstring = remove_other_than_digits(pstring)
79
+
80
+ area_code = nil
81
+ area_name = nil
82
+
83
+ # let's try to identify the country, area/operator and get the correct format for the country
84
+ # if possible
85
+ if @@phone_codes.include? country_code
86
+ country = @@phone_codes[country_code]["country"]
87
+ area_codes = @@phone_codes[country_code]["area_codes"]
88
+ pn_format = Regexp.new(@@phone_codes[country_code]["format"]) if @@phone_codes[country_code].include? "format"
89
+ area_codes.each do |code, name|
90
+ if pstring =~ /^#{code}/
91
+ area_code = code
92
+ area_name = name
93
+ pstring = pstring.sub(/#{code}/, "") # chop the area code off
94
+ break
95
+ end
96
+ end
97
+ # if not area code found and in strict mode, raise exception
98
+ if !area_code && options[:strict]
99
+ then raise(AreaCodeNotValidForGivenCountry, "The string \"#{text_number}\" does not seem to be valid phone number. Area code was not recognized."); end
100
+
101
+ # set the number size boundaries if found for given country
102
+ min_size = @@phone_codes[country_code].include?("min_size") ? @@phone_codes[country_code]["min_size"] : nil
103
+ max_size = @@phone_codes[country_code].include?("max_size") ? @@phone_codes[country_code]["max_size"] : nil
104
+ end
105
+
106
+ # if not specific format of numbers for given country set, then
107
+ # use general format
108
+ pn_format ||= /^(\+|0)[\d\s\(\)-\/]+$/
109
+ unless text_number =~ pn_format
110
+ then raise(CannotParsePhoneNumberString, "The string \"#{text_number}\" does not seem to be valid international phone number."); end
111
+
112
+ # if no size boundaries set, set them to reasonable defaults
113
+ min_size ||= 6
114
+ max_size ||= 15
115
+ # and check the remaining number against them
116
+ if pstring.size < min_size
117
+ then raise(NumberTooShort, "Cannot parse the string \"#{text_number}\" to phone number. The phone number would be too short."); end
118
+ if pstring.size > max_size
119
+ then raise(NumberTooLong, "Cannot parse the string \"#{text_number}\" to phone number. The phone number would be too long."); end
120
+
121
+ # if we made it here, we have the line number
122
+ number = pstring
123
+
124
+ out = {
125
+ :text => text_number,
126
+ :country_code => country_code,
127
+ :country => country,
128
+ :area_code => area_code,
129
+ :area_name => area_name,
130
+ :number => number
131
+ }
132
+ return out
133
+ rescue PhoneSystemError
134
+ false
135
+ end
136
+
137
+ private
138
+
139
+ # my way to remove other chars than digits from a string
140
+ # any better idea?
141
+ def self.remove_other_than_digits(numbers_and_stuff)
142
+ out = []
143
+ numbers_and_stuff.each_char{|c| out << c if c=~/\d/ }
144
+ out.join
145
+ end
146
+
147
+ end
148
+
149
+ class PhoneNumber
150
+
151
+ def self.valid?(pstring, options = {})
152
+ !!PhoneSystem::Parser.parse(pstring, options)
153
+ end
154
+
155
+ def valid?
156
+ PhoneNumber.valid? self.long
157
+ end
158
+
159
+ def initialize(pstring = nil, options = {})
160
+ @phone_number = {}
161
+ @phone_number = PhoneSystem::Parser.parse(pstring,options) unless (pstring == "" || pstring.nil?)
162
+ end
163
+
164
+ def country_code
165
+ @phone_number[:country_code]
166
+ end
167
+
168
+ def country
169
+ @phone_number[:country]
170
+ end
171
+
172
+ def area_code
173
+ @phone_number[:area_code]
174
+ end
175
+
176
+ def area_name
177
+ @phone_number[:area_name]
178
+ end
179
+
180
+ def area_name
181
+ @phone_number[:area_name]
182
+ end
183
+
184
+ def number
185
+ @phone_number[:number]
186
+ end
187
+
188
+ def text
189
+ @phone_number[:text]
190
+ end
191
+
192
+ def [](attr)
193
+ @phone_number[attr]
194
+ end
195
+
196
+ def long
197
+ "+#{country_code} #{area_code} #{number}"
198
+ end
199
+
200
+ def self.parse_and_format_long(pstring, options={})
201
+ if nr = PhoneSystem::Parser.parse(pstring, options)
202
+ return "+#{nr[:country_code]} #{nr[:area_code]} #{nr[:number]}"
203
+ else
204
+ return false
205
+ end
206
+ end
207
+
208
+ def short
209
+ "0#{area_code}/#{number}"
210
+ end
211
+
212
+ def text=(pstring)
213
+ @phone_number = PhoneSystem::Parser.new.parse(pstring)
214
+ end
215
+
216
+ def to_hash
217
+ {
218
+ :text => text,
219
+ :country_code => country_code,
220
+ :country => country,
221
+ :area_code => area_code,
222
+ :area_name => area_name,
223
+ :number => number,
224
+ :long => long,
225
+ :short => short
226
+ }
227
+ end
228
+
229
+ def ==(other_phone_number)
230
+ self.long = other_phone_number.long
231
+ end
232
+
233
+ end
234
+
235
+ end
236
+
237
+ # pn = PhoneSystem::PhoneNumber.new "0908420120"
238
+ # puts pn.inspect
239
+
@@ -0,0 +1,3 @@
1
+ module Gts
2
+ VERSION = "0.1.0"
3
+ end
data/lib/gts.rb ADDED
@@ -0,0 +1,63 @@
1
+ require "logger"
2
+
3
+ module Gts
4
+
5
+ def self.register_handler(klass)
6
+ @@registered_handlers ||= {}
7
+ klass.devices.each do |d|
8
+ @@registered_handlers[d] = klass
9
+ end
10
+ end
11
+
12
+ def self.registered_handlers
13
+ @@registered_handlers
14
+ end
15
+
16
+ def self.set_log_level(level=:info)
17
+ case level
18
+ when :debug
19
+ @@log_level = Logger::DEBUG
20
+ else
21
+ @@log_level = Logger::INFO
22
+ end
23
+ end
24
+
25
+ def self.log_level
26
+ begin
27
+ @@log_level
28
+ rescue
29
+ set_log_level
30
+ @@log_level
31
+ end
32
+ end
33
+
34
+ def self.logger
35
+ begin
36
+ @@logger
37
+ rescue
38
+ @@logger = Logger.new(STDOUT)
39
+ @@logger.datetime_format = "%d.%m.%Y %H:%M:%S"
40
+ @@logger.formatter = proc do |severity, datetime, progname, msg|
41
+ "#{datetime} #{severity}: #{msg}\n"
42
+ end
43
+ @@logger.level = log_level
44
+ @@logger
45
+ end
46
+ end
47
+
48
+ def self.server=(server_instance)
49
+ @@server = server_instance
50
+ end
51
+
52
+ def self.server
53
+ @@server
54
+ end
55
+
56
+ end
57
+
58
+ require "gts/version"
59
+ require "gts/utils/phone_system"
60
+ require "gts/abstract_gps_tracker_handler"
61
+ require "gts/handlers/tk102_handler"
62
+ require "gts/command"
63
+ require "gts/server"
@@ -0,0 +1,15 @@
1
+ # require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require "gts"
3
+
4
+ describe Gts::Command do
5
+
6
+ class SomeCommand < Gts::Command
7
+ end
8
+
9
+ it "allows to register new commands" do
10
+ expect {
11
+ SomeCommand.register :some, SomeCommand
12
+ }.not_to raise_exception
13
+ end
14
+
15
+ end
@@ -0,0 +1,20 @@
1
+ 1207272308,0907532880,GPRMC,220856.000,A,4809.6585,N,01708.0085,E,0.00,,270712,,,A*72,F,imei:359585017718724,98&?
2
+ 1207272309,0907532880,GPRMC,220957.000,A,4809.6526,N,01707.9981,E,0.00,,270712,,,A*70,F,imei:359585017718724,98?n
3
+ 1207272311,0907532880,GPRMC,221108.000,A,4809.6540,N,01708.0022,E,2.32,,270712,,,A*76,F,imei:359585017718724,98|?
4
+ 1207272311,0907532880,GPRMC,205559.000,A,4809.6760,N,01708.0389,E,0.00,,270712,,,A*71,L,imei:359585017718724,98??
5
+ 1207272315,0907532880,GPRMC,221510.000,A,4809.7429,N,01707.9160,E,7.81,352.81,270712,,,A*6B,F,imei:359585017718724,104?
6
+ 1207272316,0907532880,GPRMC,221634.000,A,4809.6799,N,01707.8709,E,1.60,208.30,270712,,,A*62,F,imei:359585017718724,104?/
7
+ 1207272317,0907532880,GPRMC,221751.000,A,4809.6481,N,01707.8410,E,2.20,229.80,270712,,,A*6E,F,imei:359585017718724,104?
8
+ 1207272318,0907532880,GPRMC,221846.000,A,4809.6214,N,01707.8074,E,2.42,240.02,270712,,,A*6A,F,imei:359585017718724,104?
9
+ 1207272320,0907532880,GPRMC,222001.000,A,4809.5866,N,01707.7769,E,0.79,65.09,270712,,,A*5E,F,imei:359585017718724,103u?
10
+ 1207272321,0907532880,GPRMC,222120.000,A,4809.5363,N,01707.7398,E,4.65,185.29,270712,,,A*6C,F,imei:359585017718724,104X?
11
+ 1207272322,0907532880,GPRMC,222221.000,A,4809.5087,N,01707.7280,E,0.00,291.23,270712,,,A*64,F,imei:359585017718724,104V?
12
+ 1207272323,0907532880,GPRMC,222335.000,A,4809.4998,N,01707.8916,E,1.16,140.35,270712,,,A*63,F,imei:359585017718724,104ݙ
13
+ 1207272325,0907532880,GPRMC,222505.000,A,4809.4882,N,01707.8748,E,5.91,74.16,270712,,,A*55,F,imei:359585017718724,103O
14
+ 1207272326,0907532880,GPRMC,222605.000,A,4809.5271,N,01707.7184,E,11.52,28.57,270712,,,A*6E,F,imei:359585017718724,104?}
15
+ 1207272327,0907532880,GPRMC,222733.000,A,4809.5048,N,01707.7301,E,1.41,,270712,,,A*78,F,imei:359585017718724,98`?
16
+ 1207272329,0907532880,GPRMC,222952.000,A,4809.4744,N,01707.6298,E,0.00,253.21,270712,,,A*66,F,imei:359585017718724,104??
17
+ 1207272330,0907532880,GPRMC,223049.000,A,4809.4793,N,01707.5970,E,1.76,,270712,,,A*79,F,imei:359585017718724,98?
18
+ 1207272331,0907532880,GPRMC,223124.000,A,4809.4821,N,01707.5665,E,1.10,,270712,,,A*7E,F,imei:359585017718724,98E\330
19
+ 1207272332,0907532880,GPRMC,223202.000,A,4809.4943,N,01707.5553,E,2.32,342.78,270712,,,A*6D,F,imei:359585017718724,104\021\370\n
20
+ 1207272332,0907532880,GPRMC,223239.000,A,4809.4987,N,01707.5872,E,1.92,34.17,270712,,,A*51,F,imei:359585017718724,103C
@@ -0,0 +1,21 @@
1
+ # require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require "gts"
3
+
4
+ CORRECT_LINE = "1207272321,0907532880,GPRMC,222120.000,A,4809.5363,N,01707.7398,E,4.65,185.29,270712,,,A*6C,F,imei:359585017718724,104X?"
5
+ INCORRECT_LINE = "blah"
6
+
7
+ describe Gts::TK102Handler do
8
+
9
+ let(:handler) { Gts::TK102Handler.new }
10
+
11
+ it "parses GPS data when data is correct" do
12
+ handler.parse(CORRECT_LINE).should be_a Hash
13
+ end
14
+
15
+ it "throws an exception when GPS data is incorrect" do
16
+ expect {
17
+ handler.parse(INCORRECT_LINE)
18
+ }.to raise_exception(Gts::TK102Handler::CantParseGPSData)
19
+ end
20
+
21
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gts
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Milan Novota
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-29 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: json
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: awesome_print
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: ! '["GPS Tracking Server"]'
79
+ email:
80
+ - milan.novota@gmail.com
81
+ executables:
82
+ - gts
83
+ extensions: []
84
+ extra_rdoc_files: []
85
+ files:
86
+ - .gitignore
87
+ - Gemfile
88
+ - LICENSE
89
+ - README.md
90
+ - Rakefile
91
+ - bin/gts
92
+ - gts.gemspec
93
+ - lib/gts.rb
94
+ - lib/gts/abstract_gps_tracker_handler.rb
95
+ - lib/gts/command.rb
96
+ - lib/gts/command_parser.rb
97
+ - lib/gts/commands/list_known_devices_command.rb
98
+ - lib/gts/commands/uptime_command.rb
99
+ - lib/gts/handlers/tk102_handler.rb
100
+ - lib/gts/server.rb
101
+ - lib/gts/utils/phone_codes.yml
102
+ - lib/gts/utils/phone_system.rb
103
+ - lib/gts/version.rb
104
+ - spec/command_spec.rb
105
+ - spec/test_data/tk102_sample_data.txt
106
+ - spec/tk102_handler_spec.rb
107
+ homepage: ''
108
+ licenses: []
109
+ post_install_message:
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 1.8.19
128
+ signing_key:
129
+ specification_version: 3
130
+ summary: ! '["GPS Tracking Server"]'
131
+ test_files:
132
+ - spec/command_spec.rb
133
+ - spec/test_data/tk102_sample_data.txt
134
+ - spec/tk102_handler_spec.rb