Beispiel #1
0
    def __init__(
        self,
        base_config,
        is_passive,
        interrupt_sig=None,
        *args,
        **kwargs,
    ):
        self.class_name = self.__class__.__name__
        super().__init__(service_name=self.class_name)

        self.log = LogParser(base_config=base_config, title=__name__)

        self.base_config = base_config

        self.is_passive = is_passive
        if self.is_passive:
            self.base_config.clock_sim = self

        self.redis = RedisManager(name=self.class_name,
                                  base_config=base_config,
                                  log=self.log)

        self.interrupt_sig = interrupt_sig
        if self.interrupt_sig is None:
            self.interrupt_sig = multiprocessing.Event()

        if not self.is_passive:
            with ClockSim.lock:
                self.setup_active_instance()

        return
Beispiel #2
0
    def __init__(self, base_config, service_name, interrupt_sig):
        self.class_name = self.__class__.__name__
        service_name = (service_name
                        if service_name is not None else self.class_name)
        super().__init__(service_name=service_name)

        self.log = LogParser(base_config=base_config, title=__name__)
        # self.log.info([['y', ' - InstHealth - ']])

        self.base_config = base_config
        self.site_type = self.base_config.site_type
        self.clock_sim = self.base_config.clock_sim
        self.inst_data = self.base_config.inst_data

        self.service_name = service_name
        self.interrupt_sig = interrupt_sig

        self.redis = RedisManager(name=self.class_name,
                                  base_config=self.base_config,
                                  log=self.log)

        self.tel_ids = self.inst_data.get_inst_ids()

        self.inst_health_s0 = dict()
        self.inst_health_s1 = dict()
        self.inst_health = dict()
        self.inst_health_sub_flat = dict()

        # minimum interval of simulation-time to wait before randomising values
        min_wait_update_sec = 10
        self.check_update_opts = {
            'prev_update': None,
            'min_wait': min_wait_update_sec,
        }

        # the fraction of telescopes to randomely update
        self.update_frac = 0.05

        # set debug_updates to 0 to have mild updates, to 1 to have frequent
        # updates for a single inst, or to 2 to frequently update all instruments
        self.debug_updates = 1

        self.inst_data = self.base_config.inst_data
        self.health_tag = self.inst_data.health_tag

        # sleep duration for thread loops
        self.loop_sleep_sec = 1
        # minimal real-time delay between randomisations (once every self.loop_act_rate sec)
        self.loop_act_rate = max(int(5 / self.loop_sleep_sec), 1)

        self.init()

        # make sure this is the only active instance
        self.init_active_instance()

        self.setup_threads()

        return
Beispiel #3
0
    def __init__(self):
        self.class_name = self.__class__.__name__

        self.app_name = 'data_manager'
        settings = parse_args(app_name=self.app_name)

        # southern or northen CTA sites have different telescope configurations
        site_type = settings['site_type']
        # the address for the site
        app_host = settings['app_host']
        # local log file location
        log_file = settings['log_file']
        # logging level
        log_level = settings['log_level']
        # the port for the site
        app_port = settings['app_port']
        # the redis port use for this site
        self.redis_port = settings['redis_port']
        # define the prefix to all urls (must be non-empy string)
        app_prefix = settings['app_prefix']
        # is this a simulation
        is_simulation = settings['is_simulation']
        # development mode
        debug_opts = settings['debug_opts']
        # do we flush redis on startup
        self.do_flush_redis = settings['do_flush_redis']

        # instantiate the general settings class (must come first!)
        self.base_config = BaseConfig(
            site_type=site_type,
            redis_port=self.redis_port,
            app_port=app_port,
            app_prefix=app_prefix,
            app_host=app_host,
            log_level=log_level,
            websocket_route=None,
            allow_panel_sync=None,
            debug_opts=debug_opts,
            is_simulation=is_simulation,
        )

        self.log = LogParser(
            base_config=self.base_config,
            title=__name__,
            log_level=log_level,
            log_file=log_file,
        )

        self.redis = RedisManager(
            name=self.class_name, base_config=self.base_config, log=self.log
        )

        return
    def __init__(self, site_type):
        self.log = HMILog(title=__name__)
        self.log.info([['y', " - PubsubTest - "], ['g', site_type]])

        self.class_name = self.__class__.__name__
        self.redis = RedisManager(name=self.class_name,
                                  port=utils.redis_port,
                                  log=self.log)

        gevent.spawn(self.loop)
        gevent.spawn(self.exe_func_loop)

        return
Beispiel #5
0
    def __init__(self,
                 base_config,
                 service_name,
                 interrupt_sig,
                 end_time_sec=None,
                 timescale=None,
                 *args,
                 **kwargs):
        self.class_name = self.__class__.__name__
        service_name = (service_name
                        if service_name is not None else self.class_name)
        super().__init__(service_name=service_name)

        self.log = LogParser(base_config=base_config, title=__name__)

        self.base_config = base_config

        self.service_name = service_name
        self.interrupt_sig = interrupt_sig

        # 28800 -> 8 hour night
        self.end_time_sec = 28800 if end_time_sec is None else end_time_sec
        # 0.035 -> have 30 minutes last for one minute in real time
        self.timescale = 0.07 if end_time_sec is None else timescale
        # 0.0035 -> have 30 minutes last for 6 sec in real time
        # if not has_acs:
        #   self.timescale /= 2
        # self.timescale /= 20

        self.redis = RedisManager(name=self.class_name,
                                  base_config=base_config,
                                  log=self.log)

        self.n_night = -1

        # sleep duration for thread loops
        self.loop_sleep_sec = 1

        # range in seconds of time-series data to be stored for eg monitoring points
        self.epoch = datetime.utcfromtimestamp(0)
        self.time_series_n_seconds = 60 * 30
        self.second_scale = 1000

        self.reset_night()

        # make sure this is the only active instance
        self.init_active_instance()

        self.setup_threads()

        return
Beispiel #6
0
    def __init__(self, base_config, service_name, interrupt_sig):
        self.class_name = self.__class__.__name__
        service_name = (service_name
                        if service_name is not None else self.class_name)
        super().__init__(service_name=service_name)

        self.log = LogParser(base_config=base_config, title=__name__)

        self.base_config = base_config
        self.site_type = self.base_config.site_type
        self.clock_sim = self.base_config.clock_sim
        self.inst_data = self.base_config.inst_data

        self.service_name = service_name
        self.interrupt_sig = interrupt_sig

        self.tel_ids = self.inst_data.get_inst_ids()

        self.inst_pos_0 = self.base_config.inst_pos_0

        self.redis = RedisManager(name=self.class_name,
                                  base_config=self.base_config,
                                  log=self.log)

        # ------------------------------------------------------------------
        rnd_seed = 10989152934
        self.rnd_gen = Random(rnd_seed)

        # minimum interval of simulation-time to wait before randomising values
        min_wait_update_sec = 10
        self.check_update_opts = {
            'prev_update': None,
            'min_wait': min_wait_update_sec,
        }

        # sleep duration for thread loops
        self.loop_sleep_sec = 1
        # minimal real-time delay between randomisations (once every self.loop_act_rate sec)
        self.loop_act_rate = max(int(5 / self.loop_sleep_sec), 1)

        self.init()

        # make sure this is the only active instance
        self.init_active_instance()

        self.setup_threads()

        return
    def __init__(self, widget_id=None, sm=None, *args, **kwargs):
        self.log = LogParser(base_config=sm.base_config, title=__name__)

        # the parent of this widget
        self.sm = sm
        # the shared basic configuration class
        self.base_config = self.sm.base_config
        self.sess_id = self.sm.sess_id
        self.user_id = self.sm.user_id

        # the id of this instance
        self.widget_id = widget_id
        # widget-class and widget group names
        self.widget_type = self.__class__.__name__
        # for common threading
        self.widget_group = self.sm.user_group_id + '_' + self.widget_type

        # redis interface
        self.redis = RedisManager(
            name=self.widget_type, base_config=self.base_config, log=self.log
        )

        # turn on periodic data updates
        self.do_data_updates = True
        # some etra logging messages for this module
        self.log_send_packet = False

        # fixed or dynamic icon
        self.n_icon = None
        # self.n_icon = -1
        # self.icon_id = -1

        # list of utility classes to loop over
        self.my_utils = dict()

        # arguments given to the setup function, to later be
        # passed to utils if needed
        self.setup_args = None

        return
    def __init__(self, base_config):
        self.log = LogParser(base_config=base_config, title=__name__)
        self.log.info([['g', ' - starting MockTarget ...']])

        self.base_config = base_config
        self.site_type = self.base_config.site_type

        self.class_name = self.__class__.__name__
        self.redis = RedisManager(
            name=self.class_name, base_config=self.base_config, log=self.log
        )

        # ------------------------------------------------------------------
        rnd_seed = 10989152934
        # self.rnd_gen = Random(rnd_seed)
        self.rnd_gen = Random()

        self.az_min_max = [-180, 180]
        self.zen_min_max_tel = [0, 70]

        self.init()

        return
