Add a GNSS simulator for dev without gpsd running
bin/gnss_simulator.rb speaks gpsd's own JSON wire protocol directly on its default port -- GpsdClient connects to it exactly as it would to real gpsd, no app changes needed, and no gpsd install/daemon required either (unlike the existing fake-NMEA-source approach in docs/development.adoc, which still needs gpsd itself in front of it). Drives a loop around a center point at a driving-ish pace rather than sitting still, since a fixed position isn't much of a "GNSS position" to look at. Verified end to end: started it, pointed a dev server at it, and confirmed /api/gnss/position reports a moving fix across two polls.
This commit is contained in:
1 parent
4aafe332a7
commit
61ba86c6e6
3 files changed
+95
-4
No files matched your search
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Speaks gpsd's own JSON wire protocol (https://gpsd.io/gpsd_json.html)
|
||||
# directly on gpsd's default port — GpsdClient (lib/maptool/gpsd_client.rb)
|
||||
# connects to it exactly as it would to real gpsd, so no app code or config
|
||||
# changes are needed. Unlike docs/development.adoc's fake-NMEA-source
|
||||
# example, this doesn't need gpsd itself installed/running at all, and
|
||||
# actually moves (a fixed point isn't much of a "GNSS position" to look
|
||||
# at) — drives a loop around a center point at a driving-ish pace, one TPV
|
||||
# report per second like a real receiver.
|
||||
#
|
||||
# Usage: bin/gnss_simulator.rb [lat] [lon] [radius_m] [port]
|
||||
require "socket"
|
||||
require "json"
|
||||
|
||||
$stdout.sync = true
|
||||
|
||||
LAT = (ARGV[0] || 48.1486).to_f
|
||||
LON = (ARGV[1] || 17.1077).to_f
|
||||
RADIUS_M = (ARGV[2] || 300).to_f
|
||||
PORT = (ARGV[3] || 2947).to_i
|
||||
LOOP_SECONDS = 120 # time to complete one full loop
|
||||
EARTH_RADIUS_M = 6_371_000
|
||||
|
||||
def point_on_loop(center_lat, center_lon, radius_m, angle_rad)
|
||||
# Flat-plane offset, not geodesic — fine at this radius (metres, not km),
|
||||
# same reasoning as measure.js's own approximations at this scale.
|
||||
lat_offset = (radius_m * Math.sin(angle_rad)) / EARTH_RADIUS_M
|
||||
lon_offset = (radius_m * Math.cos(angle_rad)) / (EARTH_RADIUS_M * Math.cos(center_lat * Math::PI / 180))
|
||||
[center_lat + lat_offset * 180 / Math::PI, center_lon + lon_offset * 180 / Math::PI]
|
||||
end
|
||||
|
||||
server = TCPServer.new("127.0.0.1", PORT)
|
||||
puts "[gnss-simulator] driving a #{RADIUS_M.to_i}m loop around #{LAT},#{LON} — listening on 127.0.0.1:#{PORT}"
|
||||
|
||||
loop do
|
||||
socket = server.accept
|
||||
puts "[gnss-simulator] client connected"
|
||||
start = Time.now
|
||||
|
||||
begin
|
||||
loop do
|
||||
elapsed = Time.now - start
|
||||
angle = (elapsed / LOOP_SECONDS) * 2 * Math::PI
|
||||
lat, lon = point_on_loop(LAT, LON, RADIUS_M, angle)
|
||||
|
||||
# Bearing of travel: tangent to the circle, 90 degrees ahead of the
|
||||
# radius vector at this point.
|
||||
track = (angle * 180 / Math::PI + 90) % 360
|
||||
speed = (2 * Math::PI * RADIUS_M) / LOOP_SECONDS # m/s, constant pace
|
||||
|
||||
report = {
|
||||
class: "TPV",
|
||||
mode: 3,
|
||||
lat: lat,
|
||||
lon: lon,
|
||||
altHAE: 150.0,
|
||||
speed: speed,
|
||||
track: track,
|
||||
eph: 3.0,
|
||||
time: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%3NZ"),
|
||||
}
|
||||
socket.puts(JSON.generate(report))
|
||||
sleep 1
|
||||
end
|
||||
rescue Errno::EPIPE, IOError
|
||||
puts "[gnss-simulator] client disconnected"
|
||||
ensure
|
||||
socket.close
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user