# Garmin HUD Companion Application # Copyright (C) 2022 Piotr Dec / ztsh.eu # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . import time from enum import Enum import serial from numpy import uint8 gps = False class OutType(Enum): Off = 0x00 Lane = 0x01 LongerLane = 0x02 LeftRoundabout = 0x04 RightRoundabout = 0x08 Flag = 0x40 ArrowOnly = 0x80 class OutAngle(Enum): Down = 0x01 SharpRight = 0x02 Right = 0x04 EasyRight = 0x08 Straight = 0x10 EasyLeft = 0x20 Left = 0x40 SharpLeft = 0x80 LeftDown = 0x81 RightDown = 0x82 AsDirection = 0x00 class Unit(Enum): Any = 0 Metres = 1 Kilometres = 3 Miles = 5 Foot = 8 class Lane(Enum): DotsRight = 0x01 OuterRight = 0x02 MiddleRight = 0x04 InnerRight = 0x08 InnerLeft = 0x10 MiddleLeft = 0x20 OuterLeft = 0x40 DotsLeft = 0x80 class Controller: def __init__(self, device: str = None): self.gps = False self.debug = device is None if not self.debug: self.ser = serial.Serial(device, 9600) time.sleep(2) print(self.ser.read_all()) def clear(self): self.send_hud([0x03, 0, 0, 0, 0x00, 0, 0]) def _as_dgt(self, number: float) -> int: n = int(number) if n == 0: return 0 n %= 10 return 10 if n == 0 else n def set_lines(self, outlines: list, arrows: list): self.send_hud([0x02, sum([lane.value for lane in outlines]), sum([lane.value for lane in arrows])]) def set_direction(self, angle: OutAngle, out: OutType = OutType.Lane, roundabout: OutAngle = OutAngle.AsDirection): if angle == OutAngle.LeftDown: param_1 = 0x10 elif angle == OutAngle.RightDown: param_1 = 0x20 else: param_1 = out.value if out == OutType.RightRoundabout or out == OutType.LeftRoundabout: param_2 = angle.value if roundabout == OutAngle.AsDirection else roundabout.value else: param_2 = 0x00 param_3 = 0x00 if angle == OutAngle.LeftDown or angle == OutAngle.RightDown else angle.value arr = [0x01, param_1, param_2, param_3] self.send_hud(arr) def set_distance(self, distance: float, unit: Unit = Unit.Any): is_float = int(distance * 10) == int(distance) * 10 if not is_float: distance = distance * 10 arr = [0x03, self._as_dgt(distance // 1000), self._as_dgt(distance // 100), self._as_dgt(distance // 10), 0x00 if is_float else 0xff, self._as_dgt(distance), unit.value] self.send_hud(arr) def set_speed(self, speed: int, limit: int = 0, acc: bool = False): acc_char = 0xff if acc else 0x00 if not self.gps: self.set_distance(speed) self.send_hud([0x06, 0, 0, 0, 0, 0, 0, 0, 0, acc_char]) return if limit > 0: arr = [0x06, self._as_dgt(speed // 100), self._as_dgt(speed // 10), self._as_dgt(speed), 0xff, self._as_dgt(limit // 100), self._as_dgt(limit // 10), self._as_dgt(limit), 0xff if speed > limit else 0x00, acc_char] else: arr = [0x06, 0x00, 0x00, 0x00, 0x00, self._as_dgt(speed // 100), self._as_dgt(speed // 10), self._as_dgt(speed), 0x00, acc_char] self.send_hud(arr) def set_time(self, hours: int, minutes: int, traffic: bool = False, flag: bool = False): traffic_char = 0xff if traffic else 0x00 flag_char = 0xff if flag else 0x00 if hours > 99: arr = [0x05, traffic_char, self._as_dgt(hours // 1000), self._as_dgt(hours // 100), 0x00, self._as_dgt(hours // 10), self._as_dgt(hours), 0xff, flag_char] else: arr = [0x05, traffic_char, self._as_dgt(hours // 10), self._as_dgt(hours), 0xff, self._as_dgt(minutes // 10), self._as_dgt(minutes), 0x00, flag_char] self.send_hud(arr) def set_gps(self, state: bool): if state: self.send_hud([0x07, 0x01]) self.gps = state def set_speed_control(self, on: bool = True): self.send_hud([0x04, 0x01 if on else 0x00]) def set_compass(self, direction: float): if direction > 337.5 or direction <= 22.5: self.set_direction(OutAngle.Straight, OutType.ArrowOnly) elif 22.5 < direction <= 67.5: self.set_direction(OutAngle.EasyRight, OutType.ArrowOnly) elif 67.5 < direction <= 112.5: self.set_direction(OutAngle.Right, OutType.ArrowOnly) elif 112.5 < direction <= 157.5: self.set_direction(OutAngle.SharpRight, OutType.ArrowOnly) elif 157.5 < direction <= 202.5: self.set_direction(OutAngle.Down, OutType.ArrowOnly) elif 202.5 < direction <= 247.5: self.set_direction(OutAngle.SharpLeft, OutType.ArrowOnly) elif 247.5 < direction <= 292.5: self.set_direction(OutAngle.Left, OutType.ArrowOnly) elif 292.5 < direction <= 337.5: self.set_direction(OutAngle.EasyLeft, OutType.ArrowOnly) self.clear() def send_hud(self, buf: list): print("buf", buf) n = len(buf) chars = [] crc = uint8(0xeb + n + n) chars.append(0x10) chars.append(0x7b) chars.append(n + 6) if n == 0xa: chars.append(0x10) chars.append(n) chars.append(0x00) chars.append(0x00) chars.append(0x00) chars.append(0x55) chars.append(0x15) for char in buf: crc = uint8(crc + char) chars.append(char) if char == 0x10: chars.append(0x10) chars.append((-crc) & 0xff) chars.append(0x10) chars.append(0x03) self.send_packet(chars) def send_packet(self, buff): encoded = [bytes(chr(char), 'raw_unicode_escape') for char in buff] if self.debug: print("raw", buff) print("enc", encoded) else: for char in buff: self.ser.write(bytes(chr(char), 'raw_unicode_escape')) time.sleep(0.2) print(self.ser.read_all()) if __name__ == '__main__': import sys device = sys.argv[1] if len(sys.argv) > 1 else None c = Controller(device)