Beispiel #9
0
class ClockSim(ServiceManager):
    """clock simulation class, simulating the procession of a night

       Only a single active instance is allowed to exist. Multiple passive instances
       are allowed. A passive instance only serves as an interface for the clock via redis
    """

    lock = Lock()

    # ------------------------------------------------------------------
    def __init__(
        self,
        base_config,
        is_passive,
        interrupt_sig=None,
        *args,
        **kwargs,
    ):
        self.class_name = self.__class__.__name__
        super().__init__(service_name=self.class_name)

        self.log = LogParser(base_config=base_config, title=__name__)

        self.base_config = base_config

        self.is_passive = is_passive
        if self.is_passive:
            self.base_config.clock_sim = self

        self.redis = RedisManager(name=self.class_name,
                                  base_config=base_config,
                                  log=self.log)

        self.interrupt_sig = interrupt_sig
        if self.interrupt_sig is None:
            self.interrupt_sig = multiprocessing.Event()

        if not self.is_passive:
            with ClockSim.lock:
                self.setup_active_instance()

        return

    # ------------------------------------------------------------------
    def setup_active_instance(self):
        """setup the active instance of the class
        """

        self.rnd_gen = Random(11)
        self.debug_datetime_now = False

        # sleep duration for thread loops
        self.loop_sleep_sec = 1
        self.pubsub_sleep_sec = 0.1

        # self.is_skip_daytime = False
        self.is_skip_daytime = True

        self.is_short_night = False
        # self.is_short_night = True

        # safety measure
        self.min_speed_factor = 1
        self.max_speed_factor = 10 * 60 * self.loop_sleep_sec

        # speedup simulation e.g.,:
        #   60*10 --> every 1 real sec goes to 10 simulated min
        self.speed_factor = 30
        # self.speed_factor = 10

        self.datetime_epoch = self.base_config.datetime_epoch

        self.init_sim_params_from_redis = True
        # self.init_sim_params_from_redis = False

        self.sim_params = {
            'speed_factor': self.speed_factor,
            'min_speed_factor': self.min_speed_factor,
            'max_speed_factor': self.max_speed_factor,
            'is_skip_daytime': self.is_skip_daytime,
            'is_short_night': self.is_short_night,
        }
        self.set_sim_speed(
            data_in={
                'speed_factor': self.speed_factor,
                'is_skip_daytime': self.is_skip_daytime,
                'is_short_night': self.is_short_night,
            },
            from_redis=self.init_sim_params_from_redis,
        )

        # make sure this is the only active instance
        self.init_active_instance()

        self.init_night_times()

        self.setup_threads()

        return

    # ------------------------------------------------------------------
    def setup_threads(self):
        """register threads to be run after this and all other services have
           been initialised
        """

        self.add_thread(target=self.loop_main)
        self.add_thread(target=self.pubsub_sim_params)

        return

    # ------------------------------------------------------------------
    def check_passive(self):
        """check if this is an active or passive instance

            if this is a passive instance, make sure that an active instance
            has been initialised by some other proccess. after n_sec_try of
            waiting, raise an exception

            Returns
            -------
            bool
                is this a passive instance
        """

        need_check = (self.can_loop() and self.is_passive
                      and not self.has_active_instance())
        # print('xxxxxxxx', self.can_loop() , self.is_passive , self.has_active_instance(),'---',need_check)
        if not need_check:
            return self.is_passive

        n_sec_sleep, n_sec_try = 0.01, 10
        n_loops = 1 + int(n_sec_try / n_sec_sleep)
        # check that the active instance has finished the initialisation stage
        for n_loop in range(n_loops + 1):
            sleep(n_sec_sleep)

            active_state = self.has_active_instance() or (not self.can_loop())
            if active_state:
                break
            if n_loop >= n_loops:
                raise Exception(
                    ' - ClockSim active instance can not initialise ?!?!')
            if n_loop > 0 and (n_loop % int(1 / n_sec_sleep) == 0):
                self.log.warn([
                    [
                        'r', ' - ClockSim blocking ( service_name = ',
                        self.class_name
                    ],
                    [
                        'r',
                        ' ) --> waiting for the active instance to init ...'
                    ],
                ])

        return self.is_passive

    # ------------------------------------------------------------------
    def get_time_now_sec(self):
        datetime_now = self.get_datetime_now()
        time_now_sec = int(datetime_to_secs(datetime_now))

        return time_now_sec

    # ------------------------------------------------------------------
    def get_is_night_now(self):
        if self.check_passive():
            return self.redis.get('clock_sim_is_night_now')

        return self.is_night_now

    # ------------------------------------------------------------------
    def get_n_nights(self):
        if self.check_passive():
            return self.redis.get('clock_sim_n_nights')

        return self.n_nights

    # ------------------------------------------------------------------
    def get_night_start_sec(self):
        if self.check_passive():
            return self.redis.get('clock_sim_night_start_sec')

        return self.night_start_sec

    # ------------------------------------------------------------------
    def get_night_end_sec(self):
        if self.check_passive():
            return self.redis.get('clock_sim_night_end_sec')

        return self.night_end_sec

    # ------------------------------------------------------------------
    def get_time_series_start_time_sec(self):
        if self.check_passive():
            start_time_sec = self.redis.get(
                'clock_sim_time_series_start_time_sec')
        else:
            start_time_sec = self.time_series_start_time_sec

        return int(start_time_sec)

    # ------------------------------------------------------------------
    def get_datetime_now(self):
        if self.check_passive():
            time_now_sec = self.redis.get('clock_sim_time_now_sec')
            return secs_to_datetime(time_now_sec)

        return self.datetime_now

    # ------------------------------------------------------------------
    def is_night_time_now(self):
        time_now_sec = self.get_time_now_sec()
        is_night = (time_now_sec > self.get_night_start_sec()
                    and time_now_sec <= self.get_night_end_sec())
        return is_night

    # ------------------------------------------------------------------
    def get_night_duration_sec(self):
        return (self.get_night_end_sec() - self.get_night_start_sec())

    # ------------------------------------------------------------------
    def get_astro_night_start_sec(self):
        # beginig of the astronomical night
        return int(self.get_night_start_sec())

    # ------------------------------------------------------------------
    def get_sim_params(self):
        if self.check_passive():
            sim_params = self.redis.get(name='clock_sim_sim_params')
        else:
            sim_params = self.sim_params

        return sim_params

    # ------------------------------------------------------------------
    def get_speed_factor(self):
        sim_params = self.get_sim_params()
        return sim_params['speed_factor']

    # ------------------------------------------------------------------
    def get_sec_since_midnight(self):
        days_since_epoch = (self.datetime_now - self.datetime_epoch).days
        sec_since_midnight = (
            (self.datetime_now - self.datetime_epoch).seconds +
            timedelta(days=days_since_epoch).total_seconds())
        return sec_since_midnight

    # ------------------------------------------------------------------
    def init_night_times(self):
        """reset the night
        """

        self.n_nights = 0
        self.datetime_now = None

        self.night_start_sec = datetime_to_secs(self.datetime_epoch)
        self.night_end_sec = datetime_to_secs(self.datetime_epoch)
        self.time_series_start_time_sec = self.night_start_sec

        self.set_night_times()

        self.update_once()

        return

    # ------------------------------------------------------------------
    def update_once(self):
        """single update, to be run as part of a loop
        """

        self.datetime_now += timedelta(seconds=self.loop_sleep_sec *
                                       self.speed_factor)

        if self.debug_datetime_now:
            self.log.info([
                ['g', ' --- Now (night:', self.n_nights, '/', ''],
                ['p', self.is_night_time_now()],
                ['g', ') '],
                ['y', self.datetime_now],
                [
                    'c',
                    ' (' + str(datetime_to_secs(self.datetime_now)) + ' sec)'
                ],
            ])

        self.update_n_night()

        time_now_sec = datetime_to_secs(self.datetime_now)
        is_night_now = self.is_night_time_now()

        self.redis.set(
            name='clock_sim_time_now_sec',
            data=time_now_sec,
        )
        self.redis.set(
            name='clock_sim_is_night_now',
            data=is_night_now,
        )
        self.redis.set(
            name='clock_sim_n_nights',
            data=self.n_nights,
        )
        self.redis.set(
            name='clock_sim_night_start_sec',
            data=self.night_start_sec,
        )
        self.redis.set(
            name='clock_sim_night_end_sec',
            data=self.night_end_sec,
        )
        self.redis.set(
            name='clock_sim_time_series_start_time_sec',
            data=self.time_series_start_time_sec,
        )
        return

    # ------------------------------------------------------------------
    def set_night_times(self):
        """reset the night
        """

        night_start_hours = self.rnd_gen.randint(18, 19)
        night_start_minutes = self.rnd_gen.randint(0, 59)
        night_end_hours = self.rnd_gen.randint(4, 5)
        night_end_minutes = self.rnd_gen.randint(0, 59)

        # short night for debugging
        if self.is_short_night:
            night_start_hours = 23
            night_start_minutes = 0
            night_end_hours = 2
            night_end_minutes = 0

        if self.datetime_now is None:
            self.datetime_now = self.datetime_epoch.replace(
                hour=(night_start_hours - 1), )

        self.time_series_start_time_sec = self.night_start_sec

        n_days = (self.datetime_now - self.datetime_epoch).days

        self.night_start_sec = timedelta(
            days=n_days,
            hours=night_start_hours,
            minutes=night_start_minutes,
            seconds=0,
        ).total_seconds()

        # e.g., night ends at 06:40
        self.night_end_sec = timedelta(
            days=(n_days + 1),
            hours=night_end_hours,
            minutes=night_end_minutes,
            seconds=0,
        ).total_seconds()

        if self.is_skip_daytime or self.is_short_night:
            self.datetime_now = (secs_to_datetime(self.night_start_sec) -
                                 timedelta(seconds=10))

        night_start = date_to_string(
            secs_to_datetime(self.night_start_sec),
            date_string=None,
        )
        night_end = date_to_string(
            secs_to_datetime(self.night_end_sec),
            date_string=None,
        )
        self.log.info([
            ['b', ' - setting new night: ['],
            ['g', night_start],
            ['b', ' --> '],
            ['g', night_end],
            ['b', ']'],
        ])

        return

    # ------------------------------------------------------------------
    def update_n_night(self):
        sec_since_midnight = self.get_sec_since_midnight()
        days_since_epoch = (self.datetime_now - self.datetime_epoch).days

        is_new_day = days_since_epoch > self.n_nights
        is_past_night_time = sec_since_midnight > self.night_end_sec

        if is_new_day and is_past_night_time:
            self.n_nights = days_since_epoch
            self.set_night_times()

        return

    # ------------------------------------------------------------------
    def need_data_update(self, update_opts):
        """check if a service needs to run an update, where
           updates only happen after min_wait of simulation time
        """

        time_now = self.get_time_now_sec()

        set_prev_update = (('prev_update' not in update_opts.keys())
                           or (update_opts['prev_update'] is None))
        if set_prev_update:
            update_opts['prev_update'] = time_now - 2 * update_opts['min_wait']

        time_diff = time_now - update_opts['prev_update']
        can_update = (time_diff > update_opts['min_wait'])

        # updates only happen during the astronimical night
        is_night_time = self.is_night_time_now()

        need_update = (is_night_time and can_update)
        if need_update:
            update_opts['prev_update'] = time_now

        return need_update

    # ------------------------------------------------------------------
    def set_sim_speed(self, data_in, from_redis=False):
        """set parameters which determine the lenght of the night, the
           real-time duration, given a speed factor, the delay between nights, etc.
        """

        speed_factor = data_in['speed_factor']
        is_skip_daytime = data_in['is_skip_daytime']
        is_short_night = data_in['is_short_night']

        if from_redis:
            red_data = self.redis.get(name='clock_sim_sim_params')

            if red_data is not None:
                speed_factor = red_data['speed_factor']
                is_skip_daytime = red_data['is_skip_daytime']
                is_short_night = red_data['is_short_night']

        if speed_factor is not None:
            speed_factor = float(speed_factor)

            is_ok = (speed_factor >= self.min_speed_factor
                     and speed_factor <= self.max_speed_factor)
            if not is_ok:
                raise ValueError(
                    'trying to set speed_factor out of bounds ...',
                    speed_factor)

            self.speed_factor = float(speed_factor)

        if is_skip_daytime is not None:
            self.is_skip_daytime = is_skip_daytime

        if is_short_night is not None:
            self.is_short_night = is_short_night

        self.log.info([
            ['b', ' - updating clock_sim_sim_params: '],
            ['c', '   speed_factor: '],
            ['p', self.speed_factor],
            ['c', ' , is_skip_daytime: '],
            ['p', self.is_skip_daytime],
            ['c', ' , is_short_night: '],
            ['p', self.is_short_night],
        ])

        self.sim_params = {
            'speed_factor': self.speed_factor,
            'min_speed_factor': self.min_speed_factor,
            'max_speed_factor': self.max_speed_factor,
            'is_skip_daytime': self.is_skip_daytime,
            'is_short_night': self.is_short_night,
        }
        self.redis.set(
            name='clock_sim_sim_params',
            data=self.sim_params,
        )

        self.redis.publish(channel='clock_sim_updated_sim_params')

        return

    # ------------------------------------------------------------------
    def loop_main(self):
        """main loop running in its own thread, updating the night
        """

        self.log.info([['g', ' - starting ClockSim.loop_main ...']])

        while self.can_loop():
            sleep(self.loop_sleep_sec)
            with ClockSim.lock:
                self.update_once()

        self.log.info([['c', ' - ending ClockSim.loop_main ...']])

        return

    # ------------------------------------------------------------------
    def pubsub_sim_params(self):
        """loop running in its own thread, reacting to pubsub events
        """

        self.log.info([['g', ' - starting ClockSim.pubsub_sim_params ...']])

        # setup the channel once
        pubsub_tag = 'clock_sim_set_sim_params'
        pubsub = self.redis.pubsub_subscribe(pubsub_tag)

        # listen to changes on the channel and do stuff
        while self.can_loop():
            sleep(self.pubsub_sleep_sec)

            msg = self.redis.pubsub_get_message(pubsub=pubsub)
            if msg is None:
                continue

            with ClockSim.lock:
                keys = ['speed_factor', 'is_skip_daytime', 'is_short_night']
                data_out = dict()
                for key in keys:
                    data_out[
                        key] = msg['data'][key] if key in msg['data'] else None

                self.set_sim_speed(data_in=data_out)

        self.log.info([['c', ' - ending ClockSim.pubsub_sim_params ...']])

        return
Beispiel #10
0
class InstPos(ServiceManager):
    """telescope pointing-position simulation class, simulating changes of pointing

       Only a single active instance is allowed to exist
    """

    lock = Lock()

    # ------------------------------------------------------------------
    def __init__(self, base_config, service_name, interrupt_sig):
        self.class_name = self.__class__.__name__
        service_name = (service_name
                        if service_name is not None else self.class_name)
        super().__init__(service_name=service_name)

        self.log = LogParser(base_config=base_config, title=__name__)

        self.base_config = base_config
        self.site_type = self.base_config.site_type
        self.clock_sim = self.base_config.clock_sim
        self.inst_data = self.base_config.inst_data

        self.service_name = service_name
        self.interrupt_sig = interrupt_sig

        self.tel_ids = self.inst_data.get_inst_ids()

        self.inst_pos_0 = self.base_config.inst_pos_0

        self.redis = RedisManager(name=self.class_name,
                                  base_config=self.base_config,
                                  log=self.log)

        # ------------------------------------------------------------------
        rnd_seed = 10989152934
        self.rnd_gen = Random(rnd_seed)

        # minimum interval of simulation-time to wait before randomising values
        min_wait_update_sec = 10
        self.check_update_opts = {
            'prev_update': None,
            'min_wait': min_wait_update_sec,
        }

        # sleep duration for thread loops
        self.loop_sleep_sec = 1
        # minimal real-time delay between randomisations (once every self.loop_act_rate sec)
        self.loop_act_rate = max(int(5 / self.loop_sleep_sec), 1)

        self.init()

        # make sure this is the only active instance
        self.init_active_instance()

        self.setup_threads()

        return

    # ------------------------------------------------------------------
    def setup_threads(self):

        self.add_thread(target=self.loop_main)

        return

    # ------------------------------------------------------------------
    def init(self):
        # self.log.info([['g', ' - InstPos.init() ...']])

        with InstPos.lock:
            self.update_inst_pos()

        return

    # ------------------------------------------------------------------
    def update_inst_pos(self):
        min_delta_pos_sqr = pow(0.05, 2)
        frac_delta_pos = 0.25

        inst_pos_in = dict()
        if self.redis.exists('inst_pos'):
            inst_pos_in = self.redis.h_get_all(name='inst_pos')

        obs_block_ids = self.redis.get(name=('obs_block_ids_' + 'run'),
                                       default_val=[])

        pipe = self.redis.get_pipe()
        for obs_block_id in obs_block_ids:
            pipe.get(obs_block_id)
        blocks = pipe.execute()

        tel_point_pos = dict()
        for n_block in range(len(blocks)):
            if not isinstance(blocks[n_block], dict):
                continue
            if len(blocks[n_block]['pointings']) == 0:
                continue
            tel_ids = (blocks[n_block]['telescopes']['large']['ids'] +
                       blocks[n_block]['telescopes']['medium']['ids'] +
                       blocks[n_block]['telescopes']['small']['ids'])
            point_pos = blocks[n_block]['pointings'][0]['pos']

            for id_now in tel_ids:
                tel_point_pos[id_now] = point_pos

        for id_now in self.tel_ids:
            inst_pos_now = inst_pos_in[
                id_now] if id_now in inst_pos_in else self.inst_pos_0
            if inst_pos_now is None:
                inst_pos_now = self.inst_pos_0
            inst_pos_new = inst_pos_now

            if id_now in tel_point_pos:
                point_pos = tel_point_pos[id_now]

                pos_dif = [(point_pos[0] - inst_pos_now[0]),
                           (point_pos[1] - inst_pos_now[1])]
                if (pos_dif[0] > 360):
                    pos_dif[0] -= 360
                elif (pos_dif[0] < -360):
                    pos_dif[0] += 360
                # if(pos_dif[0] > 180):
                #   pos_dif[0] -= 360
                # elif(pos_dif[0] < -180):
                #   pos_dif[0] += 360
                if (pos_dif[1] >= 90):
                    pos_dif[1] -= 90

                rnd_scale = 1
                if (pos_dif[0] * pos_dif[0] +
                        pos_dif[1] * pos_dif[1]) < min_delta_pos_sqr:
                    rnd_scale = -1.5 if (self.rnd_gen.random() < 0.5) else 1.5

                inst_pos_new = [
                    inst_pos_now[0] + pos_dif[0] * rnd_scale *
                    self.rnd_gen.random() * frac_delta_pos,
                    inst_pos_now[1] + pos_dif[1] * rnd_scale *
                    self.rnd_gen.random() * frac_delta_pos
                ]

            pipe.h_set(name='inst_pos', key=id_now, data=inst_pos_new)

        pipe.execute()

        return

    # ------------------------------------------------------------------
    def loop_main(self):
        self.log.info([['g', ' - starting InstPos.loop_main ...']])
        sleep(0.1)

        n_loop = 0
        while self.can_loop():
            n_loop += 1
            sleep(self.loop_sleep_sec)
            if n_loop % self.loop_act_rate != 0:
                continue

            need_update = self.clock_sim.need_data_update(
                update_opts=self.check_update_opts, )
            if not need_update:
                continue

            with InstPos.lock:
                self.update_inst_pos()

        self.log.info([['c', ' - ending InstPos.loop_main ...']])

        return
