File size: 7,365 Bytes
2130399 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
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) # number of real-time seconds elapsed since start of agent
self.milsecs = SoarWME("milliseconds", 0) # number of real-time milliseconds elapsed since start of agent
self.steps = SoarWME("steps", 0) # number of decision cycles the agent has taken
# Output Link Command: (<out> ^set-time <st>) (<st> ^hour <h> ^minute <min> ^second <sec>)
self.add_output_command("set-time")
# Clock info, hour minute second millisecond
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
# 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
# Seconds
if self.clock_info[2] >= 60:
self.clock_info[1] += self.clock_info[2] // 60
self.clock_info[2] = self.clock_info[2] % 60
# Minutes
if self.clock_info[1] >= 60:
self.clock_info[0] += self.clock_info[1] // 60
self.clock_info[1] = self.clock_info[1] % 60
# Hours
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 """
# If simulating clock, default epoch is Jan 1, 2020 at 8 AM
default_epoch = int(time.mktime(datetime.datetime(2020, 1, 1, 8, 0, 0, 0).timetuple()))
self.clock_info = [8, 0, 0, 0, default_epoch] # [ hour, min, sec, ms, 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):
# Update the global timers (time since agent start)
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)
# Update the clock, either real-time or simulated
if self.sim_clock:
self.advance_clock(self.clock_step_ms)
else:
self.update_clock()
# Update working memory
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')
### Internal methods
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
|