timed: always pull time and timezone from GPS (#31112)
	
		
	
				
					
				
			* timed * rm timezoned * just gps * ok * little more * fix * datetime * sleep a little --------- Co-authored-by: Comma Device <device@comma.ai>pull/214/head
							parent
							
								
									389b8ca30d
								
							
						
					
					
						commit
						827aa2e4fa
					
				
				 6 changed files with 97 additions and 74 deletions
			
			
		@ -0,0 +1,94 @@ | 
				
			|||||||
 | 
					#!/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.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() | 
				
			||||||
 | 
					  tf = TimezoneFinder() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # 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) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  sm = messaging.SubMaster(['liveLocationKalman']) | 
				
			||||||
 | 
					  while True: | 
				
			||||||
 | 
					    sm.update(1000) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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() | 
				
			||||||
@ -1,70 +0,0 @@ | 
				
			|||||||
#!/usr/bin/env python3 | 
					 | 
				
			||||||
import json | 
					 | 
				
			||||||
import os | 
					 | 
				
			||||||
import time | 
					 | 
				
			||||||
import subprocess | 
					 | 
				
			||||||
from typing import NoReturn | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from timezonefinder import TimezoneFinder | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from openpilot.common.params import Params | 
					 | 
				
			||||||
from openpilot.system.hardware import AGNOS | 
					 | 
				
			||||||
from openpilot.common.swaglog import cloudlog | 
					 | 
				
			||||||
from openpilot.system.version import get_version | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
REQUEST_HEADERS = {'User-Agent': "openpilot-" + get_version()} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def set_timezone(valid_timezones, timezone): | 
					 | 
				
			||||||
  if timezone not in valid_timezones: | 
					 | 
				
			||||||
    cloudlog.error(f"Timezone not supported {timezone}") | 
					 | 
				
			||||||
    return | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  cloudlog.info(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 main() -> NoReturn: | 
					 | 
				
			||||||
  params = Params() | 
					 | 
				
			||||||
  tf = TimezoneFinder() | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  # Get allowed timezones | 
					 | 
				
			||||||
  valid_timezones = subprocess.check_output('timedatectl list-timezones', shell=True, encoding='utf8').strip().split('\n') | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  timezone = params.get("Timezone", encoding='utf8') | 
					 | 
				
			||||||
  if timezone is not None: | 
					 | 
				
			||||||
    cloudlog.debug("Setting timezone based on param") | 
					 | 
				
			||||||
    set_timezone(valid_timezones, timezone) | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  while True: | 
					 | 
				
			||||||
    time.sleep(60) | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    location = params.get("LastGPSPosition", encoding='utf8') | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Find timezone by reverse geocoding the last known gps location | 
					 | 
				
			||||||
    if location is not None: | 
					 | 
				
			||||||
      cloudlog.debug("Setting timezone based on GPS location") | 
					 | 
				
			||||||
      try: | 
					 | 
				
			||||||
        location = json.loads(location) | 
					 | 
				
			||||||
      except Exception: | 
					 | 
				
			||||||
        cloudlog.exception("Error parsing location") | 
					 | 
				
			||||||
        continue | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      timezone = tf.timezone_at(lng=location['longitude'], lat=location['latitude']) | 
					 | 
				
			||||||
      if timezone is None: | 
					 | 
				
			||||||
        cloudlog.error(f"No timezone found based on location, {location}") | 
					 | 
				
			||||||
        continue | 
					 | 
				
			||||||
      set_timezone(valid_timezones, timezone) | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__": | 
					 | 
				
			||||||
  main() | 
					 | 
				
			||||||
					Loading…
					
					
				
		Reference in new issue