Beispiel #11
0
class Manager():
    """class for running asynchronous services
    """

    # ------------------------------------------------------------------
    def __init__(self):
        self.class_name = self.__class__.__name__

        self.app_name = 'data_manager'
        settings = parse_args(app_name=self.app_name)

        # southern or northen CTA sites have different telescope configurations
        site_type = settings['site_type']
        # the address for the site
        app_host = settings['app_host']
        # local log file location
        log_file = settings['log_file']
        # logging level
        log_level = settings['log_level']
        # the port for the site
        app_port = settings['app_port']
        # the redis port use for this site
        self.redis_port = settings['redis_port']
        # define the prefix to all urls (must be non-empy string)
        app_prefix = settings['app_prefix']
        # is this a simulation
        is_simulation = settings['is_simulation']
        # development mode
        debug_opts = settings['debug_opts']
        # do we flush redis on startup
        self.do_flush_redis = settings['do_flush_redis']

        # instantiate the general settings class (must come first!)
        self.base_config = BaseConfig(
            site_type=site_type,
            redis_port=self.redis_port,
            app_port=app_port,
            app_prefix=app_prefix,
            app_host=app_host,
            log_level=log_level,
            websocket_route=None,
            allow_panel_sync=None,
            debug_opts=debug_opts,
            is_simulation=is_simulation,
        )

        self.log = LogParser(
            base_config=self.base_config,
            title=__name__,
            log_level=log_level,
            log_file=log_file,
        )

        self.redis = RedisManager(
            name=self.class_name, base_config=self.base_config, log=self.log
        )

        return

    # ------------------------------------------------------------------
    def cleanup_services(self, service_name, is_verb=False):
        """graceful exit of services
        """

        # is_verb = True
        if is_verb:
            self.log.info([
                ['c', ' - Manager.service_cleanup for '],
                ['y', service_name],
                ['b', ' ...'],
            ])

        # service_manager = ServiceManager()
        # service_manager.unset_active_instance(parent=self, class_prefix=service_name)
        ServiceManager.unset_active_instance(parent=self, service_name=service_name)

        # if service_name == 'clock_sim_service':
        #     pass

        return

    # ------------------------------------------------------------------
    def run_service(self, service_name, interrupt_sig):
        """run services in individual processes
        """

        # ------------------------------------------------------------------
        # simaple locker test (should be moved to unit tests...)
        check_lock = False
        # check_lock = True
        if check_lock:
            lock_prefix = 'utils;lock;' + 'test' + ';'
            # dynamic lock names, based on the current properties
            lock_namespace = {
                'loop': lambda: 'service_loop',
            }
            # initialise the lock manager
            locker = LockManager(
                log=self.log,
                redis=self.redis,
                base_config=self.base_config,
                lock_namespace=lock_namespace,
                lock_prefix=lock_prefix,
                is_passive=True,
            )

            self.log.info([['o', ' -- trying to acquire locks for '], ['c',
                                                                       service_name]])
            with locker.locks.acquire(names='loop', debug=True, can_exist=False):
                self.log.info([['y', ' --   now i am locked 0 ... '], ['c',
                                                                       service_name]])
                sleep(0.3)
                with locker.locks.acquire(names='loop', debug=True, can_exist=True):
                    self.log.info([['y', '   -- now i am locked 1 ... '],
                                   ['c', service_name]])
                    sleep(0.2)
            self.log.info([['o', ' -- released locks for '], ['c', service_name]])
        # ------------------------------------------------------------------

        # in case of backlog from a previous reload, call the cleanup for good measure
        self.cleanup_services(service_name)

        self.log.info([
            ['g', ' - starting services for '],
            ['y', service_name],
            ['g', ' \t(pid: '],
            ['p', os.getpid()],
            ['g', ') ...'],
        ])

        # set the list of telescopes for this particular site and attach it to base_config
        InstData(base_config=self.base_config)

        # set passive instance of the clock class, to add access functions base_config
        ClockSim(
            base_config=self.base_config,
            interrupt_sig=interrupt_sig,
            is_passive=True,
        )

        # for debugging, override the global flag
        # self.do_flush_redis = True
        if service_name == 'redis_flush':
            if self.do_flush_redis:
                self.log.warn([['bb', ' --- flusing redis --- ']])
                self.redis.flush()

        elif service_name == 'redis_services':
            # prefix for all lock names in redis
            lock_prefix = 'utils;lock;' + service_name + ';'
            # dynamic lock names, based on the current properties
            lock_namespace = {
                'loop': lambda: 'service_loop',
            }

            LockManager(
                log=self.log,
                redis=self.redis,
                base_config=self.base_config,
                lock_namespace=lock_namespace,
                lock_prefix=lock_prefix,
                is_passive=False,
                interrupt_sig=interrupt_sig,
                service_name=service_name,
            )

        # ------------------------------------------------------------------
        # ------------------------------------------------------------------
        elif service_name == 'time_of_night_service':
            # start the time_of_night clock (to be phased out....)
            utils.time_of_night(
                base_config=self.base_config,
                service_name=service_name,
                interrupt_sig=interrupt_sig,
            )
        # ------------------------------------------------------------------
        # ------------------------------------------------------------------

        elif service_name == 'clock_sim_service':
            ClockSim(
                base_config=self.base_config,
                interrupt_sig=interrupt_sig,
                is_passive=False,
            )

        elif service_name == 'inst_health_service':
            InstHealth(
                base_config=self.base_config,
                service_name=service_name,
                interrupt_sig=interrupt_sig,
            )

        elif service_name == 'inst_pos_service':
            InstPos(
                base_config=self.base_config,
                service_name=service_name,
                interrupt_sig=interrupt_sig,
            )

        elif service_name == 'scheduler_service':
            if has_acs:
                raise Exception(
                    'threading has not been properly updated for the acs version....'
                )
                SchedulerACS(
                    base_config=self.base_config,
                    service_name=service_name,
                    interrupt_sig=interrupt_sig,
                )
            else:
                MockTarget(base_config=self.base_config)
                SchedulerStandalone(
                    base_config=self.base_config,
                    service_name=service_name,
                    interrupt_sig=interrupt_sig,
                )

        else:
            raise Exception('unknown service_name ?!?', service_name)

        # all service classes inherit from ServiceManager, which keeps track of
        # all thread.  after initialising all classes, start the threads (blocking action)
        ServiceManager.run_threads()

        # after interrupt_sig has released the block from outside
        # of this process, do some cleanup
        self.cleanup_services(service_name)

        return
    def __init__(self, ws_send, *args, **kwargs):
        self.ws_send = ws_send

        self.sess_id = None
        self.user_id = None
        self.sess_name = None
        self.user_group = None
        self.user_group_id = None

        self.has_init_sess = False
        self.is_sess_offline = True
        self.is_sess_open = False
        self.log_send_packet = False
        self.sess_ping_time = None

        # is it allowed to restore sessions as part of development
        # or do we always reload web pages on server reloads
        self.can_restore_existing_sess = True
        # self.can_restore_existing_sess = False

        # debug the setup / restoration of sync groups
        self.debug_sync_group = False
        # self.debug_sync_group = True
        self.debug_sync_group = (self.debug_sync_group
                                 and self.base_config.debug_opts['dev'])

        # validate all session widgets on every few seconds
        self.validate_widget_time_sec = 0
        self.min_validate_widget_time_sec = 10

        self.valid_loop_sleep_sec = 0.01
        self.basic_widget_sleep_sec = 1
        self.sess_expire_sec = 15
        self.serv_expire_sec = 30
        self.user_expire_sec = 43200
        self.widget_expire_sec = self.user_expire_sec
        self.cleanup_sleep_sec = 60

        self.n_id_digits = 4
        self.n_serv_msg = 0

        # session ping/pong heartbeat
        self.sess_ping = {
            # interval for sending ping/pong events
            'send_interval_msec': 2500,
            # how much delay is considered ok for a slow session
            'max_interval_good_msec': 500,
            # how much delay is considered ok for a disconnected session
            'max_interval_slow_msec': 1000,
            # how much delay before the client socket is forcibly closed
            # and set in a reconnection attempt loop
            'max_interval_bad_msec': 5000,
        }
        # self.sess_ping = {
        #     # interval for sending ping/pong events
        #     'send_interval_msec': 2000,
        #     # how much delay is considered ok for a slow session
        #     'max_interval_good_msec': 2000,
        #     # how much delay is considered ok for a disconnected session
        #     'max_interval_slow_msec': 6000,
        # }

        self.widget_module_dir = 'frontend_manager.py.widgets'
        self.util_module_dir = 'frontend_manager.py.utils'

        self.loop_prefix = 'ws;loop;'
        self.heartbeat_prefix = 'ws;heartbeat;'

        self.sync_group_id_prefix = 'grp_'
        self.sync_group_title_prefix = 'Group '

        self.icon_prefix = 'icn_'

        self.asyncio_queue = asyncio.Queue()

        self.log = LogParser(base_config=self.base_config, title=__name__)

        self.allowed_widget_types = self.base_config.allowed_widget_types
        self.all_widget_types = [
            a for a in (self.base_config.allowed_widget_types['synced'] +
                        self.base_config.allowed_widget_types['not_synced'])
        ]

        self.redis_port = self.base_config.redis_port
        self.site_type = self.base_config.site_type
        self.allow_panel_sync = self.base_config.allow_panel_sync
        self.is_simulation = self.base_config.is_simulation

        self.redis = RedisManager(name=self.__class__.__name__,
                                  base_config=self.base_config,
                                  log=self.log)

        rnd_seed = get_rnd_seed()
        self.rnd_gen = Random(rnd_seed)

        self.inst_data = self.base_config.inst_data

        if WebsocketBase.serv_id is None:
            self.set_server_id()

        # setup the locker for this server
        self.locker = self.setup_locker()

        # update the lock_namespace (after the session id has been set, maybe
        # later other session parameters would be needed?)
        self.update_lock_namespace()

        return
class PubsubTest():
    def __init__(self, site_type):
        self.log = HMILog(title=__name__)
        self.log.info([['y', " - PubsubTest - "], ['g', site_type]])

        self.class_name = self.__class__.__name__
        self.redis = RedisManager(name=self.class_name,
                                  port=utils.redis_port,
                                  log=self.log)

        gevent.spawn(self.loop)
        gevent.spawn(self.exe_func_loop)

        return

    # ------------------------------------------------------------------
    #
    # ------------------------------------------------------------------
    def ask_data(self, rnd_seed=-1):
        self.log.info([['y', " --------------------------------------------"]])
        self.log.info([['g', " - PubsubTest.ask_data -  "], ['p', rnd_seed]])

        args = {"func_name": "test_func", "arg0": rnd_seed}
        self.redis.publish(channel="exe_func_loop", message=packb(args))

        return

    # ------------------------------------------------------------------
    #
    # ------------------------------------------------------------------
    def exe_func_loop(self):
        self.log.info([['g', " - starting PubsubTest.exe_func_loop ..."]])

        redis_pubsub = None
        while True:
            while redis_pubsub is None:
                redis_pubsub = self.redis.set_pubsub("exe_func_loop")
                sleep(0.5)

            msg = self.redis.get_pubsub("exe_func_loop")
            if msg:
                args = unpackb(msg["data"])

                # call as blocking
                getattr(self, args["func_name"])(args)

                # call as non-blocking
                def exe_async_func(args):
                    getattr(self, args["func_name"])(args)

                gevent.spawn(exe_async_func, args)

            sleep(0.01)

        return

    # ------------------------------------------------------------------
    #
    # ------------------------------------------------------------------
    def test_func(self, args=None):
        self.log.info([['g', " - PubsubTest.test_func - "],
                       ['p', args["arg0"]]])
        return

    # ------------------------------------------------------------------
    #
    # ------------------------------------------------------------------
    def loop(self):
        self.log.info([['g', " - starting PubsubTest.loop ..."]])
        sleep(2)

        rnd_seed = 9823987423
        while True:
            self.ask_data(rnd_seed=rnd_seed)

            rnd_seed += 1
            sleep(2)

        return
