gpsd2json 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +46 -0
  4. data/lib/gps2json.rb +211 -0
  5. metadata +138 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 61aaf077f1e5605afa404ee0b7639296273c1f43
4
+ data.tar.gz: 219dccf5fe3dc81841c126362a9eba3ddb983baa
5
+ SHA512:
6
+ metadata.gz: 0dc9aadcfadb0779369fa5094ad951decb2d6deafbb93cb71e37c09868f333a665a8703f5b97e7b6a83741a417efe23cf78a1ff92331e74e02c3a9e78851608f
7
+ data.tar.gz: 0ddb145d5046fa5d313a29d392253ce14d28868eb2a83d4f69f149aa0f811a2c4a0d2403047c8caeb65e1e0c8563707619ced218cc3fed8cbcc38be6a6a9ed45
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Mischa Molhoek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # gpsd2json
2
+ ruby client to receive JSON formatted info of the gps daemon
3
+
4
+ ## initialization
5
+ ```bash
6
+ require 'gpsd2json'
7
+ gps = GPSD2JSON.new()
8
+ ```
9
+ ## First you set some callbacks on the most important changes
10
+ ```bash
11
+ gps.on_position_change { |pos| STDERR.puts pos.inpect }
12
+ gps.on_satellites_change { |sats| STDERR.puts "#{sats.count} found, #{sats.count{|sat| sat['used']} are used" }
13
+ ```
14
+ ## Then, your start watching
15
+ ```bash
16
+ gps.start
17
+ ```
18
+ after this, the positions will be given to the callback block
19
+
20
+ ## When you had enough, you can stop watching
21
+ ```bash
22
+ gps.stop
23
+ ```
24
+
25
+ ## there is on more callback to receive all data as raw json
26
+ ```bash
27
+ gps.on_raw_data { |json| STDERR.puts json.inspect}
28
+ ```
29
+
30
+ ## Also, you can change the minimum speed requered to return a position change, with
31
+ ```bash
32
+ gps.change_min_speed(speed: <whatever speed>)
33
+ ```
34
+
35
+ ## development
36
+ ```bash
37
+ # Install
38
+ bundle
39
+ # irb
40
+ bundle exec irb -r ./lib/gpsd2json.rb
41
+ # test
42
+ bundle exec rspec --color -fd spec/gpsd_client_test.rb
43
+ ```
44
+ it also have a code coverage dir for you to see if your test set is about 95%
45
+
46
+ send me PR if you want changes, but only dare to do so when you added the proper tests
data/lib/gps2json.rb ADDED
@@ -0,0 +1,211 @@
1
+ require 'socket'
2
+ require 'json'
3
+ require 'date'
4
+ class GPSD2JSON
5
+ VERBOSE = false
6
+ # A simple gpsd client that dump's json objects contianing all info received from the gpsd deamon
7
+ # you need to at least setup either the raw callback (on_raw_change) or position callback (on_position_change) to use GPSD2JSON. the raw callback just passes the json objects it received from the daemon on to the block you pass it. the on_position_change and on_satellites_change are a bit easier to use.
8
+ # @example Easy setup
9
+ # gps = GPSD2JSON.new()
10
+ # gps.on_satellites_change { |sats| STDERR.puts "found #{sats.length} satellites, of which #{sats.count{|sat| sat['used']} } active" }
11
+ # gps.on_position_change { |pos| STDERR.puts "lat: #{pos['lat']}, lng: #{pos['lon']}, alt: #{pos['alt']}, speed: #{pos['speed']} at #{pos['time']}, which is #{(Time.now - pos['time'].to_time) * 1000}ms old" }
12
+ # gps.start
13
+ # #when done
14
+ # gps.stop
15
+ # @example Quickest raw mode, just dumping all json packets as the are
16
+ # gps = GPSD2JSON.new()
17
+ # gps.on_raw_change { |raw| STDERR.puts raw.inspect }
18
+ # gps.start
19
+ # #when done
20
+ # gps.stop
21
+ def initialize(host: 'localhost', port: 2947)
22
+ @socket = nil
23
+ @socket_ready = false
24
+ @host = host
25
+ @port = port
26
+ @trackthread = nil
27
+ @socket_init_thread = nil
28
+ @min_speed = 0.8 # speed needs to be higher than this to make the gps info count
29
+ @last = nil #last gps info
30
+ @sats = nil # last satellites info
31
+ @json_raw_callback = nil
32
+ @json_pos_callback = nil
33
+ @json_sat_callback = nil
34
+ end
35
+
36
+ # @param [Object] options Possible options to pass (not used yet)
37
+ # @param [Block] block Block to call when new json object comes from gpsd
38
+ def on_raw_change(options:{}, &block)
39
+ @json_raw_callback = block
40
+ end
41
+
42
+ # @param [Object] options Possible options to pass (not used yet)
43
+ # @param [Block] block Block to call when new gps position json object comes from gpsd
44
+ def on_position_change(options:{}, &block)
45
+ @json_pos_callback = block
46
+ end
47
+
48
+ # @param [Object] options Possible options to pass (not used yet)
49
+ # @param [Block] block Block to call when new satellite info json object comes from gpsd
50
+ def on_satellites_change(options:{}, &block)
51
+ @json_sat_callback = block
52
+ end
53
+
54
+ # @param [Float] speed The minimum speed to accept a gps update
55
+ def change_min_speed(speed:)
56
+ @min_speed = speed
57
+ end
58
+
59
+ # Open the socket and when ready request the position flow from the gps daemon
60
+ def start
61
+ # background thread that is used to open the socket and wait for it to be ready
62
+ @socket_init_thread = Thread.start do
63
+ #open the socket
64
+ while not @socket_ready
65
+ init_socket
66
+ #wait for it to be ready
67
+ sleep 0.1
68
+ end
69
+ # it's ready, tell it to start watching and passing
70
+ puts "socket ready, start watching" if VERBOSE
71
+ @socket.puts '?WATCH={"enable":true,"json":true}'
72
+ end
73
+
74
+ # background thead that is used to read info from the socket and use it
75
+ @trackthread = Thread.start do
76
+ while true do
77
+ begin
78
+ read_from_socket
79
+ rescue
80
+ "error while reading socket: #{$!}" if VERBOSE
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ # @return [string] status info string containing nr satellites, fix, speed
87
+ def to_status
88
+ return "lat: #{last['lat']}, lng: #{last['lon']}, speed:#{last['speed']}, sats: #{@sats.length}(#{@sats.count{|sat| sat['used']}})" if @socket_ready and @last and @sats
89
+ return "lat: #{last['lat']}, lng: #{last['lon']}, speed:#{last['speed']}" if @socket_ready and @last and @sats.nil?
90
+ return "sats: #{@sats.length}(#{@sats.count{|sat| sat['used']}}), no fix yet" if @socket_ready and @last.nil? and @sats
91
+ return "connected with gpsd, waiting for data" if @socket_ready
92
+ return "waiting for connection with gpsd" if @socket_ready == false
93
+ end
94
+
95
+ # Stop the listening loop and close the socket. It will read the last bit of data from the socket, close it, and clean it up
96
+ def stop
97
+ # last read(s)
98
+ 3.times { read_from_socket }
99
+ # then close
100
+ close_socket
101
+ # then cleanup
102
+ Thread.kill(@socket_init_thread) if @socket_init_thread
103
+ Thread.kill(@trackthread) if @trackthread
104
+ @socket_ready = false
105
+ end
106
+
107
+ # initialize gpsd socket
108
+ def init_socket
109
+ begin
110
+ puts "init_socket" if VERBOSE
111
+ close_socket if @socket
112
+ @socket = TCPSocket.new(@host, @port)
113
+ @socket.puts("w+")
114
+ puts "reading socket..." if VERBOSE
115
+ welkom = ::JSON.parse(@socket.gets)
116
+ puts "welkom: #{welkom.inspect}" if VERBOSE
117
+ @socket_ready = (welkom and welkom['class'] and welkom['class'] == 'VERSION')
118
+ puts "@socket_ready: #{@socket_ready.inspect}" if VERBOSE
119
+ rescue
120
+ @socket_ready = false
121
+ puts "#$!" if VERBOSE
122
+ end
123
+ end
124
+
125
+ # Read from socket. this should happen in a Thread as a continues loop. It should try to read data from the socket but nothing might happen if the gps deamon might not be ready. If ready it will send packets that we read and proces
126
+ def read_from_socket
127
+ if @socket_ready
128
+ begin
129
+ if input = @socket.gets.chomp and not input.to_s.empty?
130
+ parse_socket_json(json: JSON.parse(input))
131
+ else
132
+ sleep 0.1
133
+ end
134
+ rescue
135
+ puts "error reading from socket: #{$!}" if VERBOSE
136
+ end
137
+ else
138
+ sleep 0.1
139
+ end
140
+ end
141
+
142
+ # Proceses json object returned by gpsd daemon. The TPV and SKY object
143
+ # are used the most as they give info about satellites used and gps locations
144
+ # @param [JSON] json The object returned by the daemon
145
+ def parse_socket_json(json:)
146
+ case json['class']
147
+ when 'DEVICE', 'DEVICES'
148
+ # devices that are found, not needed
149
+ when 'WATCH'
150
+ # gps deamon is ready and will send other packets, not needed yet
151
+ when 'TPV'
152
+ # gps position
153
+ # "tag"=>"RMC", # "device"=>"/dev/ttyS0", # "mode"=>3,
154
+ # "time"=>"2017-11-28T12:54:54.000Z", # "ept"=>0.005, # "lat"=>52.368576667,
155
+ # "lon"=>4.901715, # "alt"=>-6.2, # "epx"=>2.738, # "epy"=>3.5,
156
+ # "epv"=>5.06, # "track"=>198.53, # "speed"=>0.19, # "climb"=>0.0,
157
+ # "eps"=>7.0, # "epc"=>10.12
158
+ if json['mode'] > 1
159
+ #we have a 2d or 3d fix
160
+ if is_new_measurement(json: json)
161
+ json['time'] = DateTime.parse(json['time'])
162
+ puts "lat: #{json['lat']}, lng: #{json['lon']}, alt: #{json['alt']}, speed: #{json['speed']} at #{json['time']}, which is #{(Time.now - json['time'].to_time) * 1000}ms old" if VERBOSE
163
+ @json_pos_callback.call(json) if @json_pos_callback
164
+ end
165
+ end
166
+ when 'SKY'
167
+ # report on found satellites
168
+ sats = json['satellites']
169
+ if satellites_changed(sats: sats)
170
+ puts "found #{sats.length} satellites, of which #{sats.count{|sat| sat['used']}} are used" if VERBOSE
171
+ @json_sat_callback.call(sats) if @json_sat_callback
172
+ end
173
+ else
174
+ puts "hey...found unknow tag: #{json.inspect}" if VERBOSE
175
+ end
176
+ @json_raw_callback.call(json) if @json_raw_callback
177
+ end
178
+
179
+ # checks if the new satellites object return by the deamon is different enough compared
180
+ # to the last one, to use it
181
+ def satellites_changed(sats:)
182
+ if @sats.nil? or (@sats.length != sats.length or @sats.count{|sat| sat['used']} != sats.count{|sat| sat['used']})
183
+ @sats = sats
184
+ return true
185
+ end
186
+ return false
187
+ end
188
+
189
+ # checks if the new location object return by the deamon is different enough compared
190
+ # to the last one, to use it. it could be disregarded for example because the speed is to low, and you don't want to have the location jumping around when you stand still
191
+ def is_new_measurement(json:)
192
+ if @last.nil? or (@last['lat'] != json['lat'] and @last['lon'] != json['lon'] and json['speed'] >= @min_speed)
193
+ @last = json
194
+ return true
195
+ end
196
+ return false
197
+ end
198
+
199
+ # Close the gps deamon socket
200
+ def close_socket
201
+ begin
202
+ if @socket
203
+ @socket.puts '?WATCH={"enable":false}'
204
+ @socket.close
205
+ end
206
+ @socket = nil
207
+ rescue
208
+ puts "#$!" if VERBOSE
209
+ end
210
+ end
211
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gpsd2json
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mischa Molhoek
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-12-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '12.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '12.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: juwelier
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.4.0
34
+ - - "~>"
35
+ - !ruby/object:Gem::Version
36
+ version: '2.4'
37
+ type: :development
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 2.4.0
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.4'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: simplecov
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: bundler
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '1.0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '1.0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: yard-doctest
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.1'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '0.1'
103
+ description: This gem can be used to talk to the gps daemon
104
+ email: mischamolhoek@gmail.com
105
+ executables: []
106
+ extensions: []
107
+ extra_rdoc_files:
108
+ - LICENSE
109
+ - README.md
110
+ files:
111
+ - LICENSE
112
+ - README.md
113
+ - lib/gps2json.rb
114
+ homepage: http://github.com/mmolhoek/gpsd2json
115
+ licenses:
116
+ - MIT
117
+ metadata: {}
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project:
134
+ rubygems_version: 2.5.1
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: ruby gem to access the gpsd daemon
138
+ test_files: []