|  |  |  | #!/usr/bin/env python3
 | 
					
						
							|  |  |  | import datetime
 | 
					
						
							|  |  |  | import os
 | 
					
						
							|  |  |  | import subprocess
 | 
					
						
							|  |  |  | import time
 | 
					
						
							|  |  |  | from typing import NoReturn
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from timezonefinder import TimezoneFinder
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import cereal.messaging as messaging
 | 
					
						
							|  |  |  | from openpilot.common.time import system_time_valid
 | 
					
						
							|  |  |  | from openpilot.common.params import Params
 | 
					
						
							|  |  |  | from openpilot.common.swaglog import cloudlog
 | 
					
						
							|  |  |  | from openpilot.system.hardware import AGNOS
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def set_timezone(timezone):
 | 
					
						
							|  |  |  |   valid_timezones = subprocess.check_output('timedatectl list-timezones', shell=True, encoding='utf8').strip().split('\n')
 | 
					
						
							|  |  |  |   if timezone not in valid_timezones:
 | 
					
						
							|  |  |  |     cloudlog.error(f"Timezone not supported {timezone}")
 | 
					
						
							|  |  |  |     return
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   cloudlog.debug(f"Setting timezone to {timezone}")
 | 
					
						
							|  |  |  |   try:
 | 
					
						
							|  |  |  |     if AGNOS:
 | 
					
						
							|  |  |  |       tzpath = os.path.join("/usr/share/zoneinfo/", timezone)
 | 
					
						
							|  |  |  |       subprocess.check_call(f'sudo su -c "ln -snf {tzpath} /data/etc/tmptime && \
 | 
					
						
							|  |  |  |                               mv /data/etc/tmptime /data/etc/localtime"', shell=True)
 | 
					
						
							|  |  |  |       subprocess.check_call(f'sudo su -c "echo \"{timezone}\" > /data/etc/timezone"', shell=True)
 | 
					
						
							|  |  |  |     else:
 | 
					
						
							|  |  |  |       subprocess.check_call(f'sudo timedatectl set-timezone {timezone}', shell=True)
 | 
					
						
							|  |  |  |   except subprocess.CalledProcessError:
 | 
					
						
							|  |  |  |     cloudlog.exception(f"Error setting timezone to {timezone}")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def set_time(new_time):
 | 
					
						
							|  |  |  |   diff = datetime.datetime.now() - new_time
 | 
					
						
							|  |  |  |   if diff < datetime.timedelta(seconds=10):
 | 
					
						
							|  |  |  |     cloudlog.debug(f"Time diff too small: {diff}")
 | 
					
						
							|  |  |  |     return
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   cloudlog.debug(f"Setting time to {new_time}")
 | 
					
						
							|  |  |  |   try:
 | 
					
						
							|  |  |  |     subprocess.run(f"TZ=UTC date -s '{new_time}'", shell=True, check=True)
 | 
					
						
							|  |  |  |   except subprocess.CalledProcessError:
 | 
					
						
							|  |  |  |     cloudlog.exception("timed.failed_setting_time")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def main() -> NoReturn:
 | 
					
						
							|  |  |  |   """
 | 
					
						
							|  |  |  |     timed has two responsibilities:
 | 
					
						
							|  |  |  |     - getting the current time
 | 
					
						
							|  |  |  |     - getting the current timezone
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     GPS directly gives time, and timezone is looked up from GPS position.
 | 
					
						
							|  |  |  |     AGNOS will also use NTP to update the time.
 | 
					
						
							|  |  |  |   """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   params = Params()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Restore timezone from param
 | 
					
						
							|  |  |  |   tz = params.get("Timezone", encoding='utf8')
 | 
					
						
							|  |  |  |   tf = TimezoneFinder()
 | 
					
						
							|  |  |  |   if tz is not None:
 | 
					
						
							|  |  |  |     cloudlog.debug("Restoring timezone from param")
 | 
					
						
							|  |  |  |     set_timezone(tz)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   pm = messaging.PubMaster(['clocks'])
 | 
					
						
							|  |  |  |   sm = messaging.SubMaster(['liveLocationKalman'])
 | 
					
						
							|  |  |  |   while True:
 | 
					
						
							|  |  |  |     sm.update(1000)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     msg = messaging.new_message('clocks')
 | 
					
						
							|  |  |  |     msg.valid = system_time_valid()
 | 
					
						
							|  |  |  |     msg.clocks.wallTimeNanos = time.time_ns()
 | 
					
						
							|  |  |  |     pm.send('clocks', msg)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     llk = sm['liveLocationKalman']
 | 
					
						
							|  |  |  |     if not llk.gpsOK or (time.monotonic() - sm.logMonoTime['liveLocationKalman']/1e9) > 0.2:
 | 
					
						
							|  |  |  |       continue
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # set time
 | 
					
						
							|  |  |  |     # TODO: account for unixTimesatmpMillis being a (usually short) time in the past
 | 
					
						
							|  |  |  |     gps_time = datetime.datetime.fromtimestamp(llk.unixTimestampMillis / 1000.)
 | 
					
						
							|  |  |  |     set_time(gps_time)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # set timezone
 | 
					
						
							|  |  |  |     pos = llk.positionGeodetic.value
 | 
					
						
							|  |  |  |     if len(pos) == 3:
 | 
					
						
							|  |  |  |       gps_timezone = tf.timezone_at(lat=pos[0], lng=pos[1])
 | 
					
						
							|  |  |  |       if gps_timezone is None:
 | 
					
						
							|  |  |  |         cloudlog.critical(f"No timezone found based on {pos=}")
 | 
					
						
							|  |  |  |       else:
 | 
					
						
							|  |  |  |         set_timezone(gps_timezone)
 | 
					
						
							|  |  |  |         params.put_nonblocking("Timezone", gps_timezone)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     time.sleep(10)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__":
 | 
					
						
							|  |  |  |   main()
 |