class BaseWidget():
    # all session ids for this user/widget
    widget_group_sess = dict()

    # ------------------------------------------------------------------
    def __init__(self, widget_id=None, sm=None, *args, **kwargs):
        self.log = LogParser(base_config=sm.base_config, title=__name__)

        # the parent of this widget
        self.sm = sm
        # the shared basic configuration class
        self.base_config = self.sm.base_config
        self.sess_id = self.sm.sess_id
        self.user_id = self.sm.user_id

        # the id of this instance
        self.widget_id = widget_id
        # widget-class and widget group names
        self.widget_type = self.__class__.__name__
        # for common threading
        self.widget_group = self.sm.user_group_id + '_' + self.widget_type

        # redis interface
        self.redis = RedisManager(
            name=self.widget_type, base_config=self.base_config, log=self.log
        )

        # turn on periodic data updates
        self.do_data_updates = True
        # some etra logging messages for this module
        self.log_send_packet = False

        # fixed or dynamic icon
        self.n_icon = None
        # self.n_icon = -1
        # self.icon_id = -1

        # list of utility classes to loop over
        self.my_utils = dict()

        # arguments given to the setup function, to later be
        # passed to utils if needed
        self.setup_args = None

        return

    # ------------------------------------------------------------------
    async def setup(self, *args):
        self.setup_args = args

        widget_info = await self.sm.get_lazy_widget_info(
            sess_id=self.sess_id,
            widget_id=self.widget_id,
        )
        if widget_info is None:
            return

        if self.n_icon is None:
            self.n_icon = widget_info['n_icon']
            # self.icon_id = widget_info['icon_id']

        # override the global logging variable with a
        # name corresponding to the current session id
        self.log = LogParser(
            base_config=self.base_config,
            title=(
                str(self.user_id) + '/' + str(self.sm.sess_id) + '/' + __name__ + '/'
                + self.widget_id
            ),
        )

        return

    # ------------------------------------------------------------------
    async def util_setup(self, data):
        """load a utility class and set it up
        """

        util_id = data['util_id']
        util_type = data['util_type']

        self.log.debug([
            ['b', ' - util_setup: '],
            ['y', util_type],
            ['b', ' with '],
            ['y', util_id],
            ['b', ' to '],
            ['o', self.widget_id],
        ])

        widget_info = await self.sm.get_lazy_widget_info(
            sess_id=self.sess_id,
            widget_id=self.widget_id,
        )
        if widget_info is None:
            return

        if util_id not in widget_info['util_ids']:
            widget_info['util_ids'] += [util_id]

        # dynamic loading of the module
        util_source = self.sm.util_module_dir + '.' + util_type
        util_module = importlib.import_module(util_source, package=None)
        util_cls = getattr(util_module, util_type)

        # instantiate the util class
        self.my_utils[util_id] = util_cls(util_id=util_id, parent=self)

        # add a lock for initialisation. needed in case of restoration
        # of sessions, in order to make sure the initialisation
        # method is called before any other
        expire_sec = self.my_utils[util_id].sm.get_expite_sec(name='widget_init_expire')

        self.my_utils[util_id].locker.semaphores.add(
            name=self.get_util_lock_name(util_id),
            key=util_id,
            expire_sec=expire_sec,
        )

        # run the setup function of the util
        await self.my_utils[util_id].setup(self.setup_args)

        self.redis.h_set(name='ws;widget_info', key=self.widget_id, data=widget_info)

        return

    # ------------------------------------------------------------------
    async def util_func(self, data):
        """execute util methods following client events
        """

        util_id = data['util_id']
        method_name = data['method_name']
        method_args = data['method_args'] if 'method_args' in data else dict()

        # is this the initialisation method
        is_init_func = (method_name == 'util_init')

        # in case this is the first call, the util should be loaded
        if util_id not in self.my_utils:
            await self.util_setup(data)

            # if the first call is not the initialisation method, this is probably
            # a restored session. in this case we send the client a request to
            # send the initialisation method request, and the current method call will
            # be blocked by the initialisation lock
            if not is_init_func:
                opt_in = {
                    'widget': self,
                    'event_name': ('ask_init_util;' + util_id),
                }
                await self.sm.emit_widget_event(opt_in=opt_in)

        # block non-initialisation calls if initialisation has not finished yet
        if not is_init_func:
            max_lock_sec = self.sm.get_expite_sec(
                name='widget_init_expire',
                is_lock_check=True,
            )
            await self.my_utils[util_id].locker.semaphores.async_block(
                is_locked=await self.is_util_init_locked(util_id),
                max_lock_sec=max_lock_sec,
            )

        # execute the requested method
        init_func = getattr(self.my_utils[util_id], method_name)
        await init_func(method_args)

        # remove the initialisation lock
        if is_init_func:
            self.my_utils[util_id].locker.semaphores.remove(
                name=self.get_util_lock_name(util_id),
                key=util_id,
            )

        return

    # ------------------------------------------------------------------
    def get_util_lock_name(self, util_id):
        """a unique name for initialisation locking
        """

        lock_name = (
            'ws;base_widget;util_func;' + self.my_utils[util_id].class_name + ';'
            + self.my_utils[util_id].util_id
        )

        return lock_name

    # ------------------------------------------------------------------
    async def is_util_init_locked(self, util_id):
        """a function which checks if the initialisation is locked
        """
        async def is_locked():
            locked = self.my_utils[util_id].locker.semaphores.check(
                name=self.get_util_lock_name(util_id),
                key=util_id,
            )
            return locked

        return is_locked

    # ------------------------------------------------------------------
    async def back_from_offline(self, *args):
        """interface function for back-from-offline events
        """

        # check if any util is missing (e.g., in case we are back
        # after a session recovery) and ask the client to respond

        widget_info = self.redis.h_get(name='ws;widget_info', key=self.widget_id)
        util_ids = widget_info['util_ids']
        util_ids = [u for u in util_ids if u not in self.my_utils.keys()]

        for util_id in util_ids:
            opt_in = {
                'widget': self,
                'event_name': ('ask_init_util;' + util_id),
            }
            await self.sm.emit_widget_event(opt_in=opt_in)

        # loop over utils
        for util_now in self.my_utils.values():
            await util_now.back_from_offline(args)

        return
Beispiel #15
0
class SchedulerACS(ThreadManager):
    has_active = False
    lock = Lock()

    def __init__(self, base_config, interrupt_sig):
        self.log = LogParser(base_config=base_config, title=__name__)
        # self.log.info([['y', ' - SchedulerACS - '], ['g', base_config.site_type]])

        if SchedulerACS.has_active:
            raise Exception('Can not instantiate SchedulerACS more than once...')
        else:
            SchedulerACS.has_active = True

        self.base_config = base_config
        self.site_type = self.base_config.site_type
        self.clock_sim = self.base_config.clock_sim
        self.inst_data = self.base_config.inst_data

        self.interrupt_sig = interrupt_sig

        self.tel_ids = self.inst_data.get_inst_ids(inst_types=['LST', 'MST', 'SST'])

        self.no_sub_arr_name = self.base_config.no_sub_arr_name

        self.class_name = self.__class__.__name__
        self.redis = RedisManager(
            name=self.class_name, base_config=self.base_config, log=self.log
        )

        self.debug = not True
        self.expire_sec = 86400  # one day
        # self.expire_sec = 5

        self.MockSched = None

        # self.client = PySimpleClient()
        # self.supervisor = self.client.getComponent('ArraySupervisor')
        # self.log.info([['y',' - SchedulerACS - '],['p','got supervisor!']])

        self.phases_exe = dict()
        self.phases_exe['start'] = [
            'run_config_mount',
            'run_config_camera',
            'run_config_daq',
            'run_config_mirror',
        ]
        self.phases_exe['during'] = [
            'run_take_data',
        ]
        self.phases_exe['finish'] = [
            'run_finish_mount',
            'run_finish_camera',
            'run_finish_daq',
        ]

        self.az_min_max = [-180, 180]

        self.loop_sleep_sec = 3

        rnd_seed = get_rnd_seed()
        # rnd_seed = 10987268332
        self.rnd_gen = Random(rnd_seed)

        self.setup_threads()

        self.MockSched = MockSched(
            base_config=self.base_config, interrupt_sig=self.interrupt_sig
        )

        # ------------------------------------------------------------------
        # temporary hack to be consistent with SchedulerStandalone
        # ------------------------------------------------------------------
        self.external_events = []
        self.external_clock_events = []
        external_generate_clock_events(self)
        external_generate_events(self)

        return

    # ---------------------------------------------------------------------------
    def setup_threads(self):
        self.add_thread(target=self.loop_main)
        return

    # ------------------------------------------------------------------
    def reset_blocks(self):
        debug_tmp = False
        # debug_tmp = True

        if debug_tmp:
            self.log.info([['p', ' - SchedulerACS.reset_blocks() ...']])

        if self.MockSched is None:
            sleep(0.1)
            self.log.debug([[
                'r', ' - no MockSched ... will try to reset_blocks() again ...'
            ]])
            self.reset_blocks()
            return

        night_duration_sec = self.clock_sim.get_night_duration_sec()

        sched_blocks = self.MockSched.get_blocks()

        obs_block_ids = dict()
        obs_block_ids['wait'] = []
        obs_block_ids['run'] = []
        obs_block_ids['done'] = []
        obs_block_ids['cancel'] = []
        obs_block_ids['fail'] = []

        blocks_run = []
        active = sched_blocks['active']

        pipe = self.redis.get_pipe()

        for sched_blk_id, schBlock in sched_blocks['blocks'].items():

            sub_array_tels = (
                schBlock['sched_block'].config.instrument.sub_array.telescopes
            )
            tel_ids = [x.id for x in sub_array_tels]

            obs_blocks = schBlock['sched_block'].observation_blocks

            # get the total duration of all obs blocks
            block_duration_sec = 0
            for n_obs_block_now in range(len(obs_blocks)):
                obs_block_id = obs_blocks[n_obs_block_now].id
                block_duration_sec += (sched_blocks['metadata'][obs_block_id]['duration'])

            targets = get_rnd_targets(
                self=self,
                night_duration_sec=night_duration_sec,
                block_duration_sec=block_duration_sec,
            )

            for n_obs_block_now in range(len(obs_blocks)):
                obs_block_now = obs_blocks[n_obs_block_now]
                obs_block_id = obs_block_now.id
                # trg_id = obs_block_now.src.id
                # coords = obs_block_now.src.coords.horizontal

                sched_metadata = sched_blocks['metadata'][obs_block_id]
                timestamp = sched_metadata['timestamp']
                metadata = sched_metadata['metadata']
                status = sched_metadata['status']
                phases = sched_metadata['phases']
                duration = sched_metadata['duration']
                start_time_sec_plan = sched_metadata['start_time_sec_plan']
                start_time_sec_exe = sched_metadata['start_time_sec_exe']

                start_time_sec = (
                    start_time_sec_plan
                    if start_time_sec_exe is None else start_time_sec_exe
                )

                # state of ob
                if status == sb.OB_PENDING:
                    state = 'wait'
                elif status == sb.OB_RUNNING:
                    state = 'run'
                elif status == sb.OB_CANCELED:
                    state = 'cancel'
                elif status == sb.OB_FAILED:
                    state = 'fail'
                else:
                    state = 'done'

                # final sanity check
                if state == 'run' and sched_blk_id not in active:
                    state = 'done'

                run_phase = []
                if state == 'run':
                    for p in phases:
                        if p.status == sb.OB_RUNNING:
                            phase_name = 'run_' + p.name
                            for phases_exe in self.phases_exe:
                                if phase_name in self.phases_exe[phases_exe]:
                                    run_phase.append(phase_name)

                    if debug_tmp:
                        self.log.debug([
                            ['b', ' -- run_phase - '],
                            ['y', run_phase, ' '],
                            ['b', tel_ids],
                        ])

                can_run = True
                if state == 'cancel' or state == 'fail':
                    can_run = (self.clock_sim.get_time_now_sec() >= start_time_sec)

                exe_state = {'state': state, 'can_run': can_run}

                time = {
                    'start': start_time_sec,
                    'duration': duration,
                }
                time['end'] = time['start'] + time['duration']

                telescopes = {
                    'large': {
                        'min': int(len(list(filter(lambda x: 'L' in x, tel_ids))) / 2),
                        'max': 4,
                        'ids': list(filter(lambda x: 'L' in x, tel_ids))
                    },
                    'medium': {
                        'min': int(len(list(filter(lambda x: 'M' in x, tel_ids))) / 2),
                        'max': 25,
                        'ids': list(filter(lambda x: 'M' in x, tel_ids))
                    },
                    'small': {
                        'min': int(len(list(filter(lambda x: 'S' in x, tel_ids))) / 2),
                        'max': 70,
                        'ids': list(filter(lambda x: 'S' in x, tel_ids))
                    }
                }

                pointings = get_rnd_pointings(
                    self=self,
                    tel_ids=tel_ids,
                    targets=targets,
                    sched_block_id=sched_blk_id,
                    obs_block_id=obs_block_id,
                    n_obs_now=n_obs_block_now,
                )

                block = dict()
                block['sched_block_id'] = sched_blk_id
                block['obs_block_id'] = obs_block_id
                block['time'] = time
                block['metadata'] = metadata
                block['timestamp'] = timestamp
                block['telescopes'] = telescopes
                block['exe_state'] = exe_state
                block['run_phase'] = run_phase
                block['targets'] = targets
                block['pointings'] = pointings
                block['tel_ids'] = tel_ids

                if state == 'run':
                    blocks_run += [block]

                obs_block_ids[state].append(obs_block_id)

                pipe.set(
                    name=obs_block_id,
                    data=block,
                    expire_sec=self.expire_sec,
                    packed=True
                )

        pipe.set(name='obs_block_ids_' + 'wait', data=obs_block_ids['wait'], packed=True)
        pipe.set(name='obs_block_ids_' + 'run', data=obs_block_ids['run'], packed=True)
        pipe.set(name='obs_block_ids_' + 'done', data=obs_block_ids['done'], packed=True)
        pipe.set(
            name='obs_block_ids_' + 'cancel', data=obs_block_ids['cancel'], packed=True
        )
        pipe.set(name='obs_block_ids_' + 'fail', data=obs_block_ids['fail'], packed=True)

        pipe.execute()

        update_sub_arrs(self=self, blocks=blocks_run)

        return

    # # ------------------------------------------------------------------
    # #
    # # ------------------------------------------------------------------
    # def update_sub_arrs(self, blocks=None):
    #     # inst_pos = self.redis.h_get_all(name='inst_pos')

    #     pipe = self.redis.get_pipe()

    #     if blocks is None:
    #         obs_block_ids = self.redis.get(
    #             name=('obs_block_ids_' + 'run'), packed=True, default_val=[]
    #         )
    #         for obs_block_id in obs_block_ids:
    #             pipe.get(obs_block_id)

    #         blocks = pipe.execute(packed=True)

    #     # sort so last is first in the list (latest sub-array defined gets the telescope)
    #     blocks = sorted(
    #         blocks, cmp=lambda a, b: int(b['timestamp']) - int(a['timestamp'])
    #     )
    #     # print [a['timestamp'] for a in blocks]

    #     sub_arrs = []
    #     all_tel_ids_in = []
    #     for n_block in range(len(blocks)):
    #         block_tel_ids = blocks[n_block]['tel_ids']
    #         pnt_id = blocks[n_block]['point_id']
    #         pointing_name = blocks[n_block]['pointing_name']

    #         # compile the telescope list for this block
    #         tels = []
    #         for id_now in block_tel_ids:
    #             if id_now not in all_tel_ids_in:
    #                 all_tel_ids_in.append(id_now)
    #                 tels.append({'id': id_now})

    #         # add the telescope list for this block
    #         sub_arrs.append({'id': pnt_id, 'N': pointing_name, 'children': tels})

    #     # ------------------------------------------------------------------
    #     # now take care of all free telescopes
    #     # ------------------------------------------------------------------
    #     tels = []
    #     all_tel_ids = [x for x in self.tel_ids if x not in all_tel_ids_in]
    #     for id_now in all_tel_ids:
    #         tels.append({'id': id_now})

    #     sub_arrs.append({'id': self.no_sub_arr_name, 'children': tels})

    #     # ------------------------------------------------------------------
    #     # for now - a simple/stupid solution, where we write the sub-arrays and publish each
    #     # time, even if the content is actually the same ...
    #     # ------------------------------------------------------------------
    #     self.redis.set(name='sub_arrs', data=sub_arrs, packed=True)
    #     self.redis.publish(channel='sub_arrs')

    #     return

    # ------------------------------------------------------------------
    def loop_main(self):
        self.log.info([['g', ' - starting SchedulerACS.loop_main ...']])

        pipe = self.redis.get_pipe()

        pipe.set(name='obs_block_ids_' + 'wait', data='')
        pipe.set(name='obs_block_ids_' + 'run', data='')
        pipe.set(name='obs_block_ids_' + 'done', data='')
        pipe.set(name='obs_block_ids_' + 'cancel', data='')
        pipe.set(name='obs_block_ids_' + 'fail', data='')

        pipe.execute()

        update_sub_arrs(self=self, blocks=[])

        while self.can_loop():
            sleep(self.loop_sleep_sec)

            with SchedulerACS.lock:
                self.reset_blocks()

        self.log.info([['c', ' - ending SchedulerACS.loop_main ...']])

        return
