isar 1.20.2__py3-none-any.whl → 1.34.13__py3-none-any.whl

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.
Files changed (134) hide show
  1. isar/apis/api.py +135 -86
  2. isar/apis/models/__init__.py +0 -1
  3. isar/apis/models/models.py +21 -11
  4. isar/apis/models/start_mission_definition.py +115 -170
  5. isar/apis/robot_control/robot_controller.py +41 -0
  6. isar/apis/schedule/scheduling_controller.py +123 -187
  7. isar/apis/security/authentication.py +5 -5
  8. isar/config/certs/ca-cert.pem +33 -31
  9. isar/config/keyvault/keyvault_service.py +4 -2
  10. isar/config/log.py +45 -40
  11. isar/config/logging.conf +16 -31
  12. isar/config/open_telemetry.py +102 -0
  13. isar/config/settings.py +74 -117
  14. isar/eventhandlers/eventhandler.py +123 -0
  15. isar/models/events.py +184 -0
  16. isar/models/status.py +22 -0
  17. isar/modules.py +117 -200
  18. isar/robot/robot.py +383 -0
  19. isar/robot/robot_battery.py +60 -0
  20. isar/robot/robot_monitor_mission.py +357 -0
  21. isar/robot/robot_pause_mission.py +74 -0
  22. isar/robot/robot_resume_mission.py +67 -0
  23. isar/robot/robot_start_mission.py +66 -0
  24. isar/robot/robot_status.py +61 -0
  25. isar/robot/robot_stop_mission.py +68 -0
  26. isar/robot/robot_upload_inspection.py +75 -0
  27. isar/script.py +58 -41
  28. isar/services/service_connections/mqtt/mqtt_client.py +47 -11
  29. isar/services/service_connections/mqtt/robot_heartbeat_publisher.py +5 -2
  30. isar/services/service_connections/mqtt/robot_info_publisher.py +3 -3
  31. isar/services/service_connections/persistent_memory.py +69 -0
  32. isar/services/utilities/mqtt_utilities.py +93 -0
  33. isar/services/utilities/robot_utilities.py +20 -0
  34. isar/services/utilities/scheduling_utilities.py +386 -100
  35. isar/state_machine/state_machine.py +242 -539
  36. isar/state_machine/states/__init__.py +0 -8
  37. isar/state_machine/states/await_next_mission.py +114 -0
  38. isar/state_machine/states/blocked_protective_stop.py +60 -0
  39. isar/state_machine/states/going_to_lockdown.py +95 -0
  40. isar/state_machine/states/going_to_recharging.py +92 -0
  41. isar/state_machine/states/home.py +115 -0
  42. isar/state_machine/states/intervention_needed.py +77 -0
  43. isar/state_machine/states/lockdown.py +38 -0
  44. isar/state_machine/states/maintenance.py +43 -0
  45. isar/state_machine/states/monitor.py +137 -247
  46. isar/state_machine/states/offline.py +51 -53
  47. isar/state_machine/states/paused.py +92 -23
  48. isar/state_machine/states/pausing.py +48 -0
  49. isar/state_machine/states/pausing_return_home.py +48 -0
  50. isar/state_machine/states/recharging.py +80 -0
  51. isar/state_machine/states/resuming.py +57 -0
  52. isar/state_machine/states/resuming_return_home.py +64 -0
  53. isar/state_machine/states/return_home_paused.py +109 -0
  54. isar/state_machine/states/returning_home.py +217 -0
  55. isar/state_machine/states/stopping.py +69 -0
  56. isar/state_machine/states/stopping_due_to_maintenance.py +61 -0
  57. isar/state_machine/states/stopping_go_to_lockdown.py +60 -0
  58. isar/state_machine/states/stopping_go_to_recharge.py +51 -0
  59. isar/state_machine/states/stopping_paused_mission.py +36 -0
  60. isar/state_machine/states/stopping_paused_return_home.py +59 -0
  61. isar/state_machine/states/stopping_return_home.py +59 -0
  62. isar/state_machine/states/unknown_status.py +74 -0
  63. isar/state_machine/states_enum.py +23 -5
  64. isar/state_machine/transitions/mission.py +225 -0
  65. isar/state_machine/transitions/return_home.py +108 -0
  66. isar/state_machine/transitions/robot_status.py +87 -0
  67. isar/state_machine/utils/common_event_handlers.py +138 -0
  68. isar/storage/blob_storage.py +70 -52
  69. isar/storage/local_storage.py +25 -12
  70. isar/storage/storage_interface.py +28 -7
  71. isar/storage/uploader.py +174 -55
  72. isar/storage/utilities.py +32 -29
  73. {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/METADATA +119 -123
  74. isar-1.34.13.dist-info/RECORD +120 -0
  75. {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/WHEEL +1 -1
  76. {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/entry_points.txt +1 -0
  77. robot_interface/models/exceptions/robot_exceptions.py +91 -41
  78. robot_interface/models/inspection/__init__.py +0 -13
  79. robot_interface/models/inspection/inspection.py +42 -33
  80. robot_interface/models/mission/mission.py +14 -15
  81. robot_interface/models/mission/status.py +20 -26
  82. robot_interface/models/mission/task.py +154 -121
  83. robot_interface/models/robots/battery_state.py +6 -0
  84. robot_interface/models/robots/media.py +13 -0
  85. robot_interface/models/robots/robot_model.py +7 -7
  86. robot_interface/robot_interface.py +119 -84
  87. robot_interface/telemetry/mqtt_client.py +74 -12
  88. robot_interface/telemetry/payloads.py +91 -13
  89. robot_interface/utilities/json_service.py +7 -1
  90. isar/config/configuration_error.py +0 -2
  91. isar/config/keyvault/keyvault_error.py +0 -2
  92. isar/config/predefined_mission_definition/__init__.py +0 -0
  93. isar/config/predefined_mission_definition/default_exr.json +0 -51
  94. isar/config/predefined_mission_definition/default_mission.json +0 -91
  95. isar/config/predefined_mission_definition/default_turtlebot.json +0 -124
  96. isar/config/predefined_missions/__init__.py +0 -0
  97. isar/config/predefined_missions/default.json +0 -92
  98. isar/config/predefined_missions/default_turtlebot.json +0 -110
  99. isar/config/predefined_poses/__init__.py +0 -0
  100. isar/config/predefined_poses/predefined_poses.py +0 -616
  101. isar/config/settings.env +0 -25
  102. isar/mission_planner/__init__.py +0 -0
  103. isar/mission_planner/local_planner.py +0 -82
  104. isar/mission_planner/mission_planner_interface.py +0 -26
  105. isar/mission_planner/sequential_task_selector.py +0 -23
  106. isar/mission_planner/task_selector_interface.py +0 -31
  107. isar/models/communication/__init__.py +0 -0
  108. isar/models/communication/message.py +0 -12
  109. isar/models/communication/queues/__init__.py +0 -4
  110. isar/models/communication/queues/queue_io.py +0 -12
  111. isar/models/communication/queues/queue_timeout_error.py +0 -2
  112. isar/models/communication/queues/queues.py +0 -19
  113. isar/models/communication/queues/status_queue.py +0 -20
  114. isar/models/mission_metadata/__init__.py +0 -0
  115. isar/services/auth/__init__.py +0 -0
  116. isar/services/auth/azure_credentials.py +0 -14
  117. isar/services/readers/__init__.py +0 -0
  118. isar/services/readers/base_reader.py +0 -37
  119. isar/services/service_connections/request_handler.py +0 -153
  120. isar/services/service_connections/stid/__init__.py +0 -0
  121. isar/services/utilities/queue_utilities.py +0 -39
  122. isar/services/utilities/threaded_request.py +0 -68
  123. isar/state_machine/states/idle.py +0 -85
  124. isar/state_machine/states/initialize.py +0 -71
  125. isar/state_machine/states/initiate.py +0 -142
  126. isar/state_machine/states/off.py +0 -18
  127. isar/state_machine/states/stop.py +0 -95
  128. isar/storage/slimm_storage.py +0 -191
  129. isar-1.20.2.dist-info/RECORD +0 -116
  130. robot_interface/models/initialize/__init__.py +0 -1
  131. robot_interface/models/initialize/initialize_params.py +0 -9
  132. robot_interface/models/mission/step.py +0 -234
  133. {isar-1.20.2.dist-info → isar-1.34.13.dist-info/licenses}/LICENSE +0 -0
  134. {isar-1.20.2.dist-info → isar-1.34.13.dist-info}/top_level.txt +0 -0
isar/config/log.py CHANGED
@@ -1,66 +1,71 @@
1
1
  import logging
2
2
  import logging.config
3
+ import os
3
4
  from importlib.resources import as_file, files
4
5
 
5
6
  import yaml
6
- from opencensus.ext.azure.log_exporter import AzureLogHandler
7
7
  from uvicorn.logging import ColourizedFormatter
8
8
 
9
- from isar.config.configuration_error import ConfigurationError
10
- from isar.config.keyvault.keyvault_error import KeyvaultError
11
- from isar.config.keyvault.keyvault_service import Keyvault
12
9
  from isar.config.settings import settings
13
10
 
11
+ from .settings import Settings
14
12
 
15
- def setup_loggers(keyvault: Keyvault) -> None:
16
- log_levels: dict = settings.LOG_LEVELS
17
- source = files("isar").joinpath("config").joinpath("logging.conf")
18
- with as_file(source) as f:
19
- log_config = yaml.safe_load(open(f))
13
+
14
+ def setup_loggers() -> None:
15
+ log_config = load_log_config()
20
16
 
21
17
  logging.config.dictConfig(log_config)
22
18
 
19
+ env_log_levels = {}
20
+ for env_var, value in os.environ.items():
21
+ if env_var.endswith("_LOG_LEVEL"):
22
+ log_name = env_var.split("_LOG_LEVEL")[0].lower()
23
+ env_log_levels[log_name] = value.upper()
24
+
23
25
  handlers = []
24
26
  if settings.LOG_HANDLER_LOCAL_ENABLED:
25
- handlers.append(configure_console_handler(log_config=log_config))
26
- if settings.LOG_HANDLER_APPLICATION_INSIGHTS_ENABLED:
27
27
  handlers.append(
28
- configure_azure_handler(log_config=log_config, keyvault=keyvault)
28
+ configure_console_handler(log_config=log_config, settings=settings)
29
29
  )
30
30
 
31
31
  for log_handler in handlers:
32
- for loggers in log_config["loggers"].keys():
33
- logging.getLogger(loggers).addHandler(log_handler)
34
- logging.getLogger(loggers).setLevel(log_levels[loggers])
35
- logging.getLogger().addHandler(log_handler)
32
+ for logger_name, logger_config in log_config["loggers"].items():
33
+ logger = logging.getLogger(logger_name)
34
+ logger.addHandler(log_handler)
35
+ if "level" in logger_config:
36
+ logger.setLevel(logger_config["level"])
37
+ if logger_name in env_log_levels:
38
+ logger.setLevel(env_log_levels[logger_name])
39
+ root_logger = logging.getLogger()
40
+ root_logger.addHandler(log_handler)
41
+ if "level" in log_config.get("root", {}):
42
+ root_logger.setLevel(log_config["root"]["level"])
36
43
 
37
44
 
38
- def configure_console_handler(log_config: dict) -> logging.Handler:
45
+ def load_log_config():
46
+ source = files("isar").joinpath("config").joinpath("logging.conf")
47
+ with as_file(source) as f:
48
+ log_config = yaml.safe_load(open(f))
49
+ return log_config
50
+
51
+
52
+ def configure_console_handler(log_config: dict, settings: Settings) -> logging.Handler:
39
53
  handler = logging.StreamHandler()
40
54
  handler.setLevel(log_config["root"]["level"])
41
- handler.setFormatter(
42
- ColourizedFormatter(
43
- log_config["formatters"]["colourized"]["format"],
44
- style="{",
45
- use_colors=True,
55
+ if settings.DEBUG_LOG_FORMATTER:
56
+ handler.setFormatter(
57
+ ColourizedFormatter(
58
+ log_config["formatters"]["debug-formatter"]["format"],
59
+ style="{",
60
+ use_colors=True,
61
+ )
46
62
  )
47
- )
48
- return handler
49
-
50
-
51
- def configure_azure_handler(log_config: dict, keyvault: Keyvault) -> logging.Handler:
52
- connection_string: str
53
- try:
54
- connection_string = keyvault.get_secret(
55
- "application-insights-connection-string"
56
- ).value
57
- except KeyvaultError:
58
- message: str = (
59
- f"CRITICAL ERROR: Missing connection string for Application Insights in key vault '{keyvault.name}'."
63
+ else:
64
+ handler.setFormatter(
65
+ ColourizedFormatter(
66
+ log_config["formatters"]["colourized"]["format"],
67
+ style="{",
68
+ use_colors=True,
69
+ )
60
70
  )
61
- print(f"\n{message} \n")
62
- raise ConfigurationError(message)
63
-
64
- handler = AzureLogHandler(connection_string=connection_string)
65
- handler.setLevel(log_config["root"]["level"])
66
71
  return handler
isar/config/logging.conf CHANGED
@@ -5,54 +5,39 @@ formatters:
5
5
  colourized:
6
6
  style: "{"
7
7
  format: "{asctime} - {levelprefix:<8} - {name} - {message}"
8
- handlers:
9
- api:
10
- class: logging.FileHandler
11
- formatter: simple
12
- filename: api.log
13
- main:
14
- class: logging.FileHandler
15
- formatter: simple
16
- filename: main.log
17
- mqtt:
18
- class: logging.FileHandler
19
- formatter: simple
20
- filename: mqtt.log
21
- state_machine:
22
- class: logging.FileHandler
23
- formatter: simple
24
- filename: state_machine.log
25
- uploader:
26
- class: logging.FileHandler
27
- formatter: simple
28
- filename: uploader.log
8
+ debug-formatter:
9
+ style: "{"
10
+ format: "\x1b[2m{asctime}\x1b[22m - {levelprefix:<8} - \x1b[2m{filename}:{lineno:<4} - {funcName:<15} - {name}\x1b[22m\n{message}"
29
11
  loggers:
30
12
  console:
31
13
  handlers: []
32
14
  propagate: no
33
15
  main:
34
- handlers: [main]
16
+ handlers: []
35
17
  propagate: no
36
18
  api:
37
- handlers: [api]
19
+ handlers: []
38
20
  propagate: no
39
21
  mqtt:
40
- handlers: [mqtt]
41
- propagate: False
22
+ handlers: []
23
+ propagate: no
42
24
  state_machine:
43
- handlers: [state_machine]
44
- propagate: False
25
+ handlers: []
26
+ propagate: no
45
27
  uploader:
46
- handlers: [uploader]
47
- propagate: False
28
+ handlers: []
29
+ propagate: no
48
30
  urllib3:
49
31
  handlers: []
32
+ level: WARNING
50
33
  uvicorn:
51
- handlers: [api]
34
+ handlers: []
52
35
  propagate: no
36
+ level: WARNING
53
37
  azure:
54
38
  handlers: []
55
39
  propagate: no
40
+ level: WARNING
56
41
  root:
57
- level: DEBUG
42
+ level: INFO
58
43
  handlers: []
@@ -0,0 +1,102 @@
1
+ import logging
2
+ from urllib.parse import urljoin
3
+
4
+ from azure.monitor.opentelemetry.exporter import (
5
+ AzureMonitorLogExporter,
6
+ AzureMonitorTraceExporter,
7
+ )
8
+ from fastapi import FastAPI
9
+ from opentelemetry import trace
10
+ from opentelemetry._logs import set_logger_provider
11
+ from opentelemetry.exporter.otlp.proto.http._log_exporter import (
12
+ OTLPLogExporter as OTLPHttpLogExporter,
13
+ )
14
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
15
+ OTLPSpanExporter as OTLPHttpSpanExporter,
16
+ )
17
+ from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
18
+ from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
19
+ from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
20
+ from opentelemetry.sdk.resources import SERVICE_NAME, Resource
21
+ from opentelemetry.sdk.trace import TracerProvider
22
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
23
+
24
+ from isar.config.log import load_log_config
25
+ from isar.config.settings import settings
26
+
27
+ logging.getLogger("opentelemetry.sdk").setLevel(logging.CRITICAL)
28
+
29
+
30
+ def setup_open_telemetry(app: FastAPI) -> None:
31
+
32
+ service_name = settings.ROBOT_NAME
33
+ resource = Resource.create({SERVICE_NAME: service_name})
34
+
35
+ tracer_provider = TracerProvider(resource=resource)
36
+ log_provider = LoggerProvider(resource=resource)
37
+
38
+ if settings.LOG_HANDLER_APPLICATION_INSIGHTS_ENABLED:
39
+ print("[OTEL] Azure Monitor exporters enabled")
40
+ azure_monitor_trace_exporter, azure_monitor_log_exporter = (
41
+ get_azure_monitor_exporters()
42
+ )
43
+
44
+ tracer_provider.add_span_processor(
45
+ BatchSpanProcessor(azure_monitor_trace_exporter)
46
+ )
47
+
48
+ log_provider.add_log_record_processor(
49
+ BatchLogRecordProcessor(azure_monitor_log_exporter)
50
+ )
51
+
52
+ otlp_exporter_endpoint = settings.OPEN_TELEMETRY_OTLP_EXPORTER_ENDPOINT
53
+ if otlp_exporter_endpoint:
54
+ print(f"[OTEL] OTLP exporters enabled, endpoint={otlp_exporter_endpoint}")
55
+ otlp_trace_exporter, otlp_log_exporter = get_otlp_exporters(
56
+ otlp_exporter_endpoint
57
+ )
58
+ tracer_provider.add_span_processor(BatchSpanProcessor(otlp_trace_exporter))
59
+
60
+ log_provider.add_log_record_processor(
61
+ BatchLogRecordProcessor(otlp_log_exporter)
62
+ )
63
+
64
+ set_logger_provider(log_provider)
65
+ trace.set_tracer_provider(tracer_provider)
66
+
67
+ handler = LoggingHandler(logger_provider=log_provider)
68
+ attach_loggers_for_open_telemetry(handler)
69
+
70
+ FastAPIInstrumentor.instrument_app(app, tracer_provider=tracer_provider)
71
+
72
+
73
+ def attach_loggers_for_open_telemetry(handler: LoggingHandler):
74
+ log_config = load_log_config()
75
+
76
+ for logger_name in log_config["loggers"].keys():
77
+ logger = logging.getLogger(logger_name)
78
+ logger.addHandler(handler)
79
+
80
+
81
+ def get_azure_monitor_exporters() -> (
82
+ tuple[AzureMonitorTraceExporter, AzureMonitorLogExporter]
83
+ ):
84
+ connection_string = settings.APPLICATIONINSIGHTS_CONNECTION_STRING
85
+ trace_exporter = AzureMonitorTraceExporter(connection_string=connection_string)
86
+ log_exporter = AzureMonitorLogExporter(connection_string=connection_string)
87
+
88
+ return trace_exporter, log_exporter
89
+
90
+
91
+ def get_otlp_exporters(
92
+ endpoint: str,
93
+ ) -> tuple[OTLPHttpSpanExporter, OTLPHttpLogExporter]:
94
+ base = endpoint.rstrip("/") + "/"
95
+ trace_ep = urljoin(base, "v1/traces")
96
+ log_ep = urljoin(base, "v1/logs")
97
+
98
+ print("[OTEL] Using HTTP/Protobuf protocol for OpenTelemetry export")
99
+ print(f"[OTEL] traces → {trace_ep}")
100
+ print(f"[OTEL] logs → {log_ep}")
101
+
102
+ return OTLPHttpSpanExporter(endpoint=trace_ep), OTLPHttpLogExporter(endpoint=log_ep)
isar/config/settings.py CHANGED
@@ -1,33 +1,27 @@
1
1
  import os
2
2
  from importlib.resources import as_file, files
3
- from typing import Any, List, Optional
3
+ from typing import Any, List
4
4
 
5
5
  from dotenv import load_dotenv
6
6
  from pydantic import Field, ValidationInfo, field_validator
7
7
  from pydantic_settings import BaseSettings, SettingsConfigDict
8
8
 
9
- from isar.config import predefined_missions
10
9
  from robot_interface.models.robots.robot_model import RobotModel
11
- from robot_interface.telemetry.payloads import VideoStream
10
+ from robot_interface.telemetry.payloads import DocumentInfo
12
11
 
13
12
 
14
13
  class Settings(BaseSettings):
15
- def __init__(self) -> None:
16
- try:
17
- source = files("isar").joinpath("config").joinpath("settings.env")
18
- with as_file(source) as eml:
19
- env_file = eml
20
- except ModuleNotFoundError:
21
- env_file = None
22
- super().__init__(_env_file=env_file)
14
+ # Endpoint open telemetry will export telemetry in the otlp protocol to
15
+ OPEN_TELEMETRY_OTLP_EXPORTER_ENDPOINT: str = Field(default="http://localhost:4318")
16
+
17
+ # Connection string for Azure Application Insights
18
+ # This is optional and it will use managed identity if not set
19
+ APPLICATIONINSIGHTS_CONNECTION_STRING: str = Field(default="")
23
20
 
24
21
  # Determines which robot package ISAR will attempt to import
25
22
  # Name must match with an installed python package in the local environment
26
23
  ROBOT_PACKAGE: str = Field(default="isar_robot")
27
24
 
28
- # The run mode of the robot (stepwise or full mission)
29
- RUN_MISSION_STEPWISE: bool = Field(default=True)
30
-
31
25
  # Determines the local path in which results from missions are stored
32
26
  LOCAL_STORAGE_PATH: str = Field(default="./results")
33
27
 
@@ -41,27 +35,24 @@ class Settings(BaseSettings):
41
35
  # The sleep is used to throttle the system on every iteration in the loop
42
36
  FSM_SLEEP_TIME: float = Field(default=0.1)
43
37
 
44
- # Location of JSON files containing predefined missions for the Local Planner to use
45
- path: str = os.path.dirname(predefined_missions.__file__)
46
- PREDEFINED_MISSIONS_FOLDER: str = Field(default=path + "/")
47
-
48
38
  # Name of default map transformation
49
39
  DEFAULT_MAP: str = Field(default="default_map")
50
40
 
51
- # Location of JSON files containing predefined maps
52
- MAPS_FOLDER: str = Field(default="src/isar/config/maps/")
53
-
54
41
  # Determines the number of state transitions that are kept in the log
55
42
  STATE_TRANSITIONS_LOG_LENGTH: int = Field(default=20)
56
43
 
57
- # Number of attempts to initiate a step or mission before cancelling
58
- INITIATE_FAILURE_COUNTER_LIMIT: int = Field(default=10)
59
-
60
- # Number of attempts to request a step status in monitor before cancelling
44
+ # Number of attempts to request a task status in monitor before cancelling
61
45
  REQUEST_STATUS_FAILURE_COUNTER_LIMIT: int = Field(default=3)
62
46
 
47
+ # Time allocated to reconnect when failing to retrieve status due to communication
48
+ # issues
49
+ REQUEST_STATUS_COMMUNICATION_RECONNECT_DELAY: float = Field(default=10)
50
+
51
+ # Number of attempts for state transitions resume and pause if failed
52
+ STATE_TRANSITION_NUM_RETIRES: int = Field(default=10)
53
+
63
54
  # Number of attempts to stop the robot before giving up
64
- STOP_ROBOT_ATTEMPTS_LIMIT: int = Field(default=10)
55
+ STOP_ROBOT_ATTEMPTS_LIMIT: int = Field(default=3)
65
56
 
66
57
  # Number of attempts to stop the robot before giving up
67
58
  UPLOAD_FAILURE_ATTEMPTS_LIMIT: int = Field(default=10)
@@ -70,10 +61,20 @@ class Settings(BaseSettings):
70
61
  UPLOAD_FAILURE_MAX_WAIT: int = Field(default=60)
71
62
 
72
63
  # ISAR telemetry intervals
73
- ROBOT_STATUS_PUBLISH_INTERVAL: float = Field(default=1)
74
64
  ROBOT_HEARTBEAT_PUBLISH_INTERVAL: float = Field(default=1)
75
65
  ROBOT_INFO_PUBLISH_INTERVAL: float = Field(default=5)
66
+ ROBOT_API_BATTERY_POLL_INTERVAL: float = Field(default=5)
76
67
  ROBOT_API_STATUS_POLL_INTERVAL: float = Field(default=5)
68
+ THREAD_CHECK_INTERVAL: float = Field(default=0.01)
69
+
70
+ # Determines the minimum battery level the robot must have to start a mission
71
+ # If it drops below this level it will recharge to the value set by
72
+ # ROBOT_BATTERY_RECHARGE_THRESHOLD before starting new missions
73
+ ROBOT_MISSION_BATTERY_START_THRESHOLD: float = Field(default=25.0)
74
+
75
+ # Determines the minimum battery threshold to consider the robot recharged
76
+ # and ready for more missions, after having run low on charge
77
+ ROBOT_BATTERY_RECHARGE_THRESHOLD: float = Field(default=80.0)
77
78
 
78
79
  # FastAPI host
79
80
  API_HOST_VIEWED_EXTERNALLY: str = Field(default="0.0.0.0")
@@ -81,12 +82,11 @@ class Settings(BaseSettings):
81
82
  # FastAPI port
82
83
  API_PORT: int = Field(default=3000)
83
84
 
84
- # Determines which mission planner module is used by ISAR
85
- MISSION_PLANNER: str = Field(default="local")
85
+ # Determines how long delay time should be allowed before returning home
86
+ RETURN_HOME_DELAY: int = Field(default=10)
86
87
 
87
- # Determines which task selector module is used by ISAR
88
- # Options: [sequential]
89
- TASK_SELECTOR: str = Field(default="sequential")
88
+ # Sets how many times the robot should try to return home if a return home fails
89
+ RETURN_HOME_RETRY_LIMIT: int = Field(default=5)
90
90
 
91
91
  # Determines which storage modules are used by ISAR
92
92
  # Multiple storage modules can be chosen
@@ -94,7 +94,6 @@ class Settings(BaseSettings):
94
94
  # Selecting a different storage module than local may require certain access rights
95
95
  STORAGE_LOCAL_ENABLED: bool = Field(default=True)
96
96
  STORAGE_BLOB_ENABLED: bool = Field(default=False)
97
- STORAGE_SLIMM_ENABLED: bool = Field(default=False)
98
97
 
99
98
  # Determines whether the MQTT publishing module should be enabled or not
100
99
  # The publishing module will attempt to connect to the MQTT broker configured in
@@ -110,7 +109,7 @@ class Settings(BaseSettings):
110
109
  # Enabling this requires certain resources available for OAuth2 authentication
111
110
  # Currently supported authentication is Azure AD
112
111
  # (https://github.com/Intility/fastapi-azure-auth)
113
- AUTHENTICATION_ENABLED: bool = Field(default=False)
112
+ AUTHENTICATION_ENABLED: bool = Field(default=True)
114
113
 
115
114
  # Tenant ID for the Azure tenant with your Azure Active Directory application
116
115
  AZURE_TENANT_ID: str = Field(default="3aa4a235-b6e2-48d5-9195-7fcf05b459b0")
@@ -140,46 +139,28 @@ class Settings(BaseSettings):
140
139
  # Keyvault name
141
140
  KEYVAULT_NAME: str = Field(default="IsarDevKv")
142
141
 
142
+ # Determines whether inspections are uploaded asynchronously or get_inspections in robotinterface
143
+ UPLOAD_INSPECTIONS_ASYNC: bool = Field(default=False)
144
+
143
145
  # URL to storage account for Azure Blob Storage
144
- BLOB_STORAGE_ACCOUNT_URL: str = Field(
145
- default="https://eqrobotdevstorage.blob.core.windows.net"
146
- )
146
+ BLOB_STORAGE_ACCOUNT: str = Field(default="")
147
147
 
148
148
  # Name of blob container in Azure Blob Storage [slimm test]
149
149
  BLOB_CONTAINER: str = Field(default="test")
150
150
 
151
- # Client ID for SLIMM App Registration
152
- SLIMM_CLIENT_ID: str = Field(default="c630ca4d-d8d6-45ab-8cc6-68a363d0de9e")
153
-
154
- # Scope for access to SLIMM Ingestion API
155
- SLIMM_APP_SCOPE: str = Field(default=".default")
156
-
157
- # URL for SLIMM endpoint
158
- SLIMM_API_URL: str = Field(
159
- default="https://scinspectioningestapitest.azurewebsites.net/Ingest"
160
- )
161
-
162
- # Whether the results should be copied directly into the SLIMM datalake or only the
163
- # metadata
164
- COPY_FILES_TO_SLIMM_DATALAKE: bool = Field(default=False)
151
+ PERSISTENT_STORAGE_CONNECTION_STRING: str = Field(default="")
165
152
 
166
153
  # The configuration of this section is tightly coupled with the metadata that is
167
154
  # submitted with the results once they have been uploaded.
168
155
 
169
156
  # Four digit code indicating facility
170
- PLANT_CODE: str = Field(default="1320")
157
+ PLANT_CODE: str = Field(default="1210")
171
158
 
172
159
  # Name of the facility the robot is operating in
173
- PLANT_NAME: str = Field(default="Kårstø")
160
+ PLANT_NAME: str = Field(default="Huldra")
174
161
 
175
162
  # Shortname of the facility the robot is operating in
176
- PLANT_SHORT_NAME: str = Field(default="KAA")
177
-
178
- # Country the robot is operating in
179
- COUNTRY: str = Field(default="Norway")
180
-
181
- # Type of robot ISAR is monitoring
182
- ROBOT_TYPE: str = Field(default="robot")
163
+ PLANT_SHORT_NAME: str = Field(default="HUA")
183
164
 
184
165
  # Name of robot
185
166
  ROBOT_NAME: str = Field(default="Placebot")
@@ -190,25 +171,8 @@ class Settings(BaseSettings):
190
171
  # Serial number of the robot ISAR is connected to
191
172
  SERIAL_NUMBER: str = Field(default="0001")
192
173
 
193
- # Endpoints to reach video streams for the robot
194
- VIDEO_STREAMS: List[VideoStream] = Field(
195
- default=[
196
- VideoStream(
197
- name="Front camera",
198
- url="http://localhost:5000/videostream/front",
199
- type="turtlebot",
200
- ),
201
- VideoStream(
202
- name="Rear camera",
203
- url="http://localhost:5000/videostream/rear",
204
- type="turtlebot",
205
- ),
206
- ]
207
- )
208
-
209
- # Data scheme the robot should adhere to
210
- # Options [DS0001]
211
- DATA_SCHEME: str = Field(default="DS0001")
174
+ # Info about robot documentation
175
+ DOCUMENTATION: List[DocumentInfo] = Field(default=[])
212
176
 
213
177
  # Coordinate reference system of facility
214
178
  COORDINATE_REFERENCE_SYSTEM: str = Field(default="EQUINOR:4100001")
@@ -220,9 +184,6 @@ class Settings(BaseSettings):
220
184
  # Options [quaternion]
221
185
  MEDIA_ORIENTATION_REFERENCE_SYSTEM: str = Field(default="quaternion")
222
186
 
223
- # Contractor who is responsible for robot missions
224
- CONTRACTOR: str = Field(default="equinor")
225
-
226
187
  # Timezone
227
188
  TIMEZONE: str = Field(default="UTC+00:00")
228
189
 
@@ -232,15 +193,29 @@ class Settings(BaseSettings):
232
193
  # List of MQTT Topics
233
194
  TOPIC_ISAR_STATUS: str = Field(default="status", validate_default=True)
234
195
  TOPIC_ISAR_MISSION: str = Field(default="mission", validate_default=True)
196
+ TOPIC_ISAR_MISSION_ABORTED: str = Field(
197
+ default="aborted_mission", validate_default=True
198
+ )
235
199
  TOPIC_ISAR_TASK: str = Field(default="task", validate_default=True)
236
- TOPIC_ISAR_STEP: str = Field(default="step", validate_default=True)
237
200
  TOPIC_ISAR_INSPECTION_RESULT: str = Field(
238
201
  default="inspection_result", validate_default=True
239
202
  )
203
+ TOPIC_ISAR_INSPECTION_VALUE: str = Field(
204
+ default="inspection_value", validate_default=True
205
+ )
240
206
  TOPIC_ISAR_ROBOT_INFO: str = Field(default="robot_info", validate_default=True)
241
207
  TOPIC_ISAR_ROBOT_HEARTBEAT: str = Field(
242
208
  default="robot_heartbeat", validate_default=True
243
209
  )
210
+ TOPIC_ISAR_STARTUP: str = Field(default="startup", validate_default=True)
211
+ TOPIC_ISAR_INTERVENTION_NEEDED: str = Field(
212
+ default="intervention_needed", validate_default=True
213
+ )
214
+
215
+ # List of MQTT Topics Expiry
216
+ MQTT_ROBOT_HEARTBEAT_EXPIRY: int = Field(default=5)
217
+ MQTT_TELEMETRY_EXPIRY: int = Field(default=10)
218
+ MQTT_MISSION_AND_TASK_EXPIRY: int = Field(default=86400)
244
219
 
245
220
  # Logging
246
221
 
@@ -250,47 +225,29 @@ class Settings(BaseSettings):
250
225
  # Each handler will be called when logging
251
226
  # Selecting a different log handler than local may require certain access rights:
252
227
  # - The Azure AI logger requires the 'APPLICATIONINSIGHTS_CONNECTION_STRING' to be set as an environment variable.
228
+ # In general logging is configured in logging.conf
253
229
  LOG_HANDLER_LOCAL_ENABLED: bool = Field(default=True)
254
230
  LOG_HANDLER_APPLICATION_INSIGHTS_ENABLED: bool = Field(default=False)
255
231
 
256
- # Log levels
257
- API_LOG_LEVEL: str = Field(default="INFO")
258
- MAIN_LOG_LEVEL: str = Field(default="INFO")
259
- MQTT_LOG_LEVEL: str = Field(default="INFO")
260
- STATE_MACHINE_LOG_LEVEL: str = Field(default="INFO")
261
- UPLOADER_LOG_LEVEL: str = Field(default="INFO")
262
- CONSOLE_LOG_LEVEL: str = Field(default="INFO")
263
- URLLIB3_LOG_LEVEL: str = Field(default="WARNING")
264
- UVICORN_LOG_LEVEL: str = Field(default="WARNING")
265
- AZURE_LOG_LEVEL: str = Field(default="WARNING")
232
+ DEBUG_LOG_FORMATTER: bool = Field(default=False)
266
233
 
267
- LOG_LEVELS: dict = Field(default={})
234
+ # You can set logger levels from environment variables ending with _LOG_LEVEL
235
+ # For example, MQTT_LOG_LEVEL="DEBUG" will set the logger MQTT_LOGGER to DEBUG level
236
+ # Handeled in log.py and only work for loggers defined in logging.conf
268
237
 
269
238
  REQUIRED_ROLE: str = Field(default="Mission.Control")
270
239
 
271
- @field_validator("LOG_LEVELS")
272
- @classmethod
273
- def set_log_levels(cls, v: Any, info: ValidationInfo) -> dict:
274
- return {
275
- "api": info.data["API_LOG_LEVEL"],
276
- "main": info.data["MAIN_LOG_LEVEL"],
277
- "mqtt": info.data["MQTT_LOG_LEVEL"],
278
- "state_machine": info.data["STATE_MACHINE_LOG_LEVEL"],
279
- "uploader": info.data["UPLOADER_LOG_LEVEL"],
280
- "console": info.data["CONSOLE_LOG_LEVEL"],
281
- "urllib3": info.data["URLLIB3_LOG_LEVEL"],
282
- "uvicorn": info.data["UVICORN_LOG_LEVEL"],
283
- "azure": info.data["AZURE_LOG_LEVEL"],
284
- }
285
-
286
240
  @field_validator(
287
241
  "TOPIC_ISAR_STATUS",
288
242
  "TOPIC_ISAR_MISSION",
289
243
  "TOPIC_ISAR_TASK",
290
- "TOPIC_ISAR_STEP",
291
244
  "TOPIC_ISAR_ROBOT_INFO",
292
245
  "TOPIC_ISAR_ROBOT_HEARTBEAT",
293
246
  "TOPIC_ISAR_INSPECTION_RESULT",
247
+ "TOPIC_ISAR_INSPECTION_VALUE",
248
+ "TOPIC_ISAR_STARTUP",
249
+ "TOPIC_ISAR_INTERVENTION_NEEDED",
250
+ "TOPIC_ISAR_MISSION_ABORTED",
294
251
  )
295
252
  @classmethod
296
253
  def prefix_isar_topics(cls, v: Any, info: ValidationInfo):
@@ -304,7 +261,12 @@ class Settings(BaseSettings):
304
261
  )
305
262
 
306
263
 
307
- load_dotenv()
264
+ env = os.environ.get("ISAR_ENV")
265
+
266
+ if env == "test":
267
+ load_dotenv(".env.test", override=True)
268
+ else:
269
+ load_dotenv()
308
270
  settings = Settings()
309
271
 
310
272
 
@@ -324,17 +286,12 @@ class RobotSettings(BaseSettings):
324
286
 
325
287
  # ISAR steps the robot is capable of performing
326
288
  # This should be set in the robot package settings.env file
327
- CAPABILITIES: List[str] = Field(default=["drive_to_pose", "take_image"])
289
+ CAPABILITIES: List[str] = Field(default=["take_image"])
328
290
 
329
291
  # Model of the robot which ISAR is connected to
330
292
  # This should be set in the robot package settings.env file
331
293
  ROBOT_MODEL: RobotModel = Field(default=RobotModel.Robot) # type: ignore
332
294
 
333
- # Valid arm poses that the robot may utilize
334
- # This should be set in the robot package settings.env file
335
- # Note that if the robot does not support moving an arm this will be None and
336
- # the functionality will be unavailable
337
- VALID_ARM_POSES: Optional[List[str]] = Field(default=None)
338
295
  model_config = SettingsConfigDict(
339
296
  env_file_encoding="utf-8", case_sensitive=True, extra="ignore"
340
297
  )