def start(self): LOGGER.info('Started Sun Position') if not 'longitude' in self.polyConfig[ 'customParams'] or not 'latitude' in self.polyConfig[ 'customParams']: LOGGER.error( 'Please specify latitude and longitude configuration parameters' ) else: self.tz = get_localzone() self.today = datetime.date.today() self.location = Location() self.location.timezone = str(self.tz) self.location.longitude = float( self.polyConfig['customParams']['longitude']) self.location.latitude = float( self.polyConfig['customParams']['latitude']) if 'elevation' in self.polyConfig['customParams']: self.location.elevation = int( self.polyConfig['customParams']['elevation']) self.sunrise = self.location.sunrise() self.sunset = self.location.sunset() ts_now = datetime.datetime.now(self.tz) if self.sunrise < ts_now < self.sunset: self.sun_above_horizon = True self.updateInfo()
def test_BadTzinfo(self): loc = Location() loc._location_info = dataclasses.replace(loc._location_info, timezone="Bad/Timezone") with pytest.raises(ValueError): loc.tzinfo
def test_Dawn_NeverReachesDepression(): d = datetime.date(2016, 5, 29) with pytest.raises(ValueError): loc = Location( LocationInfo("Ghent", "Belgium", "Europe/Brussels", "51°3'N", "3°44'W")) loc.solar_depression = 18 loc.dawn(date=d, local=True)
def test_SolarDepression(self): c = Location( LocationInfo("Heidelberg", "Germany", "Europe/Berlin", 49.412, -8.71)) c.solar_depression = "nautical" assert c.solar_depression == 12 c.solar_depression = 18 assert c.solar_depression == 18
def __init__(self, config): super(BaseAction, self).__init__(config) self._latitude = self.config['latitude'] self._longitude = self.config['longitude'] self._timezone = self.config['timezone'] location = Location(('name', 'region', float(self._latitude), float(self._longitude), self._timezone, 0)) self.sun = location.sun()
def init_sun(self): latitude = self.AD.latitude longitude = self.AD.longitude if latitude < -90 or latitude > 90: raise ValueError("Latitude needs to be -90 .. 90") if longitude < -180 or longitude > 180: raise ValueError("Longitude needs to be -180 .. 180") self.location = Location( LocationInfo("", "", self.AD.tz.zone, latitude, longitude))
def _get_astral_location(info): try: from astral import LocationInfo from astral.location import Location latitude, longitude, timezone, elevation = info info = ("", "", timezone, latitude, longitude) return Location(LocationInfo(*info)), elevation except ImportError: from astral import Location info = ("", "", *info) return Location(info), None
def main(): loc = get_loc_from_ip() loc = json.loads(loc.text) # loc['latitude'], loc['longitude'] = (float(x) for x in loc['loc'].strip().split(',')) # loc['time_zone'] = tzlocal.get_localzone().zone # print(loc['ip']) try: location = Location() location.name = loc['country'] location.region = loc['country_iso'] location.latitude = loc['latitude'] location.longitude = loc['longitude'] location.timezone = loc['time_zone'] except ValueError as e: logger.error(str(e)) return sunrise = location.sun()['sunrise'].replace(second=0) + timedelta( minutes=0) sunset = location.sun()['sunset'].replace(second=0) + timedelta(minutes=0) today = datetime.now().astimezone( pytz.timezone("Asia/Yerevan")) + timedelta(minutes=0) dawn = sunrise.astimezone( pytz.timezone("Asia/Yerevan")).strftime('%H:%M:%S') dusk = sunset.astimezone( pytz.timezone("Asia/Yerevan")).strftime('%H:%M:%S') now = today.strftime('%H:%M:%S') print(f'Dawn: {dawn}') print(f'Dusk: {dusk}') print(f'Now: {now}') print( f"You are in {location.name} and the timezone is {location.timezone}") if now < dawn: print("oh still dark") os.system( "gsettings set org.gnome.desktop.interface gtk-theme 'Mc-OS-CTLina-Gnome-Dark-1.3.2'" ) elif dawn < now < dusk: print("it a brand new day") os.system( "gsettings set org.gnome.desktop.interface gtk-theme 'McOS-CTLina-Gnome-1.3.2'" ) else: print("oh is dark") os.system( "gsettings set org.gnome.desktop.interface gtk-theme 'Mc-OS-CTLina-Gnome-Dark-1.3.2'" ) return sunrise.astimezone( pytz.timezone("Asia/Yerevan")), sunset.astimezone( pytz.timezone("Asia/Yerevan"))
def test_LocationEquality_NotALocation(self, london_info): location = Location(london_info) class NotALocation: _location_info = london_info assert NotALocation() != location
def __init__( self, client: InfluxDBClient, location_info: LocationInfo, endpoints: List[str], tz: Any, bucket_name: str, ignore_sundown: bool = False, data_collection_interval=60, ) -> None: self.client = client self.write_api = client.write_api(write_options=SYNCHRONOUS) self.location = Location(location_info) self.endpoints = endpoints self.tz = tz self.data: Dict[Any, Any] = {} self.BUCKET_NAME = bucket_name self.IGNORE_SUN_DOWN = ignore_sundown self.DATA_COLLECTION_INTERVAL = data_collection_interval
def day_length(date: datetime, coordinate: Coordinate): """ :param date: A date or datetime object of the day in question. :param coordinate: the position on the globe :return: a floating point duration of the day at """ try: sunrise, sunset = sun.daylight(Observer(coordinate.lat, coordinate.lng), date) except ValueError: # check if sun in above or below horizon if Location(LocationInfo(latitude=coordinate.lat, longitude=coordinate.lng)).solar_elevation(date) > 0: return 24 else: return 0 return (sunset - sunrise) / datetime.timedelta(hours=1)
def get_astral_location(self) -> Location: """ ASTRAL_LOCATION is a comma separated string in the format of Astral's LocationInfo name,region,timezone,latitude,longitude eg: 'London,England,Europe/London,51°30'N,00°07'W' name and country are purely for labelling, can anything lat/long can be either in degrees and minutes or as a float (positive = North/East) elevation is in metres :return: """ location_tuple = os.environ["ASTRAL_LOCATION"].split(",") location_info = LocationInfo(*location_tuple) location = Location(location_info) return location
def get_astral_location( hass: HomeAssistant, ) -> tuple[astral.location.Location, astral.Elevation]: """Get an astral location for the current Home Assistant configuration.""" from astral import LocationInfo # pylint: disable=import-outside-toplevel from astral.location import Location # pylint: disable=import-outside-toplevel latitude = hass.config.latitude longitude = hass.config.longitude timezone = str(hass.config.time_zone) elevation = hass.config.elevation info = ("", "", timezone, latitude, longitude) # Cache astral locations so they aren't recreated with the same args if DATA_LOCATION_CACHE not in hass.data: hass.data[DATA_LOCATION_CACHE] = {} if info not in hass.data[DATA_LOCATION_CACHE]: hass.data[DATA_LOCATION_CACHE][info] = Location(LocationInfo(*info)) return hass.data[DATA_LOCATION_CACHE][info], elevation
def resh_hours(local_name, region, latitude, longitude, time_zone, elevation, resh_date=date.today()): """ :param local_name: str :param region: str :param latitude: str :param longitude: str :param time_zone: str :param elevation: int :param resh_date: date :return: dict """ city = LocationInfo(local_name, region, time_zone, latitude, longitude) location = Location(city) return {"Nascer do sol": location.sunrise(resh_date, observer_elevation=elevation), "Meio-dia solar": location.noon(resh_date), "Pôr do sol": location.sunset(resh_date, observer_elevation=elevation), "Meia-noite solar": location.midnight(resh_date + timedelta(days=1))}
class Controller(polyinterface.Controller): def __init__(self, polyglot): super().__init__(polyglot) self.name = 'Sun Position' self.address = 'sunctrl' self.primary = self.address self.location = None self.tz = None self.today = None self.sunrise = None self.sunset = None self.sun_above_horizon = False def start(self): LOGGER.info('Started Sun Position') if not 'longitude' in self.polyConfig[ 'customParams'] or not 'latitude' in self.polyConfig[ 'customParams']: LOGGER.error( 'Please specify latitude and longitude configuration parameters' ) else: self.tz = get_localzone() self.today = datetime.date.today() self.location = Location() self.location.timezone = str(self.tz) self.location.longitude = float( self.polyConfig['customParams']['longitude']) self.location.latitude = float( self.polyConfig['customParams']['latitude']) if 'elevation' in self.polyConfig['customParams']: self.location.elevation = int( self.polyConfig['customParams']['elevation']) self.sunrise = self.location.sunrise() self.sunset = self.location.sunset() ts_now = datetime.datetime.now(self.tz) if self.sunrise < ts_now < self.sunset: self.sun_above_horizon = True self.updateInfo() def stop(self): LOGGER.info('Sun Position is stopping') def shortPoll(self): self.updateInfo() def updateInfo(self): if self.location is None: return ts_now = datetime.datetime.now(self.tz) self.setDriver('GV0', round(self.location.solar_azimuth(), 2)) self.setDriver('GV1', round(self.location.solar_elevation(), 2)) self.setDriver('GV2', round(self.location.solar_zenith(ts_now), 2)) self.setDriver('GV3', round(self.location.moon_phase(), 2)) date_now = datetime.date.today() if date_now != self.today: LOGGER.debug('It\'s a new day! Calculating sunrise and sunset...') self.today = date_now self.sunrise = self.location.sunrise() self.sunset = self.location.sunset() ts_now = datetime.datetime.now(self.tz) if self.sunrise < ts_now < self.sunset: if not self.sun_above_horizon: LOGGER.info('Sunrise') self.reportCmd('DOF') self.sun_above_horizon = True else: if self.sun_above_horizon: LOGGER.info('Sunset') self.reportCmd('DON') self.sun_above_horizon = False def query(self): for node in self.nodes: self.nodes[node].reportDrivers() id = 'SUNCTRL' commands = {'QUERY': query} drivers = [{ 'driver': 'ST', 'value': 1, 'uom': 2 }, { 'driver': 'GV0', 'value': 0, 'uom': 14 }, { 'driver': 'GV1', 'value': 0, 'uom': 14 }, { 'driver': 'GV2', 'value': 0, 'uom': 14 }, { 'driver': 'GV3', 'value': 0, 'uom': 56 }]
def riyadh(riyadh_info) -> Location: return Location(riyadh_info)
def new_delhi(new_delhi_info) -> Location: return Location(new_delhi_info)
def london(london_info) -> Location: return Location(london_info)
def run(): defaults = {'log': "info"} # Parse any config file specification. We make this parser with add_help=False so # that it doesn't parse -h and print help. conf_parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, add_help=False) conf_parser.add_argument("--config", help="Specify config file", metavar='FILE') args, remaining_argv = conf_parser.parse_known_args() # Read configuration file and add it to the defaults hash. if args.config: config = ConfigParser() config.read(args.config) if "Defaults" in config: defaults.update(dict(config.items("Defaults"))) else: logging.error("Bad config file, missing Defaults section") sys.exit(1) # Parse rest of arguments parser = argparse.ArgumentParser( description=__doc__, parents=[conf_parser], ) parser.set_defaults(**defaults) parser.add_argument("--gw-station-id", help="GoodWe station ID", metavar='ID') parser.add_argument("--gw-account", help="GoodWe account", metavar='ACCOUNT') parser.add_argument("--gw-password", help="GoodWe password", metavar='PASSWORD') parser.add_argument("--mqtt-host", help="MQTT hostname", metavar='MQTT_HOST') parser.add_argument("--mqtt-port", help="MQTT port", metavar='MQTT_USER') parser.add_argument("--mqtt-user", help="MQTT username", metavar='MQTT_USER') parser.add_argument("--mqtt-password", help="MQTT password", metavar='MQTT_PASS') parser.add_argument("--mqtt-topic", help="MQTT topic", metavar='MQTT_TOPIC') parser.add_argument("--pvo-system-id", help="PVOutput system ID", metavar='ID') parser.add_argument("--pvo-api-key", help="PVOutput API key", metavar='KEY') parser.add_argument("--pvo-interval", help="PVOutput interval in minutes", type=int, choices=[5, 10, 15]) parser.add_argument("--telegram-token", help="Telegram bot token", metavar='TELEGRAM_TOKEN') parser.add_argument("--telegram-chatid", help="Telegram chat id", metavar='TELEGRAM_CHATID') parser.add_argument("--darksky-api-key", help="Dark Sky Weather API key") parser.add_argument("--openweather-api-key", help="Open Weather API key") parser.add_argument("--log", help="Set log level (default info)", choices=['debug', 'info', 'warning', 'critical']) parser.add_argument("--date", help="Copy all readings (max 14/90 days ago)", metavar='YYYY-MM-DD') parser.add_argument( "--upload-csv", help="Upload all readings from csv file (max 14/90 days ago)") parser.add_argument("--pv-voltage", help="Send pv voltage instead of grid voltage", action='store_true') parser.add_argument("--skip-offline", help="Skip uploads when inverter is offline", action='store_true') parser.add_argument( "--city", help="Sets timezone and skip uploads from dusk till dawn") parser.add_argument( '--csv', help= "Append readings to a Excel compatible CSV file, DATE in the name will be replaced by the current date" ) parser.add_argument('--version', action='version', version='%(prog)s ' + __version__) args = parser.parse_args() # Configure the logging numeric_level = getattr(logging, args.log.upper(), None) if not isinstance(numeric_level, int): raise ValueError('Invalid log level: %s' % loglevel) logging.basicConfig(format='%(levelname)-8s %(message)s', level=numeric_level) logging.debug("gw2pvo version " + __version__) if isinstance(args.skip_offline, str): args.skip_offline = args.skip_offline.lower() in [ 'true', 'yes', 'on', '1' ] if args.upload_csv is None: if args.gw_station_id is None or args.gw_account is None or args.gw_password is None: if args.mqtt_host is None or args.mqtt_topic is None: logging.error( "Missing configuation. Either MQTT configuration or Goodwe (SEMS Portal) credentails need to be provided.\nPlease add either --gw-station-id, --gw-account and --gw-password OR add --mqtt-host and --mqtt-topic (at a minimum). Alternatively, one of these options can also be configured in a configuration file." ) sys.exit(1) if args.city: city = Location(lookup(args.city, database())) os.environ['TZ'] = city.timezone time.tzset() else: city = None logging.debug("Timezone {}".format(datetime.now().astimezone().tzinfo)) # Check if we want to copy old data if args.date: try: copy(args) except KeyboardInterrupt: sys.exit(1) except Exception as exp: logging.error(exp) sys.exit() elif args.upload_csv: try: copy_csv(args) except Exception as exp: logging.error(exp) sys.exit(1) startTime = datetime.now() while True: currentTime = datetime.now() try: run_once(args, city) except KeyboardInterrupt: sys.exit(1) except Exception as exp: errorMsg = ("Failed to publish data PVOutput - " + str(exp)) logging.error(str(currentTime) + " - " + str(errorMsg)) try: telegram_notify(args.telegram_token, args.telegram_chatid, errorMsg) except Exception as exp: logging.error( str(currentTime) + " - Failed to send telegram notification - " + str(exp)) if args.pvo_interval is None: break interval = args.pvo_interval * 60 time.sleep(interval - (datetime.now() - startTime).seconds % interval)
def test_TimezoneName(self): """Test the default timezone and that the timezone is changeable""" c = Location() assert c.timezone == "Europe/London" c.name = "Asia/Riyadh" assert c.name == "Asia/Riyadh"
class Scheduler: def __init__(self, ad: AppDaemon): self.AD = ad self.logger = ad.logging.get_child("_scheduler") self.error = ad.logging.get_error() self.diag = ad.logging.get_diag() self.last_fired = None self.sleep_task = None self.active = False self.location = None self.schedule = {} self.now = pytz.utc.localize(datetime.datetime.utcnow()) # # If we were waiting for a timezone from metadata, we have it now. # tz = pytz.timezone(self.AD.time_zone) self.AD.tz = tz self.AD.logging.set_tz(tz) self.stopping = False self.realtime = True self.set_start_time() if self.AD.endtime is not None: unaware_end = None try: unaware_end = datetime.datetime.strptime( self.AD.endtime, "%Y-%m-%d %H:%M:%S") except ValueError: try: unaware_end = datetime.datetime.strptime( self.AD.endtime, "%Y-%m-%d#%H:%M:%S") except ValueError: pass if unaware_end is None: raise ValueError("Invalid end time for time travel") aware_end = self.AD.tz.localize(unaware_end) self.endtime = aware_end.astimezone(pytz.utc) else: self.endtime = None # Setup sun self.init_sun() def set_start_time(self): tt = False unaware_now = None if self.AD.starttime is not None: tt = True try: unaware_now = datetime.datetime.strptime( self.AD.starttime, "%Y-%m-%d %H:%M:%S") except ValueError: # Support "#" as date and time separator as well try: unaware_now = datetime.datetime.strptime( self.AD.starttime, "%Y-%m-%d#%H:%M:%S") except ValueError: # Catching this allows us to raise a single exception and avoid a nested exception pass if unaware_now is None: raise ValueError("Invalid start time for time travel") aware_now = self.AD.tz.localize(unaware_now) self.now = aware_now.astimezone(pytz.utc) else: self.now = pytz.utc.localize(datetime.datetime.utcnow()) if self.AD.timewarp != 1: tt = True return tt def stop(self): self.logger.debug("stop() called for scheduler") self.stopping = True async def cancel_timer(self, name, handle): executed = False self.logger.debug("Canceling timer for %s", name) if self.timer_running(name, handle): del self.schedule[name][handle] await self.AD.state.remove_entity( "admin", "scheduler_callback.{}".format(handle)) executed = True if name in self.schedule and self.schedule[name] == {}: del self.schedule[name] if not executed: self.logger.warning( "Invalid callback handle '{}' in cancel_timer() from app {}". format(handle, name)) return executed def timer_running(self, name, handle): """Check if the handler is valid by ensuring the timer is still running""" if name in self.schedule and handle in self.schedule[name]: return True return False # noinspection PyBroadException async def exec_schedule(self, name, args, uuid_): try: # Call function if "__entity" in args["kwargs"]: # # it's a "duration" entry # # first remove the duration parameter if args["kwargs"].get("__duration"): del args["kwargs"]["__duration"] executed = await self.AD.threading.dispatch_worker( name, { "id": uuid_, "name": name, "objectid": self.AD.app_management.objects[name]["id"], "type": "state", "function": args["callback"], "attribute": args["kwargs"]["__attribute"], "entity": args["kwargs"]["__entity"], "new_state": args["kwargs"]["__new_state"], "old_state": args["kwargs"]["__old_state"], "pin_app": args["pin_app"], "pin_thread": args["pin_thread"], "kwargs": args["kwargs"], }, ) if executed is True: remove = args["kwargs"].get("oneshot", False) if remove is True: await self.AD.state.cancel_state_callback( args["kwargs"]["__handle"], name) if "__timeout" in args["kwargs"] and self.timer_running( name, args["kwargs"]["__timeout"] ): # meaning there is a timeout for this callback await self.cancel_timer( name, args["kwargs"]["__timeout"] ) # cancel it as no more needed elif "__state_handle" in args["kwargs"]: # # It's a state timeout entry - just delete the callback # await self.AD.state.cancel_state_callback( args["kwargs"]["__state_handle"], name) elif "__event_handle" in args["kwargs"]: # # It's an event timeout entry - just delete the callback # await self.AD.events.cancel_event_callback( name, args["kwargs"]["__event_handle"]) elif "__log_handle" in args["kwargs"]: # # It's a log timeout entry - just delete the callback # await self.AD.logging.cancel_log_callback( name, args["kwargs"]["__log_handle"]) else: # # A regular callback # await self.AD.threading.dispatch_worker( name, { "id": uuid_, "name": name, "objectid": self.AD.app_management.objects[name]["id"], "type": "scheduler", "function": args["callback"], "pin_app": args["pin_app"], "pin_thread": args["pin_thread"], "kwargs": args["kwargs"], }, ) # If it is a repeating entry, rewrite with new timestamp if args["repeat"]: if args["type"] == "next_rising" or args[ "type"] == "next_setting": c_offset = self.get_offset(args) args["timestamp"] = self.sun(args["type"], c_offset) args["offset"] = c_offset else: # Not sunrise or sunset so just increment # the timestamp with the repeat interval args["basetime"] += timedelta(seconds=args["interval"]) args["timestamp"] = args["basetime"] + timedelta( seconds=self.get_offset(args)) # Update entity await self.AD.state.set_state( "_scheduler", "admin", "scheduler_callback.{}".format(uuid_), execution_time=utils.dt_to_str( args["timestamp"].replace(microsecond=0), self.AD.tz), ) else: # Otherwise just delete await self.AD.state.remove_entity( "admin", "scheduler_callback.{}".format(uuid_)) del self.schedule[name][uuid_] except Exception: error_logger = logging.getLogger("Error.{}".format(name)) error_logger.warning("-" * 60) error_logger.warning( "Unexpected error during exec_schedule() for App: %s", name) error_logger.warning("Args: %s", args) error_logger.warning("-" * 60) error_logger.warning(traceback.format_exc()) error_logger.warning("-" * 60) if self.AD.logging.separate_error_log() is True: self.logger.warning("Logged an error to %s", self.AD.logging.get_filename("error_log")) error_logger.warning("Scheduler entry has been deleted") error_logger.warning("-" * 60) await self.AD.state.remove_entity( "admin", "scheduler_callback.{}".format(uuid_)) del self.schedule[name][uuid_] def init_sun(self): latitude = self.AD.latitude longitude = self.AD.longitude if latitude < -90 or latitude > 90: raise ValueError("Latitude needs to be -90 .. 90") if longitude < -180 or longitude > 180: raise ValueError("Longitude needs to be -180 .. 180") self.location = Location( LocationInfo("", "", self.AD.tz.zone, latitude, longitude)) def sun(self, type: str, secs_offset: int): return self.get_next_sun_event( type, secs_offset) + datetime.timedelta(seconds=secs_offset) def get_next_sun_event(self, type: str, day_offset: int): if type == "next_rising": return self.next_sunrise(day_offset) else: return self.next_sunset(day_offset) def next_sunrise(self, offset: int = 0): day_offset = 0 while True: try: candidate_date = ( self.now + datetime.timedelta(days=day_offset)).astimezone( self.AD.tz).date() next_rising_dt = self.location.sunrise( date=candidate_date, local=False, observer_elevation=self.AD.elevation) if next_rising_dt + datetime.timedelta(seconds=offset) > ( self.now + datetime.timedelta(seconds=1)): break except ValueError: pass day_offset += 1 return next_rising_dt def next_sunset(self, offset: int = 0): day_offset = 0 while True: try: candidate_date = ( self.now + datetime.timedelta(days=day_offset)).astimezone( self.AD.tz).date() next_setting_dt = self.location.sunset( date=candidate_date, local=False, observer_elevation=self.AD.elevation) if next_setting_dt + datetime.timedelta(seconds=offset) > ( self.now + datetime.timedelta(seconds=1)): break except ValueError: pass day_offset += 1 return next_setting_dt @staticmethod def get_offset(kwargs: dict): if "offset" in kwargs["kwargs"]: if "random_start" in kwargs["kwargs"] or "random_end" in kwargs[ "kwargs"]: raise ValueError( "Can't specify offset as well as 'random_start' or " "'random_end' in 'run_at_sunrise()' or 'run_at_sunset()'") else: offset = kwargs["kwargs"]["offset"] else: rbefore = kwargs["kwargs"].get("random_start", 0) rafter = kwargs["kwargs"].get("random_end", 0) offset = random.randint(rbefore, rafter) # self.logger.debug("get_offset(): offset = %s", offset) return offset async def insert_schedule(self, name, aware_dt, callback, repeat, type_, **kwargs): # aware_dt will include a timezone of some sort - convert to utc timezone utc = aware_dt.astimezone(pytz.utc) # Round to nearest second utc = self.my_dt_round(utc, base=1) if "pin" in kwargs: pin_app = kwargs["pin"] else: pin_app = self.AD.app_management.objects[name]["pin_app"] if "pin_thread" in kwargs: pin_thread = kwargs["pin_thread"] pin_app = True else: pin_thread = self.AD.app_management.objects[name]["pin_thread"] if name not in self.schedule: self.schedule[name] = {} handle = uuid.uuid4().hex c_offset = self.get_offset({"kwargs": kwargs}) ts = utc + timedelta(seconds=c_offset) interval = kwargs.get("interval", 0) self.schedule[name][handle] = { "name": name, "id": self.AD.app_management.objects[name]["id"], "callback": callback, "timestamp": ts, "interval": interval, "basetime": utc, "repeat": repeat, "offset": c_offset, "type": type_, "pin_app": pin_app, "pin_thread": pin_thread, "kwargs": kwargs, } if callback is None: function_name = "cancel_callback" else: function_name = callback.__name__ await self.AD.state.add_entity( "admin", "scheduler_callback.{}".format(handle), "active", { "app": name, "execution_time": utils.dt_to_str(ts.replace(microsecond=0), self.AD.tz), "repeat": str(datetime.timedelta(seconds=interval)), "function": function_name, "pinned": pin_app, "pinned_thread": pin_thread, "fired": 0, "executed": 0, "kwargs": kwargs, }, ) # verbose_log(conf.logger, "INFO", conf.schedule[name][handle]) if self.active is True: await self.kick() return handle async def terminate_app(self, name): if name in self.schedule: for id in self.schedule[name]: await self.AD.state.remove_entity( "admin", "scheduler_callback.{}".format(id)) del self.schedule[name] def is_realtime(self): return self.realtime # # Timer # def get_next_entries(self): next_exec = datetime.datetime.now(pytz.utc).replace( year=datetime.MAXYEAR, month=12, day=31) for name in self.schedule.keys(): for entry in self.schedule[name].keys(): if self.schedule[name][entry]["timestamp"] < next_exec: next_exec = self.schedule[name][entry]["timestamp"] next_entries = [] for name in self.schedule.keys(): for entry in self.schedule[name].keys(): if self.schedule[name][entry]["timestamp"] == next_exec: next_entries.append({ "name": name, "uuid": entry, "timestamp": self.schedule[name][entry]["timestamp"] }) return next_entries async def process_dst(self, old, new): # # Rewrite timestamps to new local time # offset = old - new self.logger.debug("Process_dst()") self.logger.debug("offset %s", offset) for app in self.schedule: for entry in self.schedule[app]: args = self.schedule[app][entry] # Sunrise and sunset will already be correct. Anything else needs to be reset to a new local time self.logger.debug("Before rewrite: %s", args) if args["type"] != "next_rising" and args[ "type"] != "next_setting": # If our interval is less than the jump don't rewrite the timestamp if float(args["interval"]) > abs(offset.total_seconds()): args["timestamp"] += offset args["basetime"] += offset self.logger.debug("After rewrite: %s", args) def get_next_dst_offset(self, base, limit): # # I can't believe there isn't a better way to find the next DST transition but ... # We know the lower and upper bounds of DST so do a search to find the actual transition time # I don't want to rely on heuristics such as "it occurs at 2am" because I don't know if that holds # true for every timezone. With this method, as long as pytz's dst() function is correct, this should work # # TODO: Convert this to some sort of binary search for efficiency # TODO: This really should support sub 1 second periods better self.logger.debug("get_next_dst_offset() base=%s limit=%s", base, limit) current = base.astimezone(self.AD.tz).dst() self.logger.debug("current=%s", current) for offset in range(1, int(limit) + 1): candidate = (base + timedelta(seconds=offset)).astimezone( self.AD.tz) # print(candidate) if candidate.dst() != current: return offset return limit async def loop(self): # noqa: C901 self.active = True self.logger.debug("Starting scheduler loop()") self.AD.booted = await self.get_now_naive() tt = self.set_start_time() self.last_fired = pytz.utc.localize(datetime.datetime.utcnow()) if tt is True: self.realtime = False self.logger.info("Starting time travel ...") self.logger.info("Setting clocks to %s", await self.get_now_naive()) if self.AD.timewarp == 0: self.logger.info("Time displacement factor infinite") else: self.logger.info("Time displacement factor %s", self.AD.timewarp) else: self.logger.info("Scheduler running in realtime") next_entries = [] result = False idle_time = 1 delay = 0 old_dst_offset = (await self.get_now()).astimezone(self.AD.tz).dst() while not self.stopping: try: if self.endtime is not None and self.now >= self.endtime: self.logger.info("End time reached, exiting") if self.AD.stop_function is not None: self.AD.stop_function() else: self.stop() now = pytz.utc.localize(datetime.datetime.utcnow()) if self.realtime is True: self.now = now else: if result is True: # We got kicked so lets figure out the elapsed pseudo time delta = (now - self.last_fired ).total_seconds() * self.AD.timewarp else: if len(next_entries) > 0: # Time is progressing infinitely fast and it's already time for our next callback delta = delay else: # No kick, no scheduler expiry ... delta = idle_time self.now = self.now + timedelta(seconds=delta) self.last_fired = pytz.utc.localize(datetime.datetime.utcnow()) self.logger.debug("self.now = %s", self.now) # # Now we're awake and know what time it is # dst_offset = (await self.get_now()).astimezone(self.AD.tz).dst() self.logger.debug( "local now=%s old_dst_offset=%s new_dst_offset=%s", self.now.astimezone(self.AD.tz), old_dst_offset, dst_offset, ) if old_dst_offset != dst_offset: # # DST began or ended, we need to go fix any existing scheduler entries to match the new local time # self.logger.info( "Daylight Savings Time transition detected - rewriting events to new local time" ) await self.process_dst(old_dst_offset, dst_offset) # # Re calculate next entries # next_entries = self.get_next_entries() old_dst_offset = dst_offset # # OK, lets fire the entries # for entry in next_entries: # Check timestamps as we might have been interrupted to add a callback if entry["timestamp"] <= self.now: name = entry["name"] uuid_ = entry["uuid"] # Things may have changed since we last woke up # so check our callbacks are still valid before we execute them if name in self.schedule and uuid_ in self.schedule[ name]: args = self.schedule[name][uuid_] self.logger.debug("Executing: %s", args) await self.exec_schedule(name, args, uuid_) else: break for k, v in list(self.schedule.items()): if v == {}: del self.schedule[k] next_entries = self.get_next_entries() self.logger.debug("Next entries: %s", next_entries) if len(next_entries) > 0: delay = (next_entries[0]["timestamp"] - self.now).total_seconds() else: # Nothing to do, lets wait for a while, we will get woken up if anything new comes along delay = idle_time # Initially we don't want to skip over any events that haven't had a chance to be registered yet, but now # we can loosen up a little idle_time = 60 # # We are about to go to sleep, but we need to ensure we don't miss a DST transition or we will # sleep in and potentially miss an event that should happen earlier than expected due to the time change # next = self.now + timedelta(seconds=delay) self.logger.debug("next event=%s", next) if await self.is_dst() != await self.is_dst(next): # # Reset delay to wake up at the DST change so we can re-jig everything # delay = self.get_next_dst_offset(self.now, delay) self.logger.debug( "DST transition before next event: %s %s", await self.is_dst(), await self.is_dst(next)) self.logger.debug("Delay = %s seconds", delay) if delay > 0 and self.AD.timewarp > 0: # # Sleep until the next event # result = await self.sleep(delay / self.AD.timewarp) self.logger.debug("result = %s", result) else: # Not sleeping but lets be fair to the rest of AD await asyncio.sleep(0) except Exception: self.logger.warning("-" * 60) self.logger.warning("Unexpected error in scheduler loop") self.logger.warning("-" * 60) self.logger.warning(traceback.format_exc()) self.logger.warning("-" * 60) # Prevent spamming of the logs await self.sleep(1) async def sleep(self, delay): coro = asyncio.sleep(delay) self.sleep_task = asyncio.ensure_future(coro) try: await self.sleep_task self.sleep_task = None return False except asyncio.CancelledError: return True async def kick(self): while self.sleep_task is None: await asyncio.sleep(1) self.sleep_task.cancel() # # App API Calls # async def sun_up(self): return self.next_sunrise() > self.next_sunset() async def sun_down(self): return self.next_sunrise() < self.next_sunset() async def info_timer(self, handle, name): if self.timer_running(name, handle): callback = self.schedule[name][handle] return ( self.make_naive(callback["timestamp"]), callback["interval"], self.sanitize_timer_kwargs( self.AD.app_management.objects[name]["object"], callback["kwargs"]), ) else: self.logger.warning("Invalid timer handle given as: %s", handle) return None async def get_scheduler_entries(self): schedule = {} for name in self.schedule.keys(): schedule[name] = {} for entry in sorted( self.schedule[name].keys(), key=lambda uuid_: self.schedule[name][uuid_]["timestamp"], ): schedule[name][str(entry)] = {} schedule[name][str(entry)]["timestamp"] = str( self.AD.sched.make_naive( self.schedule[name][entry]["timestamp"])) schedule[name][str( entry)]["type"] = self.schedule[name][entry]["type"] schedule[name][str( entry)]["name"] = self.schedule[name][entry]["name"] schedule[name][str(entry)]["basetime"] = str( self.AD.sched.make_naive( self.schedule[name][entry]["basetime"])) schedule[name][str( entry)]["repeat"] = self.schedule[name][entry]["repeat"] if self.schedule[name][entry]["type"] == "next_rising": schedule[name][str( entry)]["interval"] = "sunrise:{}".format( utils.format_seconds( self.schedule[name][entry]["offset"])) elif self.schedule[name][entry]["type"] == "next_setting": schedule[name][str( entry)]["interval"] = "sunset:{}".format( utils.format_seconds( self.schedule[name][entry]["offset"])) elif self.schedule[name][entry]["repeat"] is True: schedule[name][str( entry)]["interval"] = utils.format_seconds( self.schedule[name][entry]["interval"]) else: schedule[name][str(entry)]["interval"] = "None" schedule[name][str( entry)]["offset"] = self.schedule[name][entry]["offset"] schedule[name][str(entry)]["kwargs"] = "" for kwarg in self.schedule[name][entry]["kwargs"]: schedule[name][str(entry)]["kwargs"] = utils.get_kwargs( self.schedule[name][entry]["kwargs"]) schedule[name][str(entry)]["callback"] = self.schedule[name][ entry]["callback"].__name__ schedule[name][str(entry)]["pin_thread"] = ( self.schedule[name][entry]["pin_thread"] if self.schedule[name][entry]["pin_thread"] != -1 else "None") schedule[name][str(entry)]["pin_app"] = ( "True" if self.schedule[name][entry]["pin_app"] is True else "False") # Order it ordered_schedule = OrderedDict( sorted(schedule.items(), key=lambda x: x[0])) return ordered_schedule async def is_dst(self, dt=None): if dt is None: return (await self.get_now()).astimezone( self.AD.tz).dst() != datetime.timedelta(0) else: return dt.astimezone(self.AD.tz).dst() != datetime.timedelta(0) async def get_now(self): if self.realtime is True: return pytz.utc.localize(datetime.datetime.utcnow()) else: return self.now # Non async version of get_now(), required for logging time formatter - no locking but only used during time travel so should be OK ... def get_now_sync(self): if self.realtime is True: return pytz.utc.localize(datetime.datetime.utcnow()) else: return self.now async def get_now_ts(self): return (await self.get_now()).timestamp() async def get_now_naive(self): return self.make_naive(await self.get_now()) async def now_is_between(self, start_time_str, end_time_str, name=None): start_time = (await self._parse_time(start_time_str, name))["datetime"] end_time = (await self._parse_time(end_time_str, name))["datetime"] now = (await self.get_now()).astimezone(self.AD.tz) start_date = now.replace(hour=start_time.hour, minute=start_time.minute, second=start_time.second) end_date = now.replace(hour=end_time.hour, minute=end_time.minute, second=end_time.second) if end_date < start_date: # Spans midnight if now < start_date and now < end_date: now = now + datetime.timedelta(days=1) end_date = end_date + datetime.timedelta(days=1) return start_date <= now <= end_date async def sunset(self, aware): if aware is True: return self.next_sunset().astimezone(self.AD.tz) else: return self.make_naive(self.next_sunset().astimezone(self.AD.tz)) async def sunrise(self, aware): if aware is True: return self.next_sunrise().astimezone(self.AD.tz) else: return self.make_naive(self.next_sunrise().astimezone(self.AD.tz)) async def parse_time(self, time_str, name=None, aware=False): if aware is True: return (await self._parse_time( time_str, name))["datetime"].astimezone(self.AD.tz).time() else: return self.make_naive( (await self._parse_time(time_str, name))["datetime"]).time() async def parse_datetime(self, time_str, name=None, aware=False): if aware is True: return (await self._parse_time(time_str, name))["datetime"].astimezone(self.AD.tz) else: return self.make_naive((await self._parse_time(time_str, name))["datetime"]) async def _parse_time(self, time_str, name=None): parsed_time = None sun = None offset = 0 parts = re.search(r"^(\d+)-(\d+)-(\d+)\s+(\d+):(\d+):(\d+)$", time_str) if parts: this_time = datetime.datetime( int(parts.group(1)), int(parts.group(2)), int(parts.group(3)), int(parts.group(4)), int(parts.group(5)), int(parts.group(6)), 0, ) parsed_time = self.AD.tz.localize(this_time) else: parts = re.search(r"^(\d+):(\d+):(\d+)$", time_str) if parts: today = (await self.get_now()).astimezone(self.AD.tz) time = datetime.time(int(parts.group(1)), int(parts.group(2)), int(parts.group(3)), 0) parsed_time = today.replace( hour=time.hour, minute=time.minute, second=time.second, microsecond=0, ) else: if time_str == "sunrise": parsed_time = await self.sunrise(True) sun = "sunrise" offset = 0 elif time_str == "sunset": parsed_time = await self.sunset(True) sun = "sunset" offset = 0 else: parts = re.search( r"^sunrise\s*([+-])\s*(\d+):(\d+):(\d+)$", time_str) if parts: sun = "sunrise" if parts.group(1) == "+": td = datetime.timedelta( hours=int(parts.group(2)), minutes=int(parts.group(3)), seconds=int(parts.group(4)), ) offset = td.total_seconds() parsed_time = await self.sunrise(True) + td else: td = datetime.timedelta( hours=int(parts.group(2)), minutes=int(parts.group(3)), seconds=int(parts.group(4)), ) offset = td.total_seconds() * -1 parsed_time = await self.sunrise(True) - td else: parts = re.search( r"^sunset\s*([+-])\s*(\d+):(\d+):(\d+)$", time_str) if parts: sun = "sunset" if parts.group(1) == "+": td = datetime.timedelta( hours=int(parts.group(2)), minutes=int(parts.group(3)), seconds=int(parts.group(4)), ) offset = td.total_seconds() parsed_time = await self.sunset(True) + td else: td = datetime.timedelta( hours=int(parts.group(2)), minutes=int(parts.group(3)), seconds=int(parts.group(4)), ) offset = td.total_seconds() * -1 parsed_time = await self.sunset(True) - td if parsed_time is None: if name is not None: raise ValueError("%s: invalid time string: %s", name, time_str) else: raise ValueError("invalid time string: %s", time_str) return {"datetime": parsed_time, "sun": sun, "offset": offset} # # Diagnostics # async def dump_sun(self): self.diag.info("--------------------------------------------------") self.diag.info("Sun") self.diag.info("--------------------------------------------------") self.diag.info("Next Sunrise: %s", self.next_sunrise()) self.diag.info("Next Sunset: %s", self.next_sunset()) self.diag.info("--------------------------------------------------") async def dump_schedule(self): if self.schedule == {}: self.diag.info("Scheduler Table is empty") else: self.diag.info( "--------------------------------------------------") self.diag.info("Scheduler Table") self.diag.info( "--------------------------------------------------") for name in self.schedule.keys(): self.diag.info("%s:", name) for entry in sorted( self.schedule[name].keys(), key=lambda uuid_: self.schedule[name][uuid_][ "timestamp"], ): self.diag.info( " Next Event Time: %s - data: %s", self.make_naive( self.schedule[name][entry]["timestamp"]), self.schedule[name][entry], ) self.diag.info( "--------------------------------------------------") # # Utilities # @staticmethod def sanitize_timer_kwargs(app, kwargs): kwargs_copy = kwargs.copy() return utils._sanitize_kwargs( kwargs_copy, [ "interval", "constrain_days", "constrain_input_boolean", "_pin_app", "_pin_thread", "__silent" ] + app.list_constraints(), ) @staticmethod def myround(x, base=1, prec=10): if base == 0: return x else: return round(base * round(float(x) / base), prec) @staticmethod def my_dt_round(dt, base=1, prec=10): if base == 0: return dt else: ts = dt.timestamp() rounded = round(base * round(float(ts) / base), prec) result = datetime.datetime.utcfromtimestamp(rounded) aware_result = pytz.utc.localize(result) return aware_result def convert_naive(self, dt): # Is it naive? result = None if dt.tzinfo is None or dt.tzinfo.utcoffset(dt) is None: # Localize with the configured timezone result = self.AD.tz.localize(dt) else: result = dt return result def make_naive(self, dt): local = dt.astimezone(self.AD.tz) return datetime.datetime( local.year, local.month, local.day, local.hour, local.minute, local.second, local.microsecond, )
def _update_sun_info(self): location = Location(('name', 'region', float(self._latitude), float(self._longitude), 'GMT+0', 0)) self.sun = location.sun()
def test_SetLongitudeString(self): loc = Location() loc.longitude = "24°28'S" assert loc.longitude == pytest.approx(-24.46666666666666)
def run(): defaults = { 'log': "info" } # Parse any config file specification. We make this parser with add_help=False so # that it doesn't parse -h and print help. conf_parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, add_help=False ) conf_parser.add_argument("--config", help="Specify config file", metavar='FILE') args, remaining_argv = conf_parser.parse_known_args() # Read configuration file and add it to the defaults hash. if args.config: config = ConfigParser() config.read(args.config) if "Defaults" in config: defaults.update(dict(config.items("Defaults"))) else: sys.exit("Bad config file, missing Defaults section") # Parse rest of arguments parser = argparse.ArgumentParser( description=__doc__, parents=[conf_parser], ) parser.set_defaults(**defaults) parser.add_argument("--gw-station-id", help="GoodWe station ID", metavar='ID') parser.add_argument("--gw-account", help="GoodWe account", metavar='ACCOUNT') parser.add_argument("--gw-password", help="GoodWe password", metavar='PASSWORD') parser.add_argument("--pvo-system-id", help="PVOutput system ID", metavar='ID') parser.add_argument("--pvo-api-key", help="PVOutput API key", metavar='KEY') parser.add_argument("--pvo-interval", help="PVOutput interval in minutes", type=int, choices=[5, 10, 15]) parser.add_argument("--darksky-api-key", help="Dark Sky Weather API key") parser.add_argument("--openweather-api-key", help="Open Weather API key") parser.add_argument("--netatmo-username", help="Netatmo username") parser.add_argument("--netatmo-password", help="Netatmo password") parser.add_argument("--netatmo-client-id", help="Netatmo OAuth client id") parser.add_argument("--netatmo-client-secret", help="Netatmo OAuth client secret") parser.add_argument("--netatmo-device-id", help="Netatmo device id") parser.add_argument("--log", help="Set log level (default info)", choices=['debug', 'info', 'warning', 'critical']) parser.add_argument("--date", help="Copy all readings (max 14/90 days ago)", metavar='YYYY-MM-DD') parser.add_argument("--pv-voltage", help="Send pv voltage instead of grid voltage", action='store_true') parser.add_argument("--skip-offline", help="Skip uploads when inverter is offline", action='store_true') parser.add_argument("--city", help="Sets timezone and skip uploads from dusk till dawn") parser.add_argument('--csv', help="Append readings to a Excel compatible CSV file, DATE in the name will be replaced by the current date") parser.add_argument('--version', action='version', version='%(prog)s ' + __version__) args = parser.parse_args() # Configure the logging numeric_level = getattr(logging, args.log.upper(), None) if not isinstance(numeric_level, int): raise ValueError('Invalid log level: %s' % loglevel) logging.basicConfig(format='%(levelname)-8s %(message)s', level=numeric_level) logging.debug("gw2pvo version " + __version__) if isinstance(args.skip_offline, str): args.skip_offline = args.skip_offline.lower() in ['true', 'yes', 'on', '1'] if args.gw_station_id is None or args.gw_account is None or args.gw_password is None: sys.exit("Missing --gw-station-id, --gw-account and/or --gw-password") if args.city: city = Location(lookup(args.city, database())) os.environ['TZ'] = city.timezone time.tzset() else: city = None logging.debug("Timezone {}".format(datetime.now().astimezone().tzinfo)) # Check if we want to copy old data if args.date: try: copy(args) except KeyboardInterrupt: sys.exit(1) except Exception as exp: logging.error(exp) sys.exit() startTime = datetime.now() while True: try: run_once(args, city) except KeyboardInterrupt: sys.exit(1) except Exception as exp: logging.error(exp) if args.pvo_interval is None: break interval = args.pvo_interval * 60 time.sleep(interval - (datetime.now() - startTime).seconds % interval)
def test_SetBadLongitudeString(self): loc = Location() with pytest.raises(ValueError): loc.longitude = "wibble"
def test_TimezoneNameBad(self): """Test that an exception is raised if an invalid timezone is specified""" c = Location() with pytest.raises(ValueError): c.timezone = "bad/timezone"
class FroniusToInflux: BACKOFF_INTERVAL = 0.1 IGNORE_SUN_DOWN: bool BUCKET: str BUCKET_NAME: str DATA_COLLECTION_INTERVAL: int def __init__( self, client: InfluxDBClient, location_info: LocationInfo, endpoints: List[str], tz: Any, bucket_name: str, ignore_sundown: bool = False, data_collection_interval=60, ) -> None: self.client = client self.write_api = client.write_api(write_options=SYNCHRONOUS) self.location = Location(location_info) self.endpoints = endpoints self.tz = tz self.data: Dict[Any, Any] = {} self.BUCKET_NAME = bucket_name self.IGNORE_SUN_DOWN = ignore_sundown self.DATA_COLLECTION_INTERVAL = data_collection_interval def get_float_or_zero(self, value: str) -> float: internal_data: Dict[Any, Any] = {} try: internal_data = self.data["Body"]["Data"] except KeyError: raise WrongFroniusData("Response structure is not healthy.") return float(internal_data.get(value, {}).get("Value", 0)) def translate_response(self) -> List[Dict]: collection = self.data["Head"]["RequestArguments"]["DataCollection"] timestamp = self.data["Head"]["Timestamp"] if collection == "CommonInverterData": error_code = self.data["Body"]["Data"]["DeviceStatus"]["ErrorCode"] return [ { "measurement": "DeviceStatus", "time": timestamp, "fields": { "ErrorCode": error_code, "ErrorCodeMessage": error_codes[error_code], "LEDColor": self.data["Body"]["Data"]["DeviceStatus"]["LEDColor"], "LEDState": self.data["Body"]["Data"]["DeviceStatus"]["LEDState"], "MgmtTimerRemainingTime": self.data["Body"]["Data"]["DeviceStatus"] ["MgmtTimerRemainingTime"], "StateToReset": self.data["Body"]["Data"]["DeviceStatus"] ["StateToReset"], "StatusCode": self.data["Body"]["Data"]["DeviceStatus"] ["StatusCode"], }, }, { "measurement": collection, "time": timestamp, "fields": { "FAC": self.get_float_or_zero("FAC"), "IAC": self.get_float_or_zero("IAC"), "IDC": self.get_float_or_zero("IDC"), "PAC": self.get_float_or_zero("PAC"), "UAC": self.get_float_or_zero("UAC"), "UDC": self.get_float_or_zero("UDC"), "DAY_ENERGY": self.get_float_or_zero("DAY_ENERGY"), "YEAR_ENERGY": self.get_float_or_zero("YEAR_ENERGY"), "TOTAL_ENERGY": self.get_float_or_zero("TOTAL_ENERGY"), }, }, ] elif collection == "3PInverterData": return [{ "measurement": collection, "time": timestamp, "fields": { "IAC_L1": self.get_float_or_zero("IAC_L1"), "IAC_L2": self.get_float_or_zero("IAC_L2"), "IAC_L3": self.get_float_or_zero("IAC_L3"), "UAC_L1": self.get_float_or_zero("UAC_L1"), "UAC_L2": self.get_float_or_zero("UAC_L2"), "UAC_L3": self.get_float_or_zero("UAC_L3"), }, }] elif collection == "MinMaxInverterData": return [{ "measurement": collection, "time": timestamp, "fields": { "DAY_PMAX": self.get_float_or_zero("DAY_PMAX"), "DAY_UACMAX": self.get_float_or_zero("DAY_UACMAX"), "DAY_UDCMAX": self.get_float_or_zero("DAY_UDCMAX"), "YEAR_PMAX": self.get_float_or_zero("YEAR_PMAX"), "YEAR_UACMAX": self.get_float_or_zero("YEAR_UACMAX"), "YEAR_UDCMAX": self.get_float_or_zero("YEAR_UDCMAX"), "TOTAL_PMAX": self.get_float_or_zero("TOTAL_PMAX"), "TOTAL_UACMAX": self.get_float_or_zero("TOTAL_UACMAX"), "TOTAL_UDCMAX": self.get_float_or_zero("TOTAL_UDCMAX"), }, }] else: raise DataCollectionError("Unknown data collection type.") def sun_is_shining(self) -> None: sun = self.location.sun() if (not self.IGNORE_SUN_DOWN and not sun["sunrise"] < datetime.datetime.now(tz=self.tz) < sun["sunset"]): raise SunIsDown return None def run(self) -> None: try: while True: try: self.sun_is_shining() collected_datas = [] for url in self.endpoints: print(f"getting {url}...") response = get(url) response.raise_for_status() self.data = response.json() collected_datas.extend(self.translate_response()) sleep(self.BACKOFF_INTERVAL) self.write_api.write(self.BUCKET_NAME, record=collected_datas) print("Data written") sleep(self.DATA_COLLECTION_INTERVAL) except SunIsDown: print("Waiting for sunrise") sleep(300) except ConnectionError: print("Waiting for connection...") sleep(60) except KeyError: raise WrongFroniusData("Response structure is not healthy") except KeyboardInterrupt: print("Finishing. Goodbye!")
#Random seed seed = 42 #input files: weather_irradiation = 'input/weather/solarirradiation_twenthe.csv' weather_timebaseDataset = 3600 #in seconds per interval #Simulation: #number of days to simulate and skipping of initial days. Simulation starts at Sunday January 1. numDays = 365 # number of days startDay = 0 # Initial day #Select the geographic location. Refer to the Astral plugin to see available locations (or give a lon+lat) # Use e.g. https://www.latlong.net/ location = Location() location.solar_depression = 'civil' location.latitude = 52.239095 location.longitude = 6.857018 location.timezone = 'Europe/Amsterdam' location.elevation = 0 #Select the devices in the neighbourhood #Devices #Scale overall consumption: consumptionFactor = 1.0 #consumption was a bit too high # Penetration of emerging technology in percentages # all values must be between 0-100 # These indicate what percentage of the houses has a certain device
def test_TimezoneLookup(self): """Test that tz refers to a timezone object""" c = Location() assert c.tz == pytz.timezone("Europe/London") c.timezone = "Europe/Stockholm" assert c.tz == pytz.timezone("Europe/Stockholm")
def test_Region(self): """Test the default region and that the region is changeable""" c = Location() assert c.region == "England" c.region = "Australia" assert c.region == "Australia"