Beispiel #16
0
class time_of_night(ServiceManager):

    # ------------------------------------------------------------------
    def __init__(self,
                 base_config,
                 service_name,
                 interrupt_sig,
                 end_time_sec=None,
                 timescale=None,
                 *args,
                 **kwargs):
        self.class_name = self.__class__.__name__
        service_name = (service_name
                        if service_name is not None else self.class_name)
        super().__init__(service_name=service_name)

        self.log = LogParser(base_config=base_config, title=__name__)

        self.base_config = base_config

        self.service_name = service_name
        self.interrupt_sig = interrupt_sig

        # 28800 -> 8 hour night
        self.end_time_sec = 28800 if end_time_sec is None else end_time_sec
        # 0.035 -> have 30 minutes last for one minute in real time
        self.timescale = 0.07 if end_time_sec is None else timescale
        # 0.0035 -> have 30 minutes last for 6 sec in real time
        # if not has_acs:
        #   self.timescale /= 2
        # self.timescale /= 20

        self.redis = RedisManager(name=self.class_name,
                                  base_config=base_config,
                                  log=self.log)

        self.n_night = -1

        # sleep duration for thread loops
        self.loop_sleep_sec = 1

        # range in seconds of time-series data to be stored for eg monitoring points
        self.epoch = datetime.utcfromtimestamp(0)
        self.time_series_n_seconds = 60 * 30
        self.second_scale = 1000

        self.reset_night()

        # make sure this is the only active instance
        self.init_active_instance()

        self.setup_threads()

        return

    # ------------------------------------------------------------------
    def setup_threads(self):

        self.add_thread(target=self.loop_main)

        return

    # ------------------------------------------------------------------
    def get_total_time_seconds(self):
        return self.end_time_sec

    # ------------------------------------------------------------------
    def get_n_night(self):
        return self.n_night

    # ------------------------------------------------------------------
    def get_timescale(self):
        return self.timescale

    # ------------------------------------------------------------------
    def get_current_time(self, n_digits=3):
        if n_digits >= 0 and n_digits is not None:
            return (int(floor(self.time_now_sec)) if n_digits == 0 else round(
                self.time_now_sec, n_digits))
        else:
            return self.time_now_sec

    # ------------------------------------------------------------------
    def get_second_scale(self):
        return self.second_scale

    # ------------------------------------------------------------------
    def get_reset_time(self):
        return self.real_reset_time_sec

    # ------------------------------------------------------------------
    def get_real_time_sec(self):
        """the global function for the current system time
        """

        return int((datetime.utcnow() - self.epoch).total_seconds() *
                   self.second_scale)

    # ------------------------------------------------------------------
    def get_time_series_start_time_sec(self):
        return self.get_real_time_sec(
        ) - self.time_series_n_seconds * self.second_scale

    # ------------------------------------------------------------------
    def get_start_time_sec(self):
        return 0

    # ------------------------------------------------------------------
    def reset_night(self, log=None):
        self.n_night += 1
        self.real_reset_time_sec = self.get_real_time_sec()

        time_now_sec = int(floor(self.get_start_time_sec()))
        self.time_now_sec = time_now_sec

        if log is not None:
            self.log.info([
                ['r', '- reset_night(): '],
                ['y', 'time_now_sec:', self.time_now_sec, ', '],
                ['b', 'n_night:', self.n_night, ', '],
                ['g', 'real_reset_time_sec:', self.real_reset_time_sec],
            ])

        pipe = self.redis.get_pipe()

        pipe.set(name='time_of_night_' + 'scale', data=self.timescale)
        pipe.set(name='time_of_night_' + 'start', data=time_now_sec)
        pipe.set(name='time_of_night_' + 'end', data=self.end_time_sec)
        pipe.set(name='time_of_night_' + 'now', data=time_now_sec)

        pipe.execute()

        return

    # ------------------------------------------------------------------
    def loop_main(self):
        self.log.info([['g', ' - starting time_of_night.loop_main ...']])

        while self.can_loop():
            self.time_now_sec += self.loop_sleep_sec / self.timescale
            if self.time_now_sec > self.end_time_sec:
                self.reset_night()

            self.redis.set(name='time_of_night_' + 'now',
                           data=int(floor(self.time_now_sec)))

            sleep(self.loop_sleep_sec)

        self.log.info([['c', ' - ending time_of_night.loop_main ...']])

        return
Beispiel #17
0
    def __init__(self, base_config, service_name, interrupt_sig):
        self.class_name = self.__class__.__name__
        service_name = (service_name if service_name is not None else self.class_name)
        super().__init__(service_name=service_name)

        self.log = LogParser(base_config=base_config, title=__name__)
        self.log.info([['g', ' - starting SchedulerStandalone ...']])

        self.base_config = base_config
        self.site_type = self.base_config.site_type
        self.clock_sim = self.base_config.clock_sim
        self.inst_data = self.base_config.inst_data

        self.service_name = service_name
        self.interrupt_sig = interrupt_sig

        self.tel_ids = self.inst_data.get_inst_ids(inst_types=['LST', 'MST', 'SST'])
        self.sub_array_insts = self.inst_data.get_sub_array_insts()

        self.no_sub_arr_name = self.base_config.no_sub_arr_name

        self.redis = RedisManager(
            name=self.class_name, base_config=self.base_config, log=self.log
        )

        self.debug = not True
        self.expire_sec = 86400 * 2  # two days
        # self.expire_sec = 5

        # self.max_n_obs_block = 4 if self.site_type == 'N' else 7
        # self.max_n_obs_block = min(self.max_n_obs_block, floor(len(self.tel_ids) / 4))

        # sleep duration for thread loops
        self.loop_sleep_sec = 1
        # minimal real-time delay between randomisations (once every self.loop_act_rate sec)
        self.loop_act_rate = max(int(2 / self.loop_sleep_sec), 1)

        self.max_n_cycles = 100
        self.min_n_sched_block = 2  # 2
        self.max_n_sched_block = 5  # 5
        self.min_n_obs_block = 1
        self.max_n_obs_block = 5
        self.min_n_tel_block = 4
        self.max_n_free_tels = 5

        self.name_prefix = get_rnd(n_digits=5, out_type=str)

        self.az_min_max = [-180, 180]
        self.zen_min_max_tel = [0, 70]
        self.zen_min_max_pnt = [0, 20]

        self.phases_exe = {
            'start': [
                'run_config_mount', 'run_config_camera', 'run_config_DAQ',
                'run_config_mirror'
            ],
            'during': ['run_take_data'],
            'finish': ['run_finish_mount', 'run_finish_camera', 'run_finish_cleanup'],
        }

        self.error_rnd_frac = {
            'E1': 0.3,
            'E2': 0.4,
            'E3': 0.5,
            'E4': 0.6,
            'E5': 0.7,
            'E6': 0.8,
            'E7': 0.9,
            'E8': 1,
        }

        self.phase_rnd_frac = {
            'start': 0.29,
            'finish': 0.1,
            'cancel': 0.06,
            'fail': 0.1,
        }

        # 1800 = 30 minutes
        self.obs_block_sec = 1800

        self.n_init_cycle = -1
        self.n_nights = -1

        self.update_name = 'obs_block_update'
        self.sched_block_prefix = 'sched_block_'
        self.obs_block_prefix = 'obs_block_'

        rnd_seed = get_rnd_seed()
        self.rnd_gen = Random(rnd_seed)

        self.external_clock_events = []
        external_generate_clock_events(self)

        self.redis.delete(self.update_name)

        self.init()

        # make sure this is the only active instance
        self.init_active_instance()

        self.setup_threads()

        return
