gts 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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