PyAres 0.1.5__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyares-0.1.5 → pyares-0.2.0}/PKG-INFO +1 -1
- {pyares-0.1.5 → pyares-0.2.0}/PyAres/Analyzing/analysis_service.py +13 -5
- {pyares-0.1.5/PyAres/Test → pyares-0.2.0/PyAres/Demo}/analyzer_test.py +2 -0
- pyares-0.2.0/PyAres/Demo/analyzer_test_tools_demo.py +19 -0
- pyares-0.2.0/PyAres/Demo/analyzer_wiki.py +31 -0
- pyares-0.2.0/PyAres/Demo/hotplate.py +72 -0
- pyares-0.2.0/PyAres/Demo/planner_test_tools_demo.py +21 -0
- pyares-0.2.0/PyAres/Demo/planner_wiki.py +29 -0
- pyares-0.2.0/PyAres/Demo/rotary_mixer.py +37 -0
- {pyares-0.1.5 → pyares-0.2.0}/PyAres/Device/device_service.py +23 -6
- {pyares-0.1.5 → pyares-0.2.0}/PyAres/Models/ares_data_models.py +7 -1
- {pyares-0.1.5 → pyares-0.2.0}/PyAres/Planning/planner_models.py +28 -10
- {pyares-0.1.5 → pyares-0.2.0}/PyAres/Planning/planning_service.py +13 -5
- {pyares-0.1.5 → pyares-0.2.0}/PyAres/Utils/ares_data_schema_utils.py +5 -13
- {pyares-0.1.5 → pyares-0.2.0}/PyAres/Utils/ares_data_type_utils.py +2 -2
- {pyares-0.1.5 → pyares-0.2.0}/PyAres/Utils/ares_outcome_utils.py +1 -1
- {pyares-0.1.5 → pyares-0.2.0}/PyAres/Utils/ares_struct_utils.py +32 -16
- {pyares-0.1.5 → pyares-0.2.0}/PyAres/Utils/ares_value_utils.py +16 -13
- pyares-0.2.0/PyAres/Utils/param_history_info_utils.py +10 -0
- pyares-0.2.0/PyAres/Utils/plan_request_utils.py +11 -0
- pyares-0.2.0/PyAres/Utils/plan_response_utils.py +16 -0
- pyares-0.2.0/PyAres/Utils/planning_param_utils.py +20 -0
- pyares-0.2.0/PyAres/test_tools/__init__.py +2 -0
- pyares-0.2.0/PyAres/test_tools/analyzer_tester.py +92 -0
- pyares-0.2.0/PyAres/test_tools/planner_tester.py +86 -0
- {pyares-0.1.5 → pyares-0.2.0}/PyAres.egg-info/PKG-INFO +1 -1
- {pyares-0.1.5 → pyares-0.2.0}/PyAres.egg-info/SOURCES.txt +17 -4
- {pyares-0.1.5 → pyares-0.2.0}/README.md +3 -1
- {pyares-0.1.5 → pyares-0.2.0}/pyproject.toml +1 -1
- {pyares-0.1.5 → pyares-0.2.0}/LICENSE +0 -0
- {pyares-0.1.5 → pyares-0.2.0}/PyAres/Analyzing/__init__.py +0 -0
- {pyares-0.1.5 → pyares-0.2.0}/PyAres/Analyzing/analyzer_models.py +0 -0
- {pyares-0.1.5/PyAres/Test → pyares-0.2.0/PyAres/Demo}/device_test.py +0 -0
- {pyares-0.1.5/PyAres/Test → pyares-0.2.0/PyAres/Demo}/planner_test.py +0 -0
- {pyares-0.1.5 → pyares-0.2.0}/PyAres/Device/__init__.py +0 -0
- {pyares-0.1.5 → pyares-0.2.0}/PyAres/Device/device_models.py +0 -0
- {pyares-0.1.5 → pyares-0.2.0}/PyAres/Device/device_warnings.py +0 -0
- {pyares-0.1.5 → pyares-0.2.0}/PyAres/Models/__init__.py +0 -0
- {pyares-0.1.5 → pyares-0.2.0}/PyAres/Planning/__init__.py +0 -0
- {pyares-0.1.5 → pyares-0.2.0}/PyAres/Utils/ares_device_command_utils.py +0 -0
- {pyares-0.1.5 → pyares-0.2.0}/PyAres/__init__.py +0 -0
- {pyares-0.1.5 → pyares-0.2.0}/PyAres.egg-info/dependency_links.txt +0 -0
- {pyares-0.1.5 → pyares-0.2.0}/PyAres.egg-info/requires.txt +0 -0
- {pyares-0.1.5 → pyares-0.2.0}/PyAres.egg-info/top_level.txt +0 -0
- {pyares-0.1.5 → pyares-0.2.0}/setup.cfg +0 -0
- {pyares-0.1.5 → pyares-0.2.0}/setup.py +0 -0
|
@@ -245,13 +245,21 @@ class AresAnalyzerService:
|
|
|
245
245
|
"""
|
|
246
246
|
self._capabilities.timeout_seconds = new_timeout
|
|
247
247
|
|
|
248
|
-
def start(self):
|
|
249
|
-
"""
|
|
250
|
-
Starts the service on the specified port, and waits for termination.
|
|
248
|
+
def start(self, wait_for_termination: bool = True):
|
|
249
|
+
"""
|
|
250
|
+
Starts the service on the specified port, and waits for termination.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
wait_for_termination (bool): A boolean value that determines whether the start method will use the "wait_for_termination" blocking call.
|
|
254
|
+
If true, the gRPC service will keep the main thread alive but at the cost of blocking any continued execution of your python logic.
|
|
255
|
+
Setting this value to false will allow you to continue execution after starting your service, however this should ONLY be done if you have
|
|
256
|
+
another mechanism for keeping your process alive (such as a GUI, or a loop). Defaults to true.
|
|
251
257
|
"""
|
|
252
|
-
print(f"Starting Ares
|
|
258
|
+
print(f"Starting Ares Device Service on port {self._port}...")
|
|
253
259
|
self._server.start()
|
|
254
|
-
|
|
260
|
+
|
|
261
|
+
if wait_for_termination:
|
|
262
|
+
self._server.wait_for_termination()
|
|
255
263
|
|
|
256
264
|
def stop(self):
|
|
257
265
|
"""
|
|
@@ -23,5 +23,7 @@ if __name__ == "__main__":
|
|
|
23
23
|
|
|
24
24
|
#Add Analysis Parameters
|
|
25
25
|
pythonDemoAnalyzer.add_analysis_parameter("Temperature", AresDataType.NUMBER)
|
|
26
|
+
pythonDemoAnalyzer.add_setting(setting_name="", setting_type=AresDataType.NULL, optional=True, constraints=[])
|
|
27
|
+
pythonDemoAnalyzer.start(wait_for_termination=True)
|
|
26
28
|
|
|
27
29
|
pythonDemoAnalyzer.start()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from PyAres.test_tools import AnalyzerTestClient
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
if __name__ == "__main__":
|
|
5
|
+
client = AnalyzerTestClient()
|
|
6
|
+
|
|
7
|
+
# 1. Health Checks
|
|
8
|
+
client.check_status()
|
|
9
|
+
client.get_info()
|
|
10
|
+
|
|
11
|
+
# 2. Run a Mock Analysis
|
|
12
|
+
# Replace these keys with actual data your analyzer expects
|
|
13
|
+
sample_inputs = {
|
|
14
|
+
"Temperature": 130.5
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
client.run_analysis(inputs=sample_inputs)
|
|
18
|
+
|
|
19
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from PyAres import AresAnalyzerService, AnalysisRequest, Analysis, AresDataType, Outcome
|
|
2
|
+
|
|
3
|
+
def analyze_sample(request: AnalysisRequest) -> Analysis:
|
|
4
|
+
# 1. Extract inputs
|
|
5
|
+
# 'Growth_Metric' would come from a sensor or previous step
|
|
6
|
+
raw_value = request.inputs.get("Growth_Metric")
|
|
7
|
+
|
|
8
|
+
if raw_value is None:
|
|
9
|
+
return Analysis(result=0.0, outcome=Outcome.FAILURE)
|
|
10
|
+
|
|
11
|
+
# 2. Perform Logic
|
|
12
|
+
print(f"Analyzing sample with value: {raw_value}")
|
|
13
|
+
|
|
14
|
+
calculated_score = raw_value * 1.5 # Placeholder logic
|
|
15
|
+
is_success = calculated_score > 10.0 # Define success criteria
|
|
16
|
+
|
|
17
|
+
# 3. Return Result
|
|
18
|
+
return Analysis(result=calculated_score, outcome=Outcome.SUCCESS)
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
service = AresAnalyzerService(
|
|
22
|
+
analyze_sample,
|
|
23
|
+
"Growth Analyzer",
|
|
24
|
+
"0.1.0",
|
|
25
|
+
"Calculates growth viability"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Define what data we need from ARES
|
|
29
|
+
service.add_analysis_parameter("Growth_Metric", AresDataType.NUMBER)
|
|
30
|
+
|
|
31
|
+
service.start()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from PyAres import AresDeviceService, AresDataType, DeviceSchemaEntry, DeviceCommandDescriptor
|
|
2
|
+
|
|
3
|
+
# --- PART 1: The Simulated Hardware ---
|
|
4
|
+
class VirtualHotplate:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self.target_temp = 25.0 # Start at room temp
|
|
7
|
+
|
|
8
|
+
def set_temperature(self, temp: float):
|
|
9
|
+
"""Simulates setting the heater."""
|
|
10
|
+
print(f"[Hardware] Heating to {temp}°C...")
|
|
11
|
+
self.target_temp = temp
|
|
12
|
+
return {} # Return empty dict if no data needs to be sent back
|
|
13
|
+
|
|
14
|
+
def get_temperature(self):
|
|
15
|
+
"""Simulates reading the sensor."""
|
|
16
|
+
# In a real device, you'd read a serial port here.
|
|
17
|
+
print("[Hardware] Retrieving the current temperature...")
|
|
18
|
+
return { "current_temp": self.target_temp }
|
|
19
|
+
|
|
20
|
+
def get_state(self):
|
|
21
|
+
"""Required: Tells ARES the current status for logging."""
|
|
22
|
+
return { "current_temp": self.target_temp }
|
|
23
|
+
|
|
24
|
+
def safe_mode(self):
|
|
25
|
+
"""Required: A safety fallback (e.g., turn off heat)."""
|
|
26
|
+
print("[Hardware] SAFE MODE TRIGGERED: Heater off.")
|
|
27
|
+
self.target_temp = 0.0
|
|
28
|
+
|
|
29
|
+
# --- PART 2: The Ares Service Wrapper ---
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
# 1. Initialize the hardware
|
|
32
|
+
my_hotplate = VirtualHotplate()
|
|
33
|
+
|
|
34
|
+
# 2. Define the Service Info
|
|
35
|
+
service = AresDeviceService(
|
|
36
|
+
my_hotplate.safe_mode,
|
|
37
|
+
my_hotplate.get_state,
|
|
38
|
+
"My Virtual Hotplate", # Device Name
|
|
39
|
+
"A simulated lab hotplate", # Description
|
|
40
|
+
"1.0.0" # Version
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# 3. Define Command: Set Temperature
|
|
44
|
+
# This schema tells ARES to draw a Number Input box in the UI
|
|
45
|
+
input_schema = {
|
|
46
|
+
"temp": DeviceSchemaEntry(AresDataType.NUMBER, "Target Temperature", "Celsius")
|
|
47
|
+
}
|
|
48
|
+
set_cmd = DeviceCommandDescriptor(
|
|
49
|
+
"Set Temp",
|
|
50
|
+
"Sets the hotplate target temperature",
|
|
51
|
+
input_schema,
|
|
52
|
+
{} # No output expected
|
|
53
|
+
)
|
|
54
|
+
service.add_new_command(set_cmd, my_hotplate.set_temperature)
|
|
55
|
+
|
|
56
|
+
# 4. Define Command: Get Temperature
|
|
57
|
+
# This schema tells ARES to expect a number back
|
|
58
|
+
output_schema = {
|
|
59
|
+
"current_temp": DeviceSchemaEntry(AresDataType.NUMBER, "Current Temperature", "Celsius")
|
|
60
|
+
}
|
|
61
|
+
get_cmd = DeviceCommandDescriptor(
|
|
62
|
+
"Get Temp",
|
|
63
|
+
"Reads the current temperature",
|
|
64
|
+
{}, # No input needed
|
|
65
|
+
output_schema
|
|
66
|
+
)
|
|
67
|
+
service.add_new_command(get_cmd, my_hotplate.get_temperature)
|
|
68
|
+
|
|
69
|
+
# 5. Start the Service
|
|
70
|
+
# This will block and listen for ARES connections
|
|
71
|
+
print("Virtual Hotplate Service Running...")
|
|
72
|
+
service.start()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from PyAres import PlanRequest, PlanningParameter, AresDataType, ParameterHistoryItem
|
|
2
|
+
from PyAres.test_tools import PlannerTestClient
|
|
3
|
+
|
|
4
|
+
if __name__ == "__main__":
|
|
5
|
+
client = PlannerTestClient()
|
|
6
|
+
|
|
7
|
+
# 1. Health Checks
|
|
8
|
+
client.check_status()
|
|
9
|
+
client.get_info()
|
|
10
|
+
|
|
11
|
+
# 2. Mock Data
|
|
12
|
+
planning_parameters = []
|
|
13
|
+
planning_parameters.append(PlanningParameter("Param One", 0, 100, [ParameterHistoryItem(123, 128)], AresDataType.NUMBER, False, False, "Random Planner"))
|
|
14
|
+
planning_parameters.append(PlanningParameter("Param Two", 0, 500, [ParameterHistoryItem(132, 122)], AresDataType.NUMBER, False, False, "Random Planner"))
|
|
15
|
+
planning_parameters.append(PlanningParameter("Param Three", 0, 500, [ParameterHistoryItem(141, 121)], AresDataType.NUMBER, False, False, "Random Planner"))
|
|
16
|
+
setting_dict = {"Random Setting" : 4000}
|
|
17
|
+
analysis_results = [10, 20, 30]
|
|
18
|
+
|
|
19
|
+
# 3. Create and Send the Request
|
|
20
|
+
request = PlanRequest(planning_parameters, setting_dict, analysis_results)
|
|
21
|
+
response = client.run_planning(request)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from PyAres import AresPlannerService, PlanRequest, PlanResponse, AresDataType
|
|
2
|
+
import random
|
|
3
|
+
|
|
4
|
+
def generate_plan(request: PlanRequest) -> PlanResponse:
|
|
5
|
+
planned_values = []
|
|
6
|
+
names = []
|
|
7
|
+
|
|
8
|
+
# Iterate through every parameter configured in the ARES Experiment
|
|
9
|
+
for param in request.parameters:
|
|
10
|
+
# Simple Logic: Pick a random value within the allowed range
|
|
11
|
+
val = random.uniform(param.minimum_value, param.maximum_value)
|
|
12
|
+
|
|
13
|
+
names.append(param.name)
|
|
14
|
+
planned_values.append(val)
|
|
15
|
+
|
|
16
|
+
return PlanResponse(parameter_names=names, parameter_values=planned_values)
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
service = AresPlannerService(
|
|
20
|
+
generate_plan,
|
|
21
|
+
"Random Search Planner",
|
|
22
|
+
"This planner picks random values within bounds.",
|
|
23
|
+
"1.0.0"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Tell ARES we can plan for Numeric values
|
|
27
|
+
service.add_supported_type(AresDataType.NUMBER)
|
|
28
|
+
|
|
29
|
+
service.start()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from PyAres import AresDeviceService, AresDataType, DeviceSchemaEntry, DeviceCommandDescriptor
|
|
2
|
+
|
|
3
|
+
# 1. Define your hardware logic
|
|
4
|
+
def set_speed(rpm: float):
|
|
5
|
+
print(f"Setting motor speed to {rpm}")
|
|
6
|
+
# Hardware communication goes here...
|
|
7
|
+
return {} # Return empty dict if no data needs to be sent back
|
|
8
|
+
|
|
9
|
+
def get_status():
|
|
10
|
+
# Return a dictionary matching your state schema
|
|
11
|
+
return { "rpm": 1200 }
|
|
12
|
+
|
|
13
|
+
def safe_mode():
|
|
14
|
+
print("Stopping motor immediately!")
|
|
15
|
+
|
|
16
|
+
# 2. Initialize Service
|
|
17
|
+
service = AresDeviceService(
|
|
18
|
+
safe_mode,
|
|
19
|
+
get_status,
|
|
20
|
+
"Rotary Mixer",
|
|
21
|
+
"High-speed mixer control",
|
|
22
|
+
"1.0.0",
|
|
23
|
+
port=7101
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# 3. Define the 'Set Speed' Command
|
|
27
|
+
# Input: One number (Speed)
|
|
28
|
+
input_schema = {
|
|
29
|
+
"rpm": DeviceSchemaEntry(AresDataType.NUMBER, "Speed in RPM", "RPM")
|
|
30
|
+
}
|
|
31
|
+
cmd_descriptor = DeviceCommandDescriptor("Set Speed", "Sets mixer speed", input_schema, {})
|
|
32
|
+
|
|
33
|
+
# 4. Register the command
|
|
34
|
+
service.add_new_command(cmd_descriptor, set_speed)
|
|
35
|
+
|
|
36
|
+
# 5. Start
|
|
37
|
+
service.start()
|
|
@@ -23,8 +23,8 @@ from ..Utils import ares_data_type_utils
|
|
|
23
23
|
|
|
24
24
|
# Type hint for the user's custom methods
|
|
25
25
|
EnterSafeModeMethod = Callable[[], None]
|
|
26
|
-
DeviceCommandMethod = Callable[..., Dict[str,
|
|
27
|
-
DeviceStateMethod = Callable[[], Dict[str,
|
|
26
|
+
DeviceCommandMethod = Callable[..., Dict[str, Any]]
|
|
27
|
+
DeviceStateMethod = Callable[[], Dict[str, Any]]
|
|
28
28
|
|
|
29
29
|
class AresDeviceServiceWrapper(device_service_grpc.AresRemoteDeviceServiceServicer):
|
|
30
30
|
"""
|
|
@@ -211,7 +211,14 @@ class AresDeviceServiceWrapper(device_service_grpc.AresRemoteDeviceServiceServic
|
|
|
211
211
|
|
|
212
212
|
class AresDeviceService:
|
|
213
213
|
""" Manages the gRPC service for the AresDeviceSerivce """
|
|
214
|
-
def __init__(self,
|
|
214
|
+
def __init__(self,
|
|
215
|
+
enter_safe_mode_logic: EnterSafeModeMethod,
|
|
216
|
+
get_device_state_logic: DeviceStateMethod,
|
|
217
|
+
device_name: str,
|
|
218
|
+
description: str,
|
|
219
|
+
version: str,
|
|
220
|
+
use_localhost: bool = True,
|
|
221
|
+
port: int = 7100):
|
|
215
222
|
"""
|
|
216
223
|
Initializes the AresDeviceService
|
|
217
224
|
|
|
@@ -273,11 +280,21 @@ class AresDeviceService:
|
|
|
273
280
|
new_ares_value = ares_value_utils.create_ares_value(setting_value)
|
|
274
281
|
self._service_wrapper._current_settings[setting_name] = new_ares_value
|
|
275
282
|
|
|
276
|
-
def start(self):
|
|
277
|
-
"""
|
|
283
|
+
def start(self, wait_for_termination: bool = True):
|
|
284
|
+
"""
|
|
285
|
+
Starts the service on the specified port, and waits for termination.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
wait_for_termination (bool): A boolean value that determines whether the start method will use the "wait_for_termination" blocking call.
|
|
289
|
+
If true, the gRPC service will keep the main thread alive but at the cost of blocking any continued execution of your python logic.
|
|
290
|
+
Setting this value to false will allow you to continue execution after starting your service, however this should ONLY be done if you have
|
|
291
|
+
another mechanism for keeping your process alive (such as a GUI, or a loop). Defaults to true.
|
|
292
|
+
"""
|
|
278
293
|
print(f"Starting Ares Device Service on port {self._port}...")
|
|
279
294
|
self._server.start()
|
|
280
|
-
|
|
295
|
+
|
|
296
|
+
if wait_for_termination:
|
|
297
|
+
self._server.wait_for_termination()
|
|
281
298
|
|
|
282
299
|
def stop(self):
|
|
283
300
|
""" Stops the service, terminating the connection. """
|
|
@@ -24,4 +24,10 @@ class RequestMetadata():
|
|
|
24
24
|
self.system_name = proto_metadata.system_name
|
|
25
25
|
self.campaign_name = proto_metadata.campaign_name
|
|
26
26
|
self.campaign_id = proto_metadata.campaign_id
|
|
27
|
-
self.experiment_id = proto_metadata.experiment_id
|
|
27
|
+
self.experiment_id = proto_metadata.experiment_id
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_default_values(cls):
|
|
31
|
+
""" Alternative constructor for creating fake metadata """
|
|
32
|
+
default = request_metadata_pb2.RequestMetadata(system_name="TEST SYSTEM", campaign_name="TEST CAMPAIGN", campaign_id="TEST ID", experiment_id="TEST EXPERIMENT ID")
|
|
33
|
+
return cls(default)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Dict, Any, List
|
|
1
|
+
from typing import Dict, Any, List, Sequence
|
|
2
2
|
from ..Models import Outcome, AresDataType, RequestMetadata
|
|
3
3
|
|
|
4
4
|
class ParameterHistoryItem:
|
|
@@ -38,23 +38,41 @@ class PlanningParameter:
|
|
|
38
38
|
planner_name: The name of the planner ARES requested be used to plan for this parameter.
|
|
39
39
|
initial_value: An optional initial value for the given parameter
|
|
40
40
|
"""
|
|
41
|
-
self.name
|
|
42
|
-
self.minimum_value
|
|
43
|
-
self.maximum_value
|
|
44
|
-
self.param_history
|
|
45
|
-
self.data_type
|
|
46
|
-
self.is_planned
|
|
47
|
-
self.is_result
|
|
48
|
-
self.planner_name
|
|
41
|
+
self.name: str = name
|
|
42
|
+
self.minimum_value: float = minimum_value
|
|
43
|
+
self.maximum_value: float = maximum_value
|
|
44
|
+
self.param_history: List = param_history
|
|
45
|
+
self.data_type: AresDataType = data_type
|
|
46
|
+
self.is_planned: bool = is_planned
|
|
47
|
+
self.is_result: bool = is_result
|
|
48
|
+
self.planner_name: str = planner_name
|
|
49
49
|
self.initial_value = initial_value
|
|
50
50
|
|
|
51
|
+
|
|
52
|
+
class ParamHistoryInfo:
|
|
53
|
+
"""
|
|
54
|
+
Represents the history of a given parameter.
|
|
55
|
+
|
|
56
|
+
Designed to provide a more user-friendly abstraction for interacting with a param history object.
|
|
57
|
+
"""
|
|
58
|
+
def __init__(self, planned_value: Any, achieved_value: Any):
|
|
59
|
+
"""
|
|
60
|
+
Initializes a ParamHistoryInfo.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
planned_value (Any): The value given directly from the planner.
|
|
64
|
+
achieved_value (Any): An optional value that represents the real world achieved value, which may differ from the planners target value.
|
|
65
|
+
"""
|
|
66
|
+
self.planned_value = planned_value
|
|
67
|
+
self.achieved_value = achieved_value
|
|
68
|
+
|
|
51
69
|
class PlanRequest:
|
|
52
70
|
"""
|
|
53
71
|
Represents a PlanRequest message received from ARES.
|
|
54
72
|
|
|
55
73
|
Designed to provide a more user-friendly abstraction for interacting with a plan request message.
|
|
56
74
|
"""
|
|
57
|
-
def __init__(self, parameters: list[PlanningParameter], settings: Dict[str, Any], analysis_results:
|
|
75
|
+
def __init__(self, parameters: list[PlanningParameter], settings: Dict[str, Any], analysis_results: Sequence[float], metadata: RequestMetadata = RequestMetadata.from_default_values()):
|
|
58
76
|
"""
|
|
59
77
|
Initializes a PlanRequest.
|
|
60
78
|
|
|
@@ -248,13 +248,21 @@ class AresPlannerService:
|
|
|
248
248
|
"""
|
|
249
249
|
self._service_wrapper._timeout = new_timeout
|
|
250
250
|
|
|
251
|
-
def start(self):
|
|
252
|
-
"""
|
|
253
|
-
Starts the service on the specified port, and waits for termination.
|
|
251
|
+
def start(self, wait_for_termination: bool = True):
|
|
252
|
+
"""
|
|
253
|
+
Starts the service on the specified port, and waits for termination.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
wait_for_termination (bool): A boolean value that determines whether the start method will use the "wait_for_termination" blocking call.
|
|
257
|
+
If true, the gRPC service will keep the main thread alive but at the cost of blocking any continued execution of your python logic.
|
|
258
|
+
Setting this value to false will allow you to continue execution after starting your service, however this should ONLY be done if you have
|
|
259
|
+
another mechanism for keeping your process alive (such as a GUI, or a loop). Defaults to true.
|
|
254
260
|
"""
|
|
255
|
-
print(f"Starting Ares
|
|
261
|
+
print(f"Starting Ares Device Service on port {self._port}...")
|
|
256
262
|
self._server.start()
|
|
257
|
-
|
|
263
|
+
|
|
264
|
+
if wait_for_termination:
|
|
265
|
+
self._server.wait_for_termination()
|
|
258
266
|
|
|
259
267
|
def stop(self):
|
|
260
268
|
"""
|
|
@@ -1,18 +1,6 @@
|
|
|
1
|
-
from typing import Union
|
|
2
|
-
|
|
3
|
-
#Datamodel Imports
|
|
1
|
+
from typing import Union
|
|
4
2
|
from ares_datamodel import ares_data_schema_pb2
|
|
5
|
-
|
|
6
3
|
from ..Models import ares_data_models
|
|
7
|
-
# def ares_schema_to_dict(schema: ares_data_schema_pb2.AresDataSchema) -> dict:
|
|
8
|
-
# """Converts an AresDataSchemaSimplified to a dictionary for user logic."""
|
|
9
|
-
# result = {}
|
|
10
|
-
# for key, entry in schema.fields.items():
|
|
11
|
-
# result[key] = {
|
|
12
|
-
# "type": ares_data_schema_pb2.AresDataType.Name(entry.type),
|
|
13
|
-
# "is_array": entry.is_array
|
|
14
|
-
# }
|
|
15
|
-
# return result
|
|
16
4
|
|
|
17
5
|
def create_settings_schema_entry(
|
|
18
6
|
setting_type: ares_data_models.AresDataType,
|
|
@@ -40,5 +28,9 @@ def create_settings_schema_entry(
|
|
|
40
28
|
schema_entry = ares_data_schema_pb2.SchemaEntry(type=setting_type.value, optional=optional)
|
|
41
29
|
schema_entry.number_choices.numbers.extend(choices)
|
|
42
30
|
|
|
31
|
+
else:
|
|
32
|
+
schema_entry = ares_data_schema_pb2.SchemaEntry(type=setting_type.value, optional=optional)
|
|
33
|
+
|
|
34
|
+
|
|
43
35
|
return schema_entry
|
|
44
36
|
|
|
@@ -4,7 +4,7 @@ from ..Models import AresDataType
|
|
|
4
4
|
|
|
5
5
|
def python_ares_type_to_proto_ares_type(py_value: AresDataType) -> ares_data_type_pb2.AresDataType:
|
|
6
6
|
""" A method to convert from the python AresDataType class to the protobuf version """
|
|
7
|
-
return py_value.value
|
|
7
|
+
return ares_data_type_pb2.AresDataType(py_value.value)
|
|
8
8
|
|
|
9
9
|
def proto_ares_type_to_python_ares_type(proto_value: ares_data_type_pb2.AresDataType) -> AresDataType:
|
|
10
10
|
""" A method to convert from the protobuf AresDataType class to the python version """
|
|
@@ -28,7 +28,7 @@ def determine_python_ares_data_type(value: Union[int, float, str, bool, list]):
|
|
|
28
28
|
return AresDataType.STRING_ARRAY
|
|
29
29
|
elif(all(isinstance(x, (int, float)) for x in value)):
|
|
30
30
|
return AresDataType.NUMBER_ARRAY
|
|
31
|
-
elif(all(isinstance(x, bool)
|
|
31
|
+
elif(all(isinstance(x, bool) for x in value)):
|
|
32
32
|
return AresDataType.BOOL_ARRAY
|
|
33
33
|
else:
|
|
34
34
|
return AresDataType.UNKNOWN
|
|
@@ -3,7 +3,7 @@ from ares_datamodel import ares_outcome_enum_pb2
|
|
|
3
3
|
|
|
4
4
|
def python_ares_outcome_to_proto_ares_outcome(py_value: Outcome) -> ares_outcome_enum_pb2.Outcome:
|
|
5
5
|
""" A method to convert from the python AresDataType class to the protobuf version """
|
|
6
|
-
return py_value.value
|
|
6
|
+
return ares_outcome_enum_pb2.Outcome(py_value.value)
|
|
7
7
|
|
|
8
8
|
def proto_ares_outcome_to_python_ares_outcome(proto_value: ares_outcome_enum_pb2.Outcome) -> Outcome:
|
|
9
9
|
""" A method to convert from the protobuf AresDataType class to the python version """
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from ares_datamodel import ares_struct_pb2
|
|
2
|
-
from typing import Union, Dict
|
|
2
|
+
from typing import Union, Dict, Any
|
|
3
3
|
|
|
4
4
|
from . import ares_value_utils
|
|
5
5
|
import copy
|
|
6
6
|
|
|
7
|
-
def ares_struct_to_dict(ares_struct: ares_struct_pb2.AresStruct) -> Dict[str,
|
|
7
|
+
def ares_struct_to_dict(ares_struct: ares_struct_pb2.AresStruct) -> Dict[str, Any]:
|
|
8
8
|
"""Converts an AresStruct protobuf message to a Python dictionary."""
|
|
9
9
|
return {k: ares_value_utils.ares_value_to_py(v) for k, v in ares_struct.fields.items()}
|
|
10
10
|
|
|
@@ -62,6 +62,9 @@ def create_bool_struct(key: str, value: bool) -> ares_struct_pb2.AresStruct:
|
|
|
62
62
|
Returns:
|
|
63
63
|
(AresStruct): A new AresStruct containing the provided key and value.
|
|
64
64
|
"""
|
|
65
|
+
new_struct = ares_struct_pb2.AresStruct()
|
|
66
|
+
new_struct.fields[key] = ares_value_utils.create_bool(value)
|
|
67
|
+
return new_struct
|
|
65
68
|
|
|
66
69
|
def create_null_struct(key: str) -> ares_struct_pb2.AresStruct:
|
|
67
70
|
"""
|
|
@@ -168,7 +171,16 @@ def copy_struct(existing_struct: ares_struct_pb2.AresStruct) -> ares_struct_pb2.
|
|
|
168
171
|
"""
|
|
169
172
|
return copy.deepcopy(existing_struct)
|
|
170
173
|
|
|
171
|
-
def
|
|
174
|
+
def create_empty_struct() -> ares_struct_pb2.AresStruct:
|
|
175
|
+
"""
|
|
176
|
+
Creates a new empty AresStruct
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
(AresStruct): The newly created empty AresStruct.
|
|
180
|
+
"""
|
|
181
|
+
return ares_struct_pb2.AresStruct()
|
|
182
|
+
|
|
183
|
+
def create_ares_struct(key: str, value: Any):
|
|
172
184
|
"""
|
|
173
185
|
Creates a new AresStruct using the provided key and value.
|
|
174
186
|
|
|
@@ -181,28 +193,32 @@ def create_ares_struct(key: str, value: any):
|
|
|
181
193
|
"""
|
|
182
194
|
|
|
183
195
|
if(isinstance(value, str)):
|
|
184
|
-
return create_string_struct(value)
|
|
196
|
+
return create_string_struct(key, value)
|
|
185
197
|
|
|
186
198
|
elif(isinstance(value, (int, float))):
|
|
187
|
-
return create_number_struct(value)
|
|
199
|
+
return create_number_struct(key, value)
|
|
188
200
|
|
|
189
201
|
elif(isinstance(value, bool)):
|
|
190
|
-
return create_bool_struct(value)
|
|
191
|
-
|
|
192
|
-
elif(isinstance(value, (list[int], list[float]))):
|
|
193
|
-
return create_number_array_struct(value)
|
|
202
|
+
return create_bool_struct(key, value)
|
|
194
203
|
|
|
195
204
|
elif(isinstance(value, bytes)):
|
|
196
|
-
return create_bytes_array_struct(value)
|
|
197
|
-
|
|
198
|
-
elif(isinstance(value, list[str])):
|
|
199
|
-
return create_string_array_struct(value)
|
|
205
|
+
return create_bytes_array_struct(key, value)
|
|
200
206
|
|
|
201
|
-
elif(isinstance(value, list
|
|
202
|
-
|
|
207
|
+
elif(isinstance(value, list)):
|
|
208
|
+
if(len(value) == 0):
|
|
209
|
+
return create_null_struct(key)
|
|
210
|
+
|
|
211
|
+
elif(all(isinstance(item, str) for item in value)):
|
|
212
|
+
return create_string_array_struct(key, value)
|
|
213
|
+
|
|
214
|
+
elif(all(isinstance(item, (float, int)) for item in value)):
|
|
215
|
+
return create_number_array_struct(key, value)
|
|
216
|
+
|
|
217
|
+
elif(all(isinstance(item, bool) for item in value)):
|
|
218
|
+
return create_bool_array_struct(key, value)
|
|
203
219
|
|
|
204
220
|
else:
|
|
205
|
-
return create_null_struct()
|
|
221
|
+
return create_null_struct(key)
|
|
206
222
|
|
|
207
223
|
|
|
208
224
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
from ares_datamodel import ares_struct_pb2
|
|
2
2
|
from ares_datamodel import ares_data_type_pb2
|
|
3
|
-
from typing import Union
|
|
3
|
+
from typing import Union, Any
|
|
4
4
|
|
|
5
|
-
from . import ares_struct_utils
|
|
6
5
|
from . import ares_data_type_utils
|
|
7
6
|
from ..Models import AresDataType
|
|
8
7
|
|
|
@@ -33,14 +32,13 @@ def py_to_ares_value(py_value, ares_value: ares_struct_pb2.AresValue):
|
|
|
33
32
|
ares_value.number_value = py_value
|
|
34
33
|
elif isinstance(py_value, bytes):
|
|
35
34
|
ares_value.bytes_value = py_value
|
|
36
|
-
elif isinstance(py_value, dict):
|
|
37
|
-
ares_struct_utils.dict_to_ares_struct(py_value, ares_value.struct_value)
|
|
38
35
|
elif isinstance(py_value, list):
|
|
39
|
-
for
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
if(all(isinstance(x, str) for x in py_value)):
|
|
37
|
+
ares_value.string_array_value.strings.extend(py_value)
|
|
38
|
+
elif(all(isinstance(x, (int, float)) for x in py_value)):
|
|
39
|
+
ares_value.number_array_value.numbers.extend(py_value)
|
|
40
|
+
elif(all(isinstance(x, bool) for x in py_value)):
|
|
41
|
+
ares_value.bool_array_value.bools.extend(py_value)
|
|
44
42
|
else:
|
|
45
43
|
raise TypeError(f"Unsupported type for AresValue: {type(py_value)}")
|
|
46
44
|
|
|
@@ -137,7 +135,9 @@ def create_bool_array(value: list[bool]) -> ares_struct_pb2.AresValue:
|
|
|
137
135
|
Returns:
|
|
138
136
|
(AresValue): A new AresValue containing the provided list of booleans.
|
|
139
137
|
"""
|
|
140
|
-
|
|
138
|
+
ares_value = ares_struct_pb2.AresValue()
|
|
139
|
+
ares_value.bool_array_value.bools.extend(value)
|
|
140
|
+
return ares_value
|
|
141
141
|
|
|
142
142
|
def create_default(python_datatype: AresDataType) -> ares_struct_pb2.AresValue:
|
|
143
143
|
"""
|
|
@@ -177,7 +177,7 @@ def create_default(python_datatype: AresDataType) -> ares_struct_pb2.AresValue:
|
|
|
177
177
|
else:
|
|
178
178
|
return create_null()
|
|
179
179
|
|
|
180
|
-
def create_ares_value(value:
|
|
180
|
+
def create_ares_value(value: Any) -> ares_struct_pb2.AresValue:
|
|
181
181
|
"""
|
|
182
182
|
Creates a new AresValue using the provided value.
|
|
183
183
|
If the provided value is not valid for use in an AresValue, a null AresValue is returned.
|
|
@@ -198,7 +198,7 @@ def create_ares_value(value: any) -> ares_struct_pb2.AresValue:
|
|
|
198
198
|
return create_bool(value)
|
|
199
199
|
|
|
200
200
|
elif(isinstance(value, bytes)):
|
|
201
|
-
return
|
|
201
|
+
return create_bytes(value)
|
|
202
202
|
|
|
203
203
|
elif(isinstance(value, list)):
|
|
204
204
|
if all(isinstance(x, (int, float)) for x in value):
|
|
@@ -209,6 +209,9 @@ def create_ares_value(value: any) -> ares_struct_pb2.AresValue:
|
|
|
209
209
|
|
|
210
210
|
elif all(isinstance(x, bool) for x in value):
|
|
211
211
|
return create_bool_array(value)
|
|
212
|
-
|
|
212
|
+
|
|
213
|
+
else:
|
|
214
|
+
return create_null()
|
|
215
|
+
|
|
213
216
|
else:
|
|
214
217
|
return create_null()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from ..Planning import ParameterHistoryItem
|
|
2
|
+
from ares_datamodel.planning import plan_pb2
|
|
3
|
+
from . import ares_value_utils
|
|
4
|
+
|
|
5
|
+
def convert_proto_param_history_to_python(proto: plan_pb2.ParameterHistoryInfo) -> ParameterHistoryItem:
|
|
6
|
+
return ParameterHistoryItem(proto.planned_value, proto.achieved_value)
|
|
7
|
+
|
|
8
|
+
def convert_python_param_history_to_proto(python: ParameterHistoryItem) -> plan_pb2.ParameterHistoryInfo:
|
|
9
|
+
info = plan_pb2.ParameterHistoryInfo(planned_value=ares_value_utils.create_ares_value(python.planned_value), achieved_value=ares_value_utils.create_ares_value(python.achieved_value))
|
|
10
|
+
return info
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from ares_datamodel.planning import plan_pb2
|
|
2
|
+
from ..Planning import PlanRequest
|
|
3
|
+
from . import planning_param_utils
|
|
4
|
+
from . import ares_struct_utils
|
|
5
|
+
|
|
6
|
+
def python_plan_request_to_proto(request: PlanRequest) -> plan_pb2.PlanningRequest:
|
|
7
|
+
proto_request = plan_pb2.PlanningRequest()
|
|
8
|
+
proto_request.analysis_results.extend(request.analysis_results)
|
|
9
|
+
ares_struct_utils.dict_to_ares_struct(request.settings, proto_request.adapter_settings)
|
|
10
|
+
proto_request.planning_parameters.extend([planning_param_utils.convert_python_plan_param_to_proto(p) for p in request.parameters])
|
|
11
|
+
return proto_request
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from ares_datamodel.planning import plan_pb2
|
|
2
|
+
from ..Planning import PlanResponse
|
|
3
|
+
from . import ares_outcome_utils
|
|
4
|
+
|
|
5
|
+
def proto_plan_response_to_python(proto_response: plan_pb2.PlanningResponse) -> PlanResponse:
|
|
6
|
+
param_names = []
|
|
7
|
+
param_values = []
|
|
8
|
+
|
|
9
|
+
for param in proto_response.planned_parameters:
|
|
10
|
+
param_names.append(param.parameter_name)
|
|
11
|
+
param_values.append(param.parameter_value)
|
|
12
|
+
|
|
13
|
+
python_response = PlanResponse(param_names, param_values)
|
|
14
|
+
python_response.outcome = ares_outcome_utils.proto_ares_outcome_to_python_ares_outcome(proto_response.planning_outcome)
|
|
15
|
+
python_response.error_string = proto_response.error_string
|
|
16
|
+
return python_response
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from ..Planning import PlanningParameter, PlanResponse
|
|
2
|
+
from ares_datamodel.planning import plan_pb2
|
|
3
|
+
from typing import List
|
|
4
|
+
from . import param_history_info_utils
|
|
5
|
+
from . import ares_data_type_utils
|
|
6
|
+
from . import ares_value_utils
|
|
7
|
+
|
|
8
|
+
def convert_python_plan_param_to_proto(param: PlanningParameter) -> plan_pb2.PlanningParameter:
|
|
9
|
+
new_proto_param = plan_pb2.PlanningParameter()
|
|
10
|
+
new_proto_param.parameter_name = param.name
|
|
11
|
+
new_proto_param.minimum_value = param.minimum_value
|
|
12
|
+
new_proto_param.maximum_value = param.maximum_value
|
|
13
|
+
new_proto_param.parameter_history.extend([param_history_info_utils.convert_python_param_history_to_proto(p) for p in param.param_history])
|
|
14
|
+
new_proto_param.data_type = ares_data_type_utils.python_ares_type_to_proto_ares_type(param.data_type)
|
|
15
|
+
new_proto_param.is_planned = param.is_planned
|
|
16
|
+
new_proto_param.is_result = param.is_result
|
|
17
|
+
new_proto_param.planner_name = param.planner_name
|
|
18
|
+
ares_value_utils.py_to_ares_value(param.initial_value, new_proto_param.initial_value)
|
|
19
|
+
|
|
20
|
+
return new_proto_param
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import grpc
|
|
2
|
+
import time
|
|
3
|
+
from typing import Dict
|
|
4
|
+
|
|
5
|
+
from ..Utils import ares_struct_utils
|
|
6
|
+
from ares_datamodel.analyzing.remote import ares_remote_analyzer_service_pb2_grpc as analyzer_service_grpc
|
|
7
|
+
from ares_datamodel.analyzing.remote import ares_remote_analyzer_service_pb2 as analyzer_service
|
|
8
|
+
from ares_datamodel import ares_analyzer_management_service_pb2
|
|
9
|
+
from ares_datamodel.connection import connection_status_pb2
|
|
10
|
+
from ares_datamodel.analyzing import analysis_pb2
|
|
11
|
+
from ares_datamodel import ares_outcome_enum_pb2
|
|
12
|
+
|
|
13
|
+
class AnalyzerTestClient:
|
|
14
|
+
def __init__(self, port=7083, host='localhost'):
|
|
15
|
+
"""
|
|
16
|
+
Initializes an AnalyzerTestClient, useful for testing your PyAres Analyzers.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
port (int): The port that your analyzer test client will use to try and reach your service. Defaults to 7083.
|
|
20
|
+
host (str): The host name that the client will use to try and reach your service. Defaults to localhost.
|
|
21
|
+
"""
|
|
22
|
+
self.target = f"{host}:{port}"
|
|
23
|
+
self.channel = grpc.insecure_channel(self.target)
|
|
24
|
+
self.stub = analyzer_service_grpc.AresRemoteAnalyzerServiceStub(self.channel)
|
|
25
|
+
print(f"Client connected to {self.target}")
|
|
26
|
+
|
|
27
|
+
def get_info(self):
|
|
28
|
+
"""Used to test that your Analyzer Service is providing info properly. If successful outputs the name, version and description of your service."""
|
|
29
|
+
print("\n--- Testing GetInfo ---")
|
|
30
|
+
try:
|
|
31
|
+
request = ares_analyzer_management_service_pb2.AnalyzerInfoRequest()
|
|
32
|
+
response = self.stub.GetInfo(request)
|
|
33
|
+
print(f"Success! Connected to: {response.name} (version {response.version})")
|
|
34
|
+
print(f"Description: {response.description}")
|
|
35
|
+
|
|
36
|
+
except grpc.RpcError as e:
|
|
37
|
+
print(f"RPC Failed: {e.code()} - {e.details()}")
|
|
38
|
+
|
|
39
|
+
def check_status(self):
|
|
40
|
+
"""Checks the status of your Analyzer Service"""
|
|
41
|
+
print("\n--- Testing Connection Status ---")
|
|
42
|
+
try:
|
|
43
|
+
request = connection_status_pb2.ConnectionStatusRequest()
|
|
44
|
+
response: connection_status_pb2.ConnectionStatus = self.stub.GetConnectionStatus(request)
|
|
45
|
+
print(f"Status: {response.status}")
|
|
46
|
+
|
|
47
|
+
except grpc.RpcError as e:
|
|
48
|
+
print(f"RPC Failed: {e.code()} - {e.details()}")
|
|
49
|
+
|
|
50
|
+
def run_analysis(self, inputs: Dict, settings: Dict = { }):
|
|
51
|
+
"""
|
|
52
|
+
This method is responsible for testing your analysis loop. Simply provide whatever values your analyzer needs to test your logic.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
inputs (Dict): The inputs required by your analyzer. Ensure the names (keys) match exactly as your analyzer expects, otherwise analysis will fail
|
|
56
|
+
settings (Dict): A dictionary that contains any specific settings you want to pass to your analyzer. Defaults to an empty dictionary.
|
|
57
|
+
"""
|
|
58
|
+
print(f"\n--- Testing Analysis ---")
|
|
59
|
+
if settings is None:
|
|
60
|
+
settings = {}
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
input_struct = ares_struct_utils.create_empty_struct()
|
|
64
|
+
ares_struct_utils.dict_to_ares_struct(inputs, input_struct)
|
|
65
|
+
|
|
66
|
+
settings_struct = ares_struct_utils.create_empty_struct()
|
|
67
|
+
ares_struct_utils.dict_to_ares_struct(settings, input_struct)
|
|
68
|
+
|
|
69
|
+
request = analyzer_service.AnalysisRequest(inputs=input_struct, settings=settings_struct)
|
|
70
|
+
|
|
71
|
+
start_time = time.perf_counter()
|
|
72
|
+
response: analysis_pb2.Analysis = self.stub.Analyze(request)
|
|
73
|
+
end_time = time.perf_counter()
|
|
74
|
+
|
|
75
|
+
if response.analysis_outcome == ares_outcome_enum_pb2.SUCCESS:
|
|
76
|
+
print(f"Analysis Successful!")
|
|
77
|
+
print(f"Result: {response.result}")
|
|
78
|
+
|
|
79
|
+
elif response.analysis_outcome == ares_outcome_enum_pb2.WARNING:
|
|
80
|
+
print("Analysis was successful, but analyzer emitted a warning.")
|
|
81
|
+
print(f"Result: {response.result}")
|
|
82
|
+
print(f"#### WARNING: {response.error_string} #####")
|
|
83
|
+
|
|
84
|
+
else:
|
|
85
|
+
print(f"Analysis Failed (Outcome: {response.analysis_outcome})")
|
|
86
|
+
print(f"Error: {response.error_string}")
|
|
87
|
+
|
|
88
|
+
elapsed_time = end_time - start_time
|
|
89
|
+
print(f"Analysis request was completed in: {elapsed_time:.4f} seconds")
|
|
90
|
+
|
|
91
|
+
except grpc.RpcError as e:
|
|
92
|
+
print(f"RPC Failed: {e.code()} - {e.details()}")
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import grpc
|
|
2
|
+
import time
|
|
3
|
+
from ..Utils import plan_request_utils, plan_response_utils
|
|
4
|
+
from ..Planning import PlanRequest, PlanResponse
|
|
5
|
+
from ..Models import Outcome
|
|
6
|
+
from ares_datamodel.planning.remote import ares_remote_planner_service_pb2_grpc as planner_service_grpc
|
|
7
|
+
from ares_datamodel import ares_planner_management_service_pb2
|
|
8
|
+
from ares_datamodel.connection import connection_status_pb2
|
|
9
|
+
from ares_datamodel.planning import plan_pb2
|
|
10
|
+
from ares_datamodel import ares_outcome_enum_pb2
|
|
11
|
+
from ares_datamodel.connection import connection_info_pb2
|
|
12
|
+
|
|
13
|
+
class PlannerTestClient:
|
|
14
|
+
def __init__(self, port=7082, host='localhost'):
|
|
15
|
+
"""
|
|
16
|
+
Initializes an PlannerTestClient, useful for testing your PyAres Planners.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
port (int): The port that your planner test client will use to try and reach your service. Defaults to 7083.
|
|
20
|
+
host (str): The host name that the client will use to try and reach your service. Defaults to localhost.
|
|
21
|
+
"""
|
|
22
|
+
self.target = f"{host}:{port}"
|
|
23
|
+
self.channel = grpc.insecure_channel(self.target)
|
|
24
|
+
self.stub = planner_service_grpc.AresRemotePlannerServiceStub(self.channel)
|
|
25
|
+
print(f"Client connected to {self.target}")
|
|
26
|
+
|
|
27
|
+
def get_info(self):
|
|
28
|
+
"""Used to test that your Planner Service is providing info properly. If successful outputs the name, version and description of your service."""
|
|
29
|
+
print("\n--- Testing GetInfo ---")
|
|
30
|
+
try:
|
|
31
|
+
request = ares_planner_management_service_pb2.PlannerInfoRequest()
|
|
32
|
+
response: connection_info_pb2.InfoResponse = self.stub.GetInfo(request)
|
|
33
|
+
print(f"Success! Connected to: {response.name} (version {response.version})")
|
|
34
|
+
print(f"Description: {response.description}")
|
|
35
|
+
|
|
36
|
+
except grpc.RpcError as e:
|
|
37
|
+
print(f"RPC Failed: {e.code()} - {e.details()}")
|
|
38
|
+
|
|
39
|
+
def check_status(self):
|
|
40
|
+
"""Checks the status of your Planner Service"""
|
|
41
|
+
print("\n--- Testing Connection Status ---")
|
|
42
|
+
try:
|
|
43
|
+
request = connection_status_pb2.ConnectionStatusRequest()
|
|
44
|
+
response: connection_status_pb2.ConnectionStatus = self.stub.GetConnectionStatus(request)
|
|
45
|
+
print(f"Status: {response.status}")
|
|
46
|
+
|
|
47
|
+
except grpc.RpcError as e:
|
|
48
|
+
print(f"RPC Failed: {e.code()} - {e.details()}")
|
|
49
|
+
|
|
50
|
+
def run_planning(self, request: PlanRequest) -> PlanResponse:
|
|
51
|
+
"""
|
|
52
|
+
This method is responsible for testing your planning loop. Simply provide whatever values your planner needs to test your logic.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
request (PlanRequest): The self crafted plan request. This should include all relevant data for your planner.
|
|
56
|
+
"""
|
|
57
|
+
print("\n--- Testing Planning Logic ---")
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
proto_request = plan_request_utils.python_plan_request_to_proto(request)
|
|
61
|
+
|
|
62
|
+
start_time = time.perf_counter()
|
|
63
|
+
response: plan_pb2.PlanningResponse = self.stub.Plan(proto_request)
|
|
64
|
+
end_time = time.perf_counter()
|
|
65
|
+
|
|
66
|
+
if response.planning_outcome == ares_outcome_enum_pb2.SUCCESS:
|
|
67
|
+
print(f"Planning Successful!")
|
|
68
|
+
|
|
69
|
+
elif response.planning_outcome == ares_outcome_enum_pb2.WARNING:
|
|
70
|
+
print("Planning was successful, but planner emitted a warning.")
|
|
71
|
+
print(f"#### WARNING: {response.error_string} #####")
|
|
72
|
+
|
|
73
|
+
else:
|
|
74
|
+
print(f"Planning Failed (Outcome: {response.planning_outcome})")
|
|
75
|
+
print(f"Error: {response.error_string}")
|
|
76
|
+
|
|
77
|
+
elapsed_time = end_time - start_time
|
|
78
|
+
print(f"Planning request was completed in: {elapsed_time:.4f} seconds")
|
|
79
|
+
|
|
80
|
+
return plan_response_utils.proto_plan_response_to_python(response)
|
|
81
|
+
|
|
82
|
+
except grpc.RpcError as e:
|
|
83
|
+
print(f"RPC Failed: {e.code()} - {e.details()}")
|
|
84
|
+
return PlanResponse([], [], error_string="GRPC FAILURE", planning_outcome=Outcome.FAILURE)
|
|
85
|
+
|
|
86
|
+
|
|
@@ -12,6 +12,15 @@ PyAres.egg-info/top_level.txt
|
|
|
12
12
|
PyAres/Analyzing/__init__.py
|
|
13
13
|
PyAres/Analyzing/analysis_service.py
|
|
14
14
|
PyAres/Analyzing/analyzer_models.py
|
|
15
|
+
PyAres/Demo/analyzer_test.py
|
|
16
|
+
PyAres/Demo/analyzer_test_tools_demo.py
|
|
17
|
+
PyAres/Demo/analyzer_wiki.py
|
|
18
|
+
PyAres/Demo/device_test.py
|
|
19
|
+
PyAres/Demo/hotplate.py
|
|
20
|
+
PyAres/Demo/planner_test.py
|
|
21
|
+
PyAres/Demo/planner_test_tools_demo.py
|
|
22
|
+
PyAres/Demo/planner_wiki.py
|
|
23
|
+
PyAres/Demo/rotary_mixer.py
|
|
15
24
|
PyAres/Device/__init__.py
|
|
16
25
|
PyAres/Device/device_models.py
|
|
17
26
|
PyAres/Device/device_service.py
|
|
@@ -21,12 +30,16 @@ PyAres/Models/ares_data_models.py
|
|
|
21
30
|
PyAres/Planning/__init__.py
|
|
22
31
|
PyAres/Planning/planner_models.py
|
|
23
32
|
PyAres/Planning/planning_service.py
|
|
24
|
-
PyAres/Test/analyzer_test.py
|
|
25
|
-
PyAres/Test/device_test.py
|
|
26
|
-
PyAres/Test/planner_test.py
|
|
27
33
|
PyAres/Utils/ares_data_schema_utils.py
|
|
28
34
|
PyAres/Utils/ares_data_type_utils.py
|
|
29
35
|
PyAres/Utils/ares_device_command_utils.py
|
|
30
36
|
PyAres/Utils/ares_outcome_utils.py
|
|
31
37
|
PyAres/Utils/ares_struct_utils.py
|
|
32
|
-
PyAres/Utils/ares_value_utils.py
|
|
38
|
+
PyAres/Utils/ares_value_utils.py
|
|
39
|
+
PyAres/Utils/param_history_info_utils.py
|
|
40
|
+
PyAres/Utils/plan_request_utils.py
|
|
41
|
+
PyAres/Utils/plan_response_utils.py
|
|
42
|
+
PyAres/Utils/planning_param_utils.py
|
|
43
|
+
PyAres/test_tools/__init__.py
|
|
44
|
+
PyAres/test_tools/analyzer_tester.py
|
|
45
|
+
PyAres/test_tools/planner_tester.py
|
|
@@ -146,5 +146,7 @@ The central component to your PyAres device is your AresDeviceService. This clas
|
|
|
146
146
|
### 📄 License
|
|
147
147
|
|
|
148
148
|
The PyAres project is licensed under the MIT License - see details in [LICENSE.txt]([https://github.com/AFRL-ARES/PyAres/blob/Develop/LICENSE.txt](https://github.com/AFRL-ARES/PyAres/blob/Develop/LICENSE)) <br></br>
|
|
149
|
-
|
|
149
|
+
|
|
150
|
+
### CLEARANCE
|
|
151
|
+
Distribution A. Approved for public release: distribution unlimited. AFRL-2025-5332.
|
|
150
152
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|