Beispiel #18
0
class InstHealth(ServiceManager):
    """instrument health simulation class, simulating changes to the metrics of instruments

       Only a single active instance is allowed to exist
    """

    lock = Lock()

    # ------------------------------------------------------------------
    def __init__(self, base_config, service_name, interrupt_sig):
        self.class_name = self.__class__.__name__
        service_name = (service_name
                        if service_name is not None else self.class_name)
        super().__init__(service_name=service_name)

        self.log = LogParser(base_config=base_config, title=__name__)
        # self.log.info([['y', ' - InstHealth - ']])

        self.base_config = base_config
        self.site_type = self.base_config.site_type
        self.clock_sim = self.base_config.clock_sim
        self.inst_data = self.base_config.inst_data

        self.service_name = service_name
        self.interrupt_sig = interrupt_sig

        self.redis = RedisManager(name=self.class_name,
                                  base_config=self.base_config,
                                  log=self.log)

        self.tel_ids = self.inst_data.get_inst_ids()

        self.inst_health_s0 = dict()
        self.inst_health_s1 = dict()
        self.inst_health = dict()
        self.inst_health_sub_flat = dict()

        # minimum interval of simulation-time to wait before randomising values
        min_wait_update_sec = 10
        self.check_update_opts = {
            'prev_update': None,
            'min_wait': min_wait_update_sec,
        }

        # the fraction of telescopes to randomely update
        self.update_frac = 0.05

        # set debug_updates to 0 to have mild updates, to 1 to have frequent
        # updates for a single inst, or to 2 to frequently update all instruments
        self.debug_updates = 1

        self.inst_data = self.base_config.inst_data
        self.health_tag = self.inst_data.health_tag

        # sleep duration for thread loops
        self.loop_sleep_sec = 1
        # minimal real-time delay between randomisations (once every self.loop_act_rate sec)
        self.loop_act_rate = max(int(5 / self.loop_sleep_sec), 1)

        self.init()

        # make sure this is the only active instance
        self.init_active_instance()

        self.setup_threads()

        return

    # ------------------------------------------------------------------
    def setup_threads(self):

        self.add_thread(target=self.loop_main)

        return

    # ------------------------------------------------------------------
    def init(self):
        # self.log.info([['g', ' - inst_health.init() ...']])

        pipe = self.redis.get_pipe()

        for id_now in self.tel_ids:
            self.inst_health_s0[id_now] = {
                self.health_tag: 100,
                'status': 'run',
                'camera': 100,
                'mirror': 100,
                'mount': 100,
                'daq': 100,
                'aux': 100
            }

            for key, val in self.inst_health_s0[id_now].items():
                pipe.h_set(
                    name='inst_health_summary;' + str(id_now),
                    key=key,
                    data=val,
                )

            # self.redPipe.hmset('inst_health_s0'+str(id_now), self.inst_health_s0[id_now])

        self.inst_health = self.inst_data.get_inst_healths()

        # derive the top-level properties, eg:
        #   {'inst_0', 'camera', 'prc_0', 'mount', 'mirror',
        #    'prc_1', 'aux', 'inst_1', 'daq'}
        self.rnd_props = set()
        for (k_0, v_0) in self.inst_health.items():
            for (k_1, v_1) in v_0.items():
                self.rnd_props.add(k_1)
        self.rnd_props = list(self.rnd_props)

        # a flat dict with references to each level of the original dict
        self.inst_health_sub_flat = dict()
        for id_now in self.tel_ids:
            self.inst_health_sub_flat[id_now] = flatten_dict(
                self.inst_health[id_now])

        for id_now in self.tel_ids:
            self.set_tel_health_s1(id_now)

            for key, val in self.inst_health_sub_flat[id_now].items():
                if 'val' in val['data']:
                    pipe.h_set(name='inst_health_summary;' + str(id_now),
                               key=key,
                               data=val['data']['val'])

        # set the full health metrics for each instrument
        self.inst_health_deep = self.inst_data.get_inst_health_fulls()
        for (id_now, inst) in self.inst_health_deep.items():
            for (field_id, data) in inst.items():
                pipe.h_set(
                    name='inst_health_deep;' + str(id_now),
                    key=field_id,
                    data=data,
                )

        pipe.execute()

        self.rand_once(update_frac=1)

        return

    # ------------------------------------------------------------------
    def set_tel_health_s1(self, id_now):
        self.inst_health_s1[id_now] = {
            'id': id_now,
            self.health_tag: self.inst_health_s0[id_now][self.health_tag],
            'status': 'run',
            'data': [v for v in self.inst_health[id_now].values()]
        }

        return

    # ------------------------------------------------------------------
    def rand_once(self, rnd_seed=-1, update_frac=None):
        ids = self.rand_s0(rnd_seed=rnd_seed, update_frac=update_frac)
        self.rand_s1(tel_id_in=ids, rnd_seed=rnd_seed)

        return

    # ------------------------------------------------------------------
    def rand_s0(self, rnd_seed=-1, update_frac=None):
        if rnd_seed < 0:
            rnd_seed = random.randint(0, 100000)
        rnd_gen = Random(rnd_seed)

        arr_props = dict()

        if update_frac is None:
            update_frac = self.update_frac

        n_rnd_props = len(self.rnd_props)

        pipe = self.redis.get_pipe()

        for id_now in self.tel_ids:

            # example change of the connection status (negative health value)
            if (id_now in ['Lx02']):
                rnd = rnd_gen.random()
                sign = 1 if rnd < 0.5 else -1

                self.inst_health_s0[id_now][self.health_tag] = (
                    sign * abs(self.inst_health_s0[id_now][self.health_tag]))

                pipe.h_set(
                    name='inst_health_summary;' + str(id_now),
                    key=self.health_tag,
                    data=self.inst_health_s0[id_now][self.health_tag],
                )

            if (self.debug_updates >= 2) or (id_now in ['Lx01']):
                update_frac_now = 1
            else:
                update_frac_now = update_frac

            rnd = rnd_gen.random()
            if self.debug_updates == 0:
                if rnd > update_frac_now:
                    continue
            elif self.debug_updates == 1:
                if (id_now not in ['Lx01']) and (rnd < 0.5):
                    continue
                elif rnd > update_frac_now:
                    continue
            elif self.debug_updates == 2:
                pass

            arr_props[id_now] = self.inst_health_s0[id_now]

            rnd = rnd_gen.random()
            if rnd < 0.06:
                health_tot = rnd_gen.randint(0, 100)
            elif rnd < 0.1:
                health_tot = rnd_gen.randint(40, 100)
            else:
                health_tot = rnd_gen.randint(60, 100)

            if self.debug_updates == 0:
                pass
            elif self.debug_updates == 1:
                if (id_now in ['Lx01']) and rnd < 0.5:
                    health_tot = rnd_gen.randint(0, 100)
            elif self.debug_updates >= 2:
                health_tot = rnd_gen.randint(0, 100)

            arr_props[id_now][self.health_tag] = health_tot

            bad = 100 - health_tot
            for n_prop_now in range(n_rnd_props):
                rnd = rnd_gen.randint(0, bad)

                if n_prop_now == n_rnd_props - 1:
                    rnd = bad
                else:
                    bad -= rnd

                if self.rnd_props[n_prop_now] in arr_props[id_now]:
                    arr_props[id_now][self.rnd_props[n_prop_now]] = 100 - rnd

            self.inst_health_s0[id_now] = arr_props[id_now]

            self.inst_health_s1[id_now][self.health_tag] = health_tot

            for key, val in self.inst_health_s0[id_now].items():
                pipe.h_set(name='inst_health_summary;' + str(id_now),
                           key=key,
                           data=val)
                # print('inst_health_summary;' + str(id_now), key, val)

        pipe.execute()

        ids = [id_now for id_now in arr_props]

        return ids

    # ------------------------------------------------------------------
    def rand_s1(self, tel_id_in=None, rnd_seed=-1):

        if rnd_seed < 0:
            rnd_seed = random.randint(0, 100000)
        rnd_gen = Random(rnd_seed)

        rnd_props = [p for p in self.rnd_props]

        # recursive randomization of all 'val' values of the
        # dict and its child elements
        def set_rnd_props(data_in):
            keys = data_in.keys()

            if 'children' in keys:
                for child in data_in['children']:
                    set_rnd_props(child)

            if 'val' in keys:
                rnd_val(data_in)

            return

        # in-place randomisation of the 'val' key of an input dict
        def rnd_val(data_in):
            if rnd_gen.random() < 0.1:
                rnd_now = rnd_gen.uniform(-30, 30)
            else:
                rnd_now = rnd_gen.uniform(-10, 10)

            val = data_in['val'] + ceil(rnd_now)
            val = max(-1, min(100, int(val)))
            data_in['val'] = val

            return

        ids = self.tel_ids if (tel_id_in is None) else tel_id_in

        pipe = self.redis.get_pipe()

        n_rnd_props_0 = 3
        n_rnd_props_1 = 10

        for id_now in self.tel_ids:
            random.shuffle(rnd_props)

            # call the randomization function
            if id_now in ids:
                # randomise the main metrics
                for prop_name in rnd_props:
                    if prop_name not in self.inst_health[id_now]:
                        continue

                    set_rnd_props(self.inst_health[id_now][prop_name])
                    # if id_now == 'Lx01':
                    #     print (id_now, prop_name, '\n', self.inst_health[id_now][prop_name])

                    # sync with the value in self.inst_health_s0
                    prop_value = self.inst_health[id_now][prop_name]['val']
                    self.inst_health_s0[id_now][prop_name] = prop_value
                    pipe.h_set(name='inst_health_summary;' + str(id_now),
                               key=prop_name,
                               data=prop_value)

                # randomise the list of full health
                inst_health_deep = self.redis.h_get_all(
                    name='inst_health_deep;' + str(id_now),
                    default_val={},
                )

                # select a sub sample of properties to randomise
                props_0 = inst_health_deep.keys()
                props_0 = random.sample(props_0,
                                        min(n_rnd_props_0,
                                            len(props_0) - 1))

                # for debugging, always randomise this particular property
                if id_now == 'Lx01' and 'camera_1_0' not in props_0:
                    if 'camera_1_0' in inst_health_deep.keys():
                        props_0 += ['camera_1_0']

                for n_prop_0 in range(len(props_0)):
                    prop_0_name = props_0[n_prop_0]
                    props_1 = inst_health_deep[prop_0_name]

                    # select a random sub sample of properties
                    # by picking their indices in the list
                    update_indices = random.sample(
                        range(len(props_1) - 1),
                        min(n_rnd_props_1,
                            len(props_1) - 1),
                    )
                    # randomise in-place the val of the selected properties
                    for n_prop_1 in range(len(update_indices)):
                        prop_1_name = update_indices[n_prop_1]
                        rnd_val(props_1[prop_1_name])

                    pipe.h_set(
                        name='inst_health_deep;' + str(id_now),
                        key=props_0[n_prop_0],
                        data=props_1,
                    )

                pipe.execute()

                self.set_tel_health_s1(id_now)

            time_now_sec = self.clock_sim.get_time_now_sec()
            time_min = self.clock_sim.get_time_series_start_time_sec()

            base_name = 'inst_health_summary;' + str(id_now)

            for key, val in self.inst_health_sub_flat[id_now].items():
                if 'val' in val['data']:
                    pipe.h_set(name=base_name,
                               key=key,
                               data=val['data']['val'])
                    pipe.z_add(name=base_name + ';' + key,
                               score=time_now_sec,
                               data={
                                   'time_sec': time_now_sec,
                                   'value': val['data']['val'],
                               },
                               clip_score=time_min)

            pipe.execute()

        # # self.redis.z_get('inst_health_summary;Lx03;camera_1')
        # data = self.redis.z_get('inst_health_summary;Lx03;camera_1')
        # print('----------->', data)
        # raise Exception('aaaaa aaaaaaaaaa')

        return

    # ------------------------------------------------------------------
    def loop_main(self):
        self.log.info([['g', ' - starting InstHealth.loop_main ...']])
        sleep(0.1)

        n_loop = 0
        rnd_seed = 12564654
        while self.can_loop():
            n_loop += 1
            sleep(self.loop_sleep_sec)
            if n_loop % self.loop_act_rate != 0:
                continue

            need_update = self.clock_sim.need_data_update(
                update_opts=self.check_update_opts, )
            if not need_update:
                continue

            with InstHealth.lock:
                self.rand_once(rnd_seed=rnd_seed)
                rnd_seed += 1

        self.log.info([['c', ' - ending InstHealth.loop_main ...']])

        return
Beispiel #19
0
    def __init__(self, base_config, interrupt_sig):
        self.log = LogParser(base_config=base_config, title=__name__)
        # self.log.info([['y', ' - SchedulerACS - '], ['g', base_config.site_type]])

        if SchedulerACS.has_active:
            raise Exception('Can not instantiate SchedulerACS more than once...')
        else:
            SchedulerACS.has_active = True

        self.base_config = base_config
        self.site_type = self.base_config.site_type
        self.clock_sim = self.base_config.clock_sim
        self.inst_data = self.base_config.inst_data

        self.interrupt_sig = interrupt_sig

        self.tel_ids = self.inst_data.get_inst_ids(inst_types=['LST', 'MST', 'SST'])

        self.no_sub_arr_name = self.base_config.no_sub_arr_name

        self.class_name = self.__class__.__name__
        self.redis = RedisManager(
            name=self.class_name, base_config=self.base_config, log=self.log
        )

        self.debug = not True
        self.expire_sec = 86400  # one day
        # self.expire_sec = 5

        self.MockSched = None

        # self.client = PySimpleClient()
        # self.supervisor = self.client.getComponent('ArraySupervisor')
        # self.log.info([['y',' - SchedulerACS - '],['p','got supervisor!']])

        self.phases_exe = dict()
        self.phases_exe['start'] = [
            'run_config_mount',
            'run_config_camera',
            'run_config_daq',
            'run_config_mirror',
        ]
        self.phases_exe['during'] = [
            'run_take_data',
        ]
        self.phases_exe['finish'] = [
            'run_finish_mount',
            'run_finish_camera',
            'run_finish_daq',
        ]

        self.az_min_max = [-180, 180]

        self.loop_sleep_sec = 3

        rnd_seed = get_rnd_seed()
        # rnd_seed = 10987268332
        self.rnd_gen = Random(rnd_seed)

        self.setup_threads()

        self.MockSched = MockSched(
            base_config=self.base_config, interrupt_sig=self.interrupt_sig
        )

        # ------------------------------------------------------------------
        # temporary hack to be consistent with SchedulerStandalone
        # ------------------------------------------------------------------
        self.external_events = []
        self.external_clock_events = []
        external_generate_clock_events(self)
        external_generate_events(self)

        return
class MockTarget():
    """target simulation class, simulating the execution of observations
    """

    # ------------------------------------------------------------------
    def __init__(self, base_config):
        self.log = LogParser(base_config=base_config, title=__name__)
        self.log.info([['g', ' - starting MockTarget ...']])

        self.base_config = base_config
        self.site_type = self.base_config.site_type

        self.class_name = self.__class__.__name__
        self.redis = RedisManager(
            name=self.class_name, base_config=self.base_config, log=self.log
        )

        # ------------------------------------------------------------------
        rnd_seed = 10989152934
        # self.rnd_gen = Random(rnd_seed)
        self.rnd_gen = Random()

        self.az_min_max = [-180, 180]
        self.zen_min_max_tel = [0, 70]

        self.init()

        return

    # ------------------------------------------------------------------
    def init(self):
        self.create_target()
        return

    # ------------------------------------------------------------------
    def create_target(self):
        n_rnd_targets = max(5, int(self.rnd_gen.random() * 12))
        start_time_sec = 0
        end_time_sec = 28800
        step = end_time_sec / (n_rnd_targets + 1)
        offset = step * 0.66

        self.target_ids = []
        self.targets = []

        pipe = self.redis.get_pipe()

        for index in range(n_rnd_targets):
            # if self.redis.exists('inst_pos'):
            #     inst_pos_in = self.redis.h_get_all(name="inst_pos",
            target = {
                "id": "target_" + str(index),
                "name": "target_" + str(index),
            }

            target["pos"] = []
            target["pos"] += [
                self.rnd_gen.random() *
                (self.az_min_max[1] - self.az_min_max[0]) + self.az_min_max[0]
            ]
            target["pos"] += [
                self.rnd_gen.random() *
                (self.zen_min_max_tel[1] - self.zen_min_max_tel[0])
                + self.zen_min_max_tel[0]
            ]

            minimal = start_time_sec + (step * index) - (self.rnd_gen.random() * offset)

            optimal = 1500 + start_time_sec + (step * index) + (step * 0.5) + (
                (self.rnd_gen.random() - 0.5) * offset
            )

            maximal = 4000 + start_time_sec + (step * (index + 1)) + (
                self.rnd_gen.random() * offset
            )

            target["observability"] = {
                "minimal": minimal,
                "optimal": optimal,
                "maximal": maximal
            }

            self.target_ids.append("target_" + str(index))
            self.targets.append(target)

            pipe.set(name='target_' + str(index), data=target)

        pipe.execute()

        pipe.set(name='target_ids', data=self.target_ids)
        pipe.execute()

        return
