|
|
|
import time |
|
import datetime |
|
current_time_ms = lambda: int(round(time.time() * 1000)) |
|
|
|
from .AgentConnector import AgentConnector |
|
from .SoarWME import SoarWME |
|
|
|
class TimeConnector(AgentConnector): |
|
""" An agent connector that will maintain time info on the input-link |
|
|
|
The input link will look like: |
|
(<il> ^time <t>) |
|
(<t> ^seconds <secs> # real-time seconds elapsed since start of agent |
|
^milliseconds <ms> # real-time milliseconds elapsed since start |
|
^steps <steps> # number of decision cycles since start of agent |
|
^clock <clock>) |
|
(<clock> ^hour <hr> # 0-23 |
|
^minute <min> # 0-59 |
|
^second <sec> # 0-59 |
|
^millisecond <ms> # 0-999 |
|
^epoch <sec> # Unix epoch time in seconds) |
|
|
|
Also, if using a simulated clock, the agent can send the following output-command: |
|
(<out> ^set-time <st>) (<st> ^hour <h> ^minute <min> ^second <sec>) |
|
|
|
Settings: |
|
clock_include_ms: bool [default=True] |
|
If true, includes milliseconds with both elapsed time and clock time |
|
sim_clock: bool [default=False] |
|
If true, uses a simulated clock that starts at 8AM and advances a fixed amount every DC |
|
If false, will use the local real time |
|
clock_step_ms: int [default=5000] |
|
If using the simulated clock, this is the number of milliseconds it will increase every DC |
|
|
|
""" |
|
def __init__(self, client, clock_include_ms=True, sim_clock=False, clock_step_ms=50, **kwargs): |
|
""" Initializes the connector with the time info |
|
|
|
clock_include_ms - If True: will include millisecond resolution on clock/elapsed |
|
(Setting to false will mean fewer changes to the input-link, slightly faster) |
|
sim_clock - If False: clock uses real-time. If True: clock is simulated |
|
clock_step_ms - If sim_clock=True, this is how much the clock advances every DC |
|
""" |
|
AgentConnector.__init__(self, client) |
|
|
|
self.include_ms = clock_include_ms |
|
self.sim_clock = sim_clock |
|
self.clock_step_ms = int(clock_step_ms) |
|
|
|
self.time_id = None |
|
self.seconds = SoarWME("seconds", 0) |
|
self.milsecs = SoarWME("milliseconds", 0) |
|
self.steps = SoarWME("steps", 0) |
|
|
|
|
|
self.add_output_command("set-time") |
|
|
|
|
|
self.clock_id = None |
|
self.clock_info = [0, 0, 0, 0, 0] |
|
self.clock_wmes = [ SoarWME("hour", 0), SoarWME("minute", 0), SoarWME("second", 0), SoarWME("millisecond", 0), SoarWME("epoch", 0) ] |
|
self.reset_time() |
|
|
|
def advance_clock(self, num_ms): |
|
""" Advances the simulated clock by the given number of milliseconds """ |
|
self.clock_info[3] += num_ms |
|
|
|
if self.clock_info[3] >= 1000: |
|
self.clock_info[2] += self.clock_info[3] // 1000 |
|
self.clock_info[4] += self.clock_info[3] // 1000 |
|
self.clock_info[3] = self.clock_info[3] % 1000 |
|
|
|
if self.clock_info[2] >= 60: |
|
self.clock_info[1] += self.clock_info[2] // 60 |
|
self.clock_info[2] = self.clock_info[2] % 60 |
|
|
|
if self.clock_info[1] >= 60: |
|
self.clock_info[0] += self.clock_info[1] // 60 |
|
self.clock_info[1] = self.clock_info[1] % 60 |
|
|
|
self.clock_info[0] = self.clock_info[0] % 24 |
|
|
|
def update_clock(self): |
|
""" Updates the clock with the real time """ |
|
localtime = time.localtime() |
|
self.clock_info[0] = localtime.tm_hour |
|
self.clock_info[1] = localtime.tm_min |
|
self.clock_info[2] = localtime.tm_sec |
|
self.clock_info[3] = current_time_ms() % 1000 |
|
self.clock_info[4] = int(time.time()) |
|
|
|
def reset_time(self): |
|
""" Resets the time info """ |
|
|
|
default_epoch = int(time.mktime(datetime.datetime(2020, 1, 1, 8, 0, 0, 0).timetuple())) |
|
self.clock_info = [8, 0, 0, 0, default_epoch] |
|
self.milsecs.set_value(0) |
|
self.seconds.set_value(0) |
|
self.steps.set_value(0) |
|
self.start_time = current_time_ms() |
|
|
|
def on_init_soar(self): |
|
self._remove_from_wm() |
|
self.reset_time() |
|
|
|
def set_time(self, hour, min, sec=0, ms=0): |
|
if not self.sim_clock: |
|
return |
|
self.clock_info[0] = hour |
|
self.clock_info[1] = (0 if min is None else min) |
|
self.clock_info[2] = (0 if sec is None else sec) |
|
self.clock_info[3] = ms |
|
self.clock_info[4] = int(time.mktime(datetime.datetime(2020, 1, 1, hour, min, sec, ms).timetuple())) |
|
|
|
def on_input_phase(self, input_link): |
|
|
|
self.milsecs.set_value(int(current_time_ms() - self.start_time)) |
|
self.seconds.set_value(int((current_time_ms() - self.start_time)/1000)) |
|
self.steps.set_value(self.steps.get_value() + 1) |
|
|
|
|
|
if self.sim_clock: |
|
self.advance_clock(self.clock_step_ms) |
|
else: |
|
self.update_clock() |
|
|
|
|
|
if self.time_id is None: |
|
self._add_to_wm(input_link) |
|
else: |
|
self._update_wm() |
|
|
|
def on_output_event(self, command_name, root_id): |
|
if command_name == "set-time": |
|
self.process_set_time_command(root_id) |
|
|
|
def process_set_time_command(self, time_id): |
|
h = time_id.GetChildInt('hour') |
|
m = time_id.GetChildInt('minute') |
|
s = time_id.GetChildInt('second') |
|
self.set_time(h, m, s) |
|
time_id.CreateStringWME('status', 'complete') |
|
|
|
|
|
|
|
def _add_to_wm(self, parent_id): |
|
self.time_id = parent_id.CreateIdWME("time") |
|
if self.include_ms: |
|
self.milsecs.add_to_wm(self.time_id) |
|
self.seconds.add_to_wm(self.time_id) |
|
self.steps.add_to_wm(self.time_id) |
|
|
|
self.clock_id = self.time_id.CreateIdWME("clock") |
|
for i, wme in enumerate(self.clock_wmes): |
|
if i == 3 and not self.include_ms: |
|
continue |
|
wme.set_value(self.clock_info[i]) |
|
wme.add_to_wm(self.clock_id) |
|
|
|
def _update_wm(self): |
|
if self.include_ms: |
|
self.milsecs.update_wm() |
|
self.seconds.update_wm() |
|
self.steps.update_wm() |
|
for i, wme in enumerate(self.clock_wmes): |
|
wme.set_value(self.clock_info[i]) |
|
wme.update_wm() |
|
|
|
def _remove_from_wm(self): |
|
if self.time_id is None: |
|
return |
|
for wme in self.clock_wmes: |
|
wme.remove_from_wm() |
|
self.milsecs.remove_from_wm() |
|
self.seconds.remove_from_wm() |
|
self.steps.remove_from_wm() |
|
self.time_id.DestroyWME() |
|
self.time_id = None |
|
self.clock_id = None |
|
|
|
|