defmodule XGPS.Tools do require Bitwise def calculate_checksum text do Enum.reduce(String.codepoints(text), 0, &xor/2) end defp xor(x, acc) do <> = x Bitwise.bxor(acc, val) end def hex_string_to_int(string) do string |> Base.decode16! |> :binary.decode_unsigned end def int_to_hex_string(int) do int |> :binary.encode_unsigned |> Base.encode16 end def lat_to_decimal_degrees(degrees, minutes, "N"), do: degrees + (minutes/60.0) def lat_to_decimal_degrees(degrees, minutes, "S"), do: (degrees + (minutes/60.0)) * (-1.0) def lon_to_decimal_degrees(degrees, minutes, "E"), do: degrees + (minutes/60.0) def lon_to_decimal_degrees(degrees, minutes, "W"), do: (degrees + (minutes/60.0)) * (-1.0) def lat_from_decimal_degrees(decimal_degrees) when decimal_degrees >= 0.0 do degrees = Float.floor(decimal_degrees) |> round minutes = (decimal_degrees - degrees) * 60.0 bearing = "N" {degrees, minutes, bearing} end def lat_from_decimal_degrees(decimal_degrees) when decimal_degrees < 0.0 do degrees = Float.ceil(decimal_degrees) * (-1.0) |> round minutes = (decimal_degrees + degrees) * -60.0 bearing = "S" {degrees, minutes, bearing} end def lon_from_decimal_degrees(decimal_degrees) when decimal_degrees >= 0.0 do degrees = Float.floor(decimal_degrees) |> round minutes = (decimal_degrees - degrees) * 60.0 bearing = "E" {degrees, minutes, bearing} end def lon_from_decimal_degrees(decimal_degrees) when decimal_degrees < 0.0 do degrees = Float.ceil(decimal_degrees) * (-1.0) |> round minutes = (decimal_degrees + degrees) * -60.0 bearing = "W" {degrees, minutes, bearing} end def to_gps_date(date) do year = "#{date.year}" |> String.slice(2,2) month = "#{date.month}" |> String.pad_leading(2,"0") day = "#{date.day}" |> String.pad_leading(2,"0") day <> month <> year end def to_gps_time(time) do hour = "#{time.hour}" |> String.pad_leading(2,"0") minute = "#{time.minute}" |> String.pad_leading(2,"0") second = "#{time.second}" |> String.pad_leading(2,"0") {micro, _} = time.microsecond ms = round(micro / 1000) millis = "#{ms}" |> String.pad_leading(3, "0") "#{hour}#{minute}#{second}." <> millis end def generate_rmc_and_gga_for_simulation(lat, lon, alt, date_time) do {lat_deg, lat_min, lat_bear} = XGPS.Tools.lat_from_decimal_degrees(lat) {lon_deg, lon_min, lon_bear} = XGPS.Tools.lon_from_decimal_degrees(lon) latitude = lat_to_string(lat_deg, lat_min, lat_bear) longitude = lon_to_string(lon_deg, lon_min, lon_bear) date = XGPS.Tools.to_gps_date(date_time) time = XGPS.Tools.to_gps_time(date_time) rmc_body = "GPRMC,#{time},A,#{latitude},#{longitude},0.0,0.0,#{date},,,A" rmc_checksum = XGPS.Tools.calculate_checksum(rmc_body) |> XGPS.Tools.int_to_hex_string rmc = "$#{rmc_body}*#{rmc_checksum}" gga_body = "GPGGA,#{time},#{latitude},#{longitude},1,05,0.0,#{alt},M,0.0,M,," gga_checksum = XGPS.Tools.calculate_checksum(gga_body) |> XGPS.Tools.int_to_hex_string gga = "$#{gga_body}*#{gga_checksum}" {rmc, gga} end def generate_rmc_and_gga_for_simulation_no_fix(date_time) do date = XGPS.Tools.to_gps_date(date_time) time = XGPS.Tools.to_gps_time(date_time) rmc_body = "GPRMC,#{time},V,,,,,,,#{date},,,A" rmc_checksum = XGPS.Tools.calculate_checksum(rmc_body) |> XGPS.Tools.int_to_hex_string rmc = "$#{rmc_body}*#{rmc_checksum}" gga_body = "GPGGA,#{time},,,,,0,0,,,M,,M,," gga_checksum = XGPS.Tools.calculate_checksum(gga_body) |> XGPS.Tools.int_to_hex_string gga = "$#{gga_body}*#{gga_checksum}" {rmc, gga} end defp lat_to_string(deg, min, bearing) when min >= 10.0 do deg_string = "#{deg}" |> String.pad_leading(2, "0") min_string = "#{Float.round(min,4)}" |> String.pad_trailing(7, "0") deg_string <> min_string <> "," <> bearing end defp lat_to_string(deg, min, bearing) do deg_string = "#{deg}" |> String.pad_leading(2, "0") min_string = "0#{Float.round(min,4)}" |> String.pad_trailing(7, "0") deg_string <> min_string <> "," <> bearing end defp lon_to_string(deg, min, bearing) when min > 10.0 do deg_string = "#{deg}" |> String.pad_leading(3, "0") min_string = "#{Float.round(min,4)}" |> String.pad_trailing(7, "0") deg_string <> min_string <> "," <> bearing end defp lon_to_string(deg, min, bearing) do deg_string = "#{deg}" |> String.pad_leading(3, "0") min_string = "0#{Float.round(min,4)}" |> String.pad_trailing(7, "0") deg_string <> min_string <> "," <> bearing end end