Beispiel #21
0
class SchedulerStandalone(ServiceManager):
    """scheduler simulation class, simulating the execution of scheduling blocks

       Only a single active instance is allowed to exist
    """

    lock = Lock()

    # ------------------------------------------------------------------
    def __init__(self, base_config, service_name, interrupt_sig):
        self.class_name = self.__class__.__name__
        service_name = (service_name if service_name is not None else self.class_name)
        super().__init__(service_name=service_name)

        self.log = LogParser(base_config=base_config, title=__name__)
        self.log.info([['g', ' - starting SchedulerStandalone ...']])

        self.base_config = base_config
        self.site_type = self.base_config.site_type
        self.clock_sim = self.base_config.clock_sim
        self.inst_data = self.base_config.inst_data

        self.service_name = service_name
        self.interrupt_sig = interrupt_sig

        self.tel_ids = self.inst_data.get_inst_ids(inst_types=['LST', 'MST', 'SST'])
        self.sub_array_insts = self.inst_data.get_sub_array_insts()

        self.no_sub_arr_name = self.base_config.no_sub_arr_name

        self.redis = RedisManager(
            name=self.class_name, base_config=self.base_config, log=self.log
        )

        self.debug = not True
        self.expire_sec = 86400 * 2  # two days
        # self.expire_sec = 5

        # self.max_n_obs_block = 4 if self.site_type == 'N' else 7
        # self.max_n_obs_block = min(self.max_n_obs_block, floor(len(self.tel_ids) / 4))

        # sleep duration for thread loops
        self.loop_sleep_sec = 1
        # minimal real-time delay between randomisations (once every self.loop_act_rate sec)
        self.loop_act_rate = max(int(2 / self.loop_sleep_sec), 1)

        self.max_n_cycles = 100
        self.min_n_sched_block = 2  # 2
        self.max_n_sched_block = 5  # 5
        self.min_n_obs_block = 1
        self.max_n_obs_block = 5
        self.min_n_tel_block = 4
        self.max_n_free_tels = 5

        self.name_prefix = get_rnd(n_digits=5, out_type=str)

        self.az_min_max = [-180, 180]
        self.zen_min_max_tel = [0, 70]
        self.zen_min_max_pnt = [0, 20]

        self.phases_exe = {
            'start': [
                'run_config_mount', 'run_config_camera', 'run_config_DAQ',
                'run_config_mirror'
            ],
            'during': ['run_take_data'],
            'finish': ['run_finish_mount', 'run_finish_camera', 'run_finish_cleanup'],
        }

        self.error_rnd_frac = {
            'E1': 0.3,
            'E2': 0.4,
            'E3': 0.5,
            'E4': 0.6,
            'E5': 0.7,
            'E6': 0.8,
            'E7': 0.9,
            'E8': 1,
        }

        self.phase_rnd_frac = {
            'start': 0.29,
            'finish': 0.1,
            'cancel': 0.06,
            'fail': 0.1,
        }

        # 1800 = 30 minutes
        self.obs_block_sec = 1800

        self.n_init_cycle = -1
        self.n_nights = -1

        self.update_name = 'obs_block_update'
        self.sched_block_prefix = 'sched_block_'
        self.obs_block_prefix = 'obs_block_'

        rnd_seed = get_rnd_seed()
        self.rnd_gen = Random(rnd_seed)

        self.external_clock_events = []
        external_generate_clock_events(self)

        self.redis.delete(self.update_name)

        self.init()

        # make sure this is the only active instance
        self.init_active_instance()

        self.setup_threads()

        return

    # ------------------------------------------------------------------
    def setup_threads(self):

        self.add_thread(target=self.loop_main)

        return

    # ------------------------------------------------------------------
    def init(self):
        debug_tmp = False
        # debug_tmp = True

        self.exe_phase = dict()
        self.all_obs_blocks = []
        self.external_events = []

        self.n_nights = self.clock_sim.get_n_nights()
        night_start_sec = self.clock_sim.get_night_start_sec()
        night_end_sec = self.clock_sim.get_night_end_sec()
        night_duration_sec = self.clock_sim.get_night_duration_sec()

        self.n_init_cycle += 1

        is_cycle_done = False
        n_cycle_now = 0
        n_sched_block = -1
        overhead_sec = self.obs_block_sec * 0.05

        tot_sched_duration_sec = night_start_sec
        max_block_duration_sec = night_end_sec - self.obs_block_sec

        pipe = self.redis.get_pipe()

        while True:
            can_break = not ((tot_sched_duration_sec < max_block_duration_sec) and
                             (n_cycle_now < self.max_n_cycles) and (not is_cycle_done))
            if can_break:
                break

            base_cycle_name = (
                self.name_prefix + '_' + str(self.n_init_cycle) + '_' + str(n_cycle_now)
                + '_'
            )
            n_cycle_now += 1

            # derive a random combination of sub-arrays which do not
            # conflict with each other
            sub_array_ids = list(self.sub_array_insts.keys())
            n_sa_0 = self.rnd_gen.randint(0, len(sub_array_ids) - 1)
            sa_id_0 = sub_array_ids[n_sa_0]

            allowed_sa_ids = self.inst_data.get_allowed_sub_arrays(sa_id_0)

            sa_ids = [sa_id_0]
            while len(allowed_sa_ids) > 0:
                # select a random id from the allowed list of the initial sa
                check_n_sa = self.rnd_gen.randint(0, len(allowed_sa_ids) - 1)
                sa_id_add = allowed_sa_ids[check_n_sa]
                allowed_sa_ids.remove(sa_id_add)

                # check if this id is allowed by all included sas
                check_sa_ids = []
                for sa_id in sa_ids:
                    check_sa_ids_now = self.inst_data.get_allowed_sub_arrays(sa_id)
                    check_sa_ids += [int(sa_id_add in check_sa_ids_now)]

                # add the new sa if it is allowed by all
                if sum(check_sa_ids) == len(check_sa_ids):
                    sa_ids += [sa_id_add]

            if debug_tmp:
                precent = (tot_sched_duration_sec - night_start_sec) / night_duration_sec
                print()
                print('-' * 100)
                print(
                    ' -    n_nights/n_cycle_now',
                    [self.n_nights, n_cycle_now],
                    'tot_sched_duration_sec / percentage:',
                    [tot_sched_duration_sec, int(100 * precent)],
                )

            sched_block_duration_sec = []

            # for n_sched_block_now in range(n_cycle_sched_blocks):
            for n_sched_block_now in range(len(sa_ids)):
                sched_block_id = (
                    self.sched_block_prefix + base_cycle_name + str(n_sched_block_now)
                )

                n_sched_block += 1

                sa_id = sa_ids[n_sched_block_now]
                tel_ids = self.sub_array_insts[sa_id]
                n_tel_now = len(tel_ids)

                if debug_tmp:
                    print(' --   sub-array:', sa_id, '\n', ' ' * 15, tel_ids)

                # choose the number of obs blocks inside these blocks
                n_obs_blocks = self.rnd_gen.randint(
                    self.min_n_obs_block, self.max_n_obs_block
                )

                if debug_tmp:
                    print(
                        ' ---  n_sched_block:', n_sched_block,
                        ' ---  n_sched_block_now / n_tel_now:', n_sched_block_now,
                        n_tel_now, '-------', sched_block_id
                    )

                tot_obs_block_duration_sec = 0
                block_duration_sec = tot_sched_duration_sec

                targets = get_rnd_targets(
                    self=self,
                    night_duration_sec=night_duration_sec,
                    block_duration_sec=block_duration_sec,
                )

                for n_obs_now in range(n_obs_blocks):
                    obs_block_id = (
                        self.obs_block_prefix + base_cycle_name + str(n_sched_block_now)
                        + '_' + str(n_obs_now)
                    )

                    obs_block_name = (str(n_sched_block) + ' (' + str(n_obs_now) + ')')

                    self.exe_phase[obs_block_id] = ''

                    rnd = self.rnd_gen.random()
                    obs_block_sec = self.obs_block_sec
                    if rnd < 0.05:
                        obs_block_sec /= 1.8
                    elif rnd < 0.3:
                        obs_block_sec /= 1.5
                    elif rnd < 0.5:
                        obs_block_sec /= 1.1
                    obs_block_sec = int(floor(obs_block_sec))

                    planed_block_end_sec = block_duration_sec + obs_block_sec
                    is_cycle_done = (planed_block_end_sec > night_end_sec)
                    if is_cycle_done:
                        if debug_tmp:
                            print(
                                ' - is_cycle_done - ',
                                'n_obs_now / start_time_sec / duration:', n_obs_now,
                                block_duration_sec, obs_block_sec
                            )
                        break

                    # integrated time for all obs blocks within this sched block
                    tot_obs_block_duration_sec += obs_block_sec

                    pointings = get_rnd_pointings(
                        self=self,
                        tel_ids=tel_ids,
                        targets=targets,
                        sched_block_id=sched_block_id,
                        obs_block_id=obs_block_id,
                        n_obs_now=n_obs_now,
                    )

                    if debug_tmp:
                        print(
                            ' ---- n_obs_now / start_time_sec / duration:',
                            n_obs_now,
                            block_duration_sec,
                            obs_block_sec,
                            '-------',
                            obs_block_id,
                        )

                    time = {
                        'start': block_duration_sec,
                        'duration': obs_block_sec - overhead_sec,
                    }
                    time['end'] = time['start'] + time['duration']

                    exe_state = {'state': 'wait', 'can_run': True}

                    metadata = {
                        'n_sched': n_sched_block,
                        'n_obs': n_obs_now,
                        'block_name': obs_block_name
                    }

                    telescopes = {
                        'large': {
                            'min':
                            int(len(list(filter(lambda x: 'L' in x, tel_ids))) / 2),
                            'max': 4,
                            'ids': list(filter(lambda x: 'L' in x, tel_ids))
                        },
                        'medium': {
                            'min':
                            int(len(list(filter(lambda x: 'M' in x, tel_ids))) / 2),
                            'max': 25,
                            'ids': list(filter(lambda x: 'M' in x, tel_ids))
                        },
                        'small': {
                            'min':
                            int(len(list(filter(lambda x: 'S' in x, tel_ids))) / 2),
                            'max': 70,
                            'ids': list(filter(lambda x: 'S' in x, tel_ids))
                        }
                    }

                    block = {
                        'sched_block_id': sched_block_id,
                        'obs_block_id': obs_block_id,
                        'time': time,
                        'metadata': metadata,
                        'timestamp': get_time('msec'),
                        'telescopes': telescopes,
                        'exe_state': exe_state,
                        'run_phase': [],
                        'targets': targets,
                        'pointings': pointings,
                        'tel_ids': tel_ids,
                    }

                    pipe.set(
                        name=block['obs_block_id'],
                        data=block,
                        expire_sec=self.expire_sec
                    )

                    self.all_obs_blocks.append(block)

                    block_duration_sec += obs_block_sec

                # list of duration of all sched blocks within this cycle
                if tot_obs_block_duration_sec > 0:  # timedelta(seconds = 0):
                    sched_block_duration_sec += [tot_obs_block_duration_sec]

            # the maximal duration of all blocks within this cycle
            tot_sched_duration_sec += max(sched_block_duration_sec)

        pipe.set(name='external_events', data=self.external_events)
        pipe.set(name='external_clock_events', data=self.external_clock_events)

        pipe.execute()

        self.update_exe_statuses()

        return

    # ------------------------------------------------------------------
    def get_obs_block_template(self):
        """temporary hardcoded dict......
        """

        # generated with:
        #   print jsonAcs.encode(jsonAcs.classFactory.defaultValues[sb.ObservationBlock])

        template = {
            'py/object': 'sb.ObservationBlock',
            'src': {
                'py/object': 'sb.Source',
                'proposal_priority': {
                    'py/object': 'sb.High'
                },
                'proposal_type': {
                    'py/object': 'sb.placeholder'
                },
                'region_of_interest': {
                    'py/object': 'sb.RegionOfInterest',
                    'circle_radius': 100
                },
                'coords': {
                    'py/object': 'sb.Coordinates',
                    'equatorial': {
                        'py/object': 'sb.EquatorialCoordinates',
                        'dec': 4,
                        'ra': 2
                    }
                },
                'id': 'source',
                'observing_mode': {
                    'py/object': 'sb.ObservingMode',
                    'slewing_': {
                        'py/object': 'sb.Slewing',
                        'take_data': 1
                    },
                    'observing_type': {
                        'py/object': 'sb.ObservingType',
                        'wobble_': {
                            'py/object': 'sb.Wobble',
                            'angle': 1,
                            'offset': 1
                        }
                    }
                }
            },
            'observing_conditions': {
                'py/object': 'sb.ObservingConditions',
                'quality_': {
                    'py/object': 'sb.Quality',
                    'illumination': 1,
                    'min_nsb_range': 1,
                    'max_nsb_range': 1
                },
                'start_time_sec': {
                    'py/object': 'sb.DateTime',
                    'placeholder': 1
                },
                'weather_': {
                    'py/object': 'sb.Weather',
                    'wind_speed': 1,
                    'precision_pointing': 1,
                    'cloudiness': 1,
                    'humidity': 1
                },
                'duration': 0,
                'tolerance': 1
            },
            'max_script_duration': 0,
            'script_id': 'script_id',
            'id': 'ob_id'
        }

        return template

    # ------------------------------------------------------------------
    def wait_to_run(self):
        """move one from wait to run
        """

        # time_now_sec = self.time_of_night.get_current_time()
        time_now_sec = self.clock_sim.get_time_now_sec()

        # move to run state
        wait_blocks = [
            x for x in self.all_obs_blocks if (x['exe_state']['state'] == 'wait')
        ]

        pipe = self.redis.get_pipe()

        has_change = False
        for block in wait_blocks:
            time_comp = (
                block['time']['start'] - (self.loop_sleep_sec * self.loop_act_rate)
            )
            if time_now_sec < time_comp:
                # datetime.strptime(block['start_time_sec'], '%Y-%m-%d %H:%M:%S'):
                # - deltatime((self.loop_sleep_sec * self.loop_act_rate))
                continue

            block['exe_state']['state'] = 'run'

            self.exe_phase[block['obs_block_id']] = 'start'
            block['run_phase'] = copy.deepcopy(self.phases_exe['start'])

            has_change = True
            pipe.set(name=block['obs_block_id'], data=block, expire_sec=self.expire_sec)

        if has_change:
            pipe.execute()

            # check for blocks which cant begin as their time is already past
            wait_blocks = [
                x for x in self.all_obs_blocks if x['exe_state']['state'] == 'wait'
            ]

            has_change = False
            for block in wait_blocks:
                # # adjust the starting/ending time
                # block['end_time_sec'] = block['start_time_sec'] + block['duration']

                is_over_time = time_now_sec >= block['time']['end']
                is_rnd_stop = (
                    self.rnd_gen.random() < self.phase_rnd_frac['cancel'] * 0.1
                )
                if is_over_time or is_rnd_stop:

                    block['exe_state']['state'] = 'cancel'
                    if self.rnd_gen.random() < self.error_rnd_frac['E1']:
                        block['exe_state']['error'] = 'E1'
                    elif self.rnd_gen.random() < self.error_rnd_frac['E2']:
                        block['exe_state']['error'] = 'E2'
                    elif self.rnd_gen.random() < self.error_rnd_frac['E3']:
                        block['exe_state']['error'] = 'E3'
                    elif self.rnd_gen.random() < self.error_rnd_frac['E4']:
                        block['exe_state']['error'] = 'E4'
                    elif self.rnd_gen.random() < self.error_rnd_frac['E8']:
                        block['exe_state']['error'] = 'E8'

                    block['exe_state']['can_run'] = False

                    block['run_phase'] = []

                    self.exe_phase[block['obs_block_id']] = ''

                    has_change = True
                    pipe.set(
                        name=block['obs_block_id'],
                        data=block,
                        expire_sec=self.expire_sec,
                    )

            if has_change:
                pipe.execute()

        return

    # ------------------------------------------------------------------
    def run_phases(self):
        """progress run phases
        """

        time_now_sec = self.clock_sim.get_time_now_sec()

        runs = [x for x in self.all_obs_blocks if (x['exe_state']['state'] == 'run')]

        pipe = self.redis.get_pipe()

        has_change = False
        for block in runs:
            phase = self.exe_phase[block['obs_block_id']]
            if phase == '':
                continue

            for phase_now in self.phases_exe[phase]:
                if phase_now in block['run_phase']:

                    if phase_now in self.phases_exe['start']:
                        is_done = (self.rnd_gen.random() < self.phase_rnd_frac['start'])
                        # if is_done:
                        #   block['end_time_sec'] = block['start_time_sec'] + block['duration']

                    elif phase_now in self.phases_exe['during']:
                        is_done = (
                            time_now_sec >= (
                                block['time']['end'] -
                                block['time']['duration'] * self.phase_rnd_frac['finish']
                            )
                        )  # (datetime.strptime(block['end_time_sec'], '%Y-%m-%d %H:%M:%S') - timedelta(seconds = int(block['duration']) * self.phase_rnd_frac['finish'])))

                    else:
                        is_done = (
                            time_now_sec >= block['time']['end']
                        )  # is_done = (time_now_sec >= datetime.strptime(block['end_time_sec'], '%Y-%m-%d %H:%M:%S'))

                    if is_done:
                        block['run_phase'].remove(phase_now)
                    # print is_done,block['run_phase']

            if len(block['run_phase']) == 0:
                next_phase = ''
                if phase == 'start':
                    next_phase = 'during'
                elif phase == 'during':
                    next_phase = 'finish'

                if next_phase in self.phases_exe:
                    block['run_phase'] = copy.deepcopy(self.phases_exe[next_phase])

                self.exe_phase[block['obs_block_id']] = next_phase

            has_change = True
            pipe.set(name=block['obs_block_id'], data=block, expire_sec=self.expire_sec)

        if has_change:
            pipe.execute()

        return

    # ------------------------------------------------------------------
    def run_to_done(self):
        """move one from run to done
        """

        # time_now_sec = self.time_of_night.get_current_time()
        time_now_sec = self.clock_sim.get_time_now_sec()

        runs = [x for x in self.all_obs_blocks if x['exe_state']['state'] == 'run']

        pipe = self.redis.get_pipe()

        has_change = False
        for block in runs:
            if time_now_sec < block['time']['end']:
                continue

            if self.rnd_gen.random() < self.phase_rnd_frac['cancel']:
                block['exe_state']['state'] = 'cancel'
                if self.rnd_gen.random() < self.error_rnd_frac['E1']:
                    block['exe_state']['error'] = 'E1'
                elif self.rnd_gen.random() < self.error_rnd_frac['E2']:
                    block['exe_state']['error'] = 'E2'
                elif self.rnd_gen.random() < self.error_rnd_frac['E3']:
                    block['exe_state']['error'] = 'E3'
                elif self.rnd_gen.random() < self.error_rnd_frac['E4']:
                    block['exe_state']['error'] = 'E4'
                elif self.rnd_gen.random() < self.error_rnd_frac['E8']:
                    block['exe_state']['error'] = 'E8'

            elif self.rnd_gen.random() < self.phase_rnd_frac['fail']:
                block['exe_state']['state'] = 'fail'
                if self.rnd_gen.random() < self.error_rnd_frac['E1']:
                    block['exe_state']['error'] = 'E1'
                elif self.rnd_gen.random() < self.error_rnd_frac['E2']:
                    block['exe_state']['error'] = 'E2'
                elif self.rnd_gen.random() < self.error_rnd_frac['E3']:
                    block['exe_state']['error'] = 'E3'
                elif self.rnd_gen.random() < self.error_rnd_frac['E4']:
                    block['exe_state']['error'] = 'E4'
                elif self.rnd_gen.random() < self.error_rnd_frac['E8']:
                    block['exe_state']['error'] = 'E8'

            else:
                block['exe_state']['state'] = 'done'

            block['run_phase'] = []

            has_change = True
            pipe.set(name=block['obs_block_id'], data=block, expire_sec=self.expire_sec)

            self.exe_phase[block['obs_block_id']] = ''

        if has_change:
            pipe.execute()

        return

    # ------------------------------------------------------------------
    def update_exe_statuses(self):
        """update the exeStatus lists in redis
        """

        blocks_run = []
        obs_block_ids = {'wait': [], 'run': [], 'done': [], 'cancel': [], 'fail': []}

        pipe = self.redis.get_pipe()

        for block in self.all_obs_blocks:
            obs_block_id = block['obs_block_id']
            exe_state = block['exe_state']['state']

            if self.redis.exists(obs_block_id):
                obs_block_ids[exe_state].append(obs_block_id)

                if exe_state == 'run':
                    blocks_run += [block]

        for key, val in obs_block_ids.items():
            pipe.set(name='obs_block_ids_' + key, data=val)

        pipe.execute()

        update_sub_arrs(self=self, blocks=blocks_run)

        return

    # # ------------------------------------------------------------------
    # def update_sub_arrs(self, blocks=None):
    #     pipe = self.redis.get_pipe()
    #     if blocks is None:
    #         obs_block_ids = self.redis.get(
    #             name=('obs_block_ids_' + 'run'), default_val=[]
    #         )
    #         for obs_block_id in obs_block_ids:
    #             pipe.get(obs_block_id)

    #         blocks = pipe.execute()

    #     #
    #     sub_arrs = []
    #     all_tel_ids = copy.deepcopy(self.tel_ids)

    #     for n_block in range(len(blocks)):
    #         block_tel_ids = (
    #             blocks[n_block]['telescopes']['large']['ids']
    #             + blocks[n_block]['telescopes']['medium']['ids']
    #             + blocks[n_block]['telescopes']['small']['ids']
    #         )
    #         pnt_id = blocks[n_block]['pointings'][0]['id']
    #         pointing_name = blocks[n_block]['pointings'][0]['name']

    #         # compile the telescope list for this block
    #         tels = []
    #         for id_now in block_tel_ids:
    #             tels.append({'id': id_now})

    #             if id_now in all_tel_ids:
    #                 all_tel_ids.remove(id_now)

    #         # add the telescope list for this block
    #         sub_arrs.append({'id': pnt_id, 'N': pointing_name, 'children': tels})

    #     # ------------------------------------------------------------------
    #     # now take care of all free telescopes
    #     # ------------------------------------------------------------------
    #     tels = []
    #     for id_now in all_tel_ids:
    #         tels.append({'id': id_now})

    #     sub_arrs.append({'id': self.no_sub_arr_name, 'children': tels})

    #     # ------------------------------------------------------------------
    #     # for now - a simple/stupid solution, where we write the sub-arrays and publish each
    #     # time, even if the content is actually the same ...
    #     # ------------------------------------------------------------------
    #     self.redis.set(name='sub_arrs', data=sub_arrs)
    #     self.redis.publish(channel='sub_arrs')

    #     return

    # ------------------------------------------------------------------
    def external_add_new_redis_blocks(self):
        obs_block_update = self.redis.get(self.update_name, default_val=None)
        if obs_block_update is None:
            return

        pipe = self.redis.get_pipe()

        # for key in self.all_obs_blocks[0]:
        #     self.log.info([['g', key, self.all_obs_blocks[0][key]]])

        # self.log.info([['g', obs_block_update]])
        self.log.info([['g', len(obs_block_update), len(self.all_obs_blocks)]])

        total = 0
        for n_block in range(len(obs_block_update)):
            if self.redis.exists(obs_block_update[n_block]['obs_block_id']):
                # for x in self.all_obs_blocks:
                #     if x['obs_block_id'] == obs_block_update[n_block]['obs_block_id']:
                #         current = [x][0]

                current = [
                    x for x in self.all_obs_blocks
                    if x['obs_block_id'] == obs_block_update[n_block]['obs_block_id']
                ]
                if len(current) == 0:
                    current = obs_block_update[n_block]
                    self.all_obs_blocks.append(current)
                    # for key in obs_block_update[n_block]:
                    #     self.log.info([['g', key, obs_block_update[n_block][key]]])
                else:
                    current = current[0]
                if current['exe_state']['state'] not in ['wait', 'run']:
                    continue

                total += 1

                pipe.set(
                    name=obs_block_update[n_block]['obs_block_id'],
                    data=obs_block_update[n_block],
                    expire_sec=self.expire_sec,
                )
                current = obs_block_update[n_block]

            else:
                self.all_obs_blocks.append(obs_block_update[n_block])
                pipe.set(
                    name=obs_block_update[n_block]['obs_block_id'],
                    data=obs_block_update[n_block],
                    expire_sec=self.expire_sec,
                )

        self.update_exe_statuses()
        # for block in self.all_obs_blocks:
        #     exe_state = block['exe_state']['state']
        #     self.log.info([['g', block['metadata']['block_name'] + ' ' + exe_state]])

        pipe.delete(self.update_name)
        pipe.execute()

        self.log.info([['g', total, len(obs_block_update), len(self.all_obs_blocks)]])

        return

    # ------------------------------------------------------------------
    def loop_main(self):
        self.log.info([['g', ' - starting SchedulerStandalone.loop_main ...']])
        sleep(0.1)

        n_loop = 0
        while self.can_loop():
            n_loop += 1
            sleep(self.loop_sleep_sec)
            if n_loop % self.loop_act_rate != 0:
                continue

            with SchedulerStandalone.lock:
                if self.n_nights < self.clock_sim.get_n_nights():
                    self.init()
                else:
                    self.external_add_new_redis_blocks()
                    wait_blocks = [
                        x for x in self.all_obs_blocks
                        if (x['exe_state']['state'] == 'wait')
                    ]
                    runs = [
                        x for x in self.all_obs_blocks
                        if (x['exe_state']['state'] == 'run')
                    ]

                    if len(wait_blocks) + len(runs) == 0:
                        self.init()
                    else:
                        self.wait_to_run()
                        self.run_phases()
                        self.run_to_done()
                        external_generate_events(self)

                self.update_exe_statuses()

        self.log.info([['c', ' - ending SchedulerStandalone.loop_main ...']])

        return