diff --git a/src/signage/config/signage_param.yaml b/src/signage/config/signage_param.yaml
index e5d57df..41661b6 100644
--- a/src/signage/config/signage_param.yaml
+++ b/src/signage/config/signage_param.yaml
@@ -10,3 +10,13 @@ signage:
accept_start: 5.0 # second
monitor_width: 1920
monitor_height: 540
+ announce:
+ emergency: true
+ restart_engage: true
+ door_close: true
+ door_open: true
+ engage: true
+ thank_you: true
+ in_emergency: true
+ going_to_depart: true
+ going_to_arrive: true
\ No newline at end of file
diff --git a/src/signage/package.xml b/src/signage/package.xml
index b60e554..ddcd027 100644
--- a/src/signage/package.xml
+++ b/src/signage/package.xml
@@ -11,11 +11,13 @@
signage_version
autoware_auto_system_msgs
+ python-pulsectl-pip
rclpy
tier4_api_msgs
tier4_debug_msgs
tier4_external_api_msgs
tier4_hmi_msgs
+ diagnostic_updater
ament_python
diff --git a/src/signage/resource/sound/arrived.wav b/src/signage/resource/sound/arrived.wav
deleted file mode 100644
index 0fb821a..0000000
Binary files a/src/signage/resource/sound/arrived.wav and /dev/null differ
diff --git a/src/signage/resource/sound/restart_engage.wav b/src/signage/resource/sound/restart_engage.wav
new file mode 100644
index 0000000..96a52ff
Binary files /dev/null and b/src/signage/resource/sound/restart_engage.wav differ
diff --git a/src/signage/resource/sound/wait_for_oncoming_car.wav b/src/signage/resource/sound/wait_for_oncoming_car.wav
deleted file mode 100644
index 200089c..0000000
Binary files a/src/signage/resource/sound/wait_for_oncoming_car.wav and /dev/null differ
diff --git a/src/signage/resource/sound/wait_for_walker.wav b/src/signage/resource/sound/wait_for_walker.wav
deleted file mode 100644
index 1d9ea17..0000000
Binary files a/src/signage/resource/sound/wait_for_walker.wav and /dev/null differ
diff --git a/src/signage/setup.py b/src/signage/setup.py
index 2a89093..875085c 100644
--- a/src/signage/setup.py
+++ b/src/signage/setup.py
@@ -42,6 +42,7 @@ def package_files(directory):
("share/" + package_name, ["package.xml"]),
("share/" + package_name + "/launch", ["launch/signage.launch.xml"]),
("share/" + package_name + "/config", ["config/signage_param.yaml"]),
+ ("share/" + package_name + "/config", ["config/announce_settings.yaml"]),
],
install_requires=["setuptools"],
zip_safe=True,
diff --git a/src/signage/src/signage/announce_controller.py b/src/signage/src/signage/announce_controller.py
index aeeff49..63d85ca 100644
--- a/src/signage/src/signage/announce_controller.py
+++ b/src/signage/src/signage/announce_controller.py
@@ -2,10 +2,14 @@
# -*- coding: utf-8 -*-
# This Python file uses the following encoding: utf-8
+import os
+
from PyQt5.QtMultimedia import QSound
from rclpy.duration import Duration
from ament_index_python.packages import get_package_share_directory
+from dataclasses import asdict
+
# The higher the value, the higher the priority
PRIORITY_DICT = {
"emergency": 3,
@@ -20,6 +24,8 @@
"going_to_arrive": 1,
}
+CURRENT_VOLUME_PATH = "/opt/autoware/volume.txt"
+
class AnnounceControllerProperty:
def __init__(self, node, autoware_interface, parameter_interface):
@@ -27,6 +33,7 @@ def __init__(self, node, autoware_interface, parameter_interface):
self._node = node
self._parameter = parameter_interface.parameter
+ self._announce_settings = parameter_interface.announce_settings
self._current_announce = ""
self._pending_announce_list = []
self._sound = QSound("")
@@ -34,6 +41,18 @@ def __init__(self, node, autoware_interface, parameter_interface):
self._package_path = get_package_share_directory("signage") + "/resource/sound/"
self._check_playing_timer = self._node.create_timer(1, self.check_playing_callback)
+ self._pulse = Pulse()
+ if os.path.isfile(CURRENT_VOLUME_PATH):
+ with open(CURRENT_VOLUME_PATH, "r") as f:
+ self._sink = self._pulse.get_sink_by_name(
+ self._pulse.server_info().default_sink_name
+ )
+ self._pulse.volume_set_all_chans(self._sink, float(f.readline()))
+
+ self._get_volume_pub = self._node.create_publisher(Float32, "~/get/volume", 1)
+ self._node.create_timer(1.0, self.publish_volume_callback)
+ self._node.create_service(SetVolume, "~/set/volume", self.set_volume)
+
def process_pending_announce(self):
try:
for play_sound in self._pending_announce_list:
@@ -62,10 +81,21 @@ def play_sound(self, message):
self._sound = QSound(self._package_path + message + ".wav")
self._sound.play()
+ # skip announce by setting
+ def check_announce_or_not(self, message):
+ try:
+ return asdict(self._announce_settings).get(message, False)
+ except Exception as e:
+ self._node.get_logger().error("check announce or not: " + str(e))
+ return False
+
def send_announce(self, message):
priority = PRIORITY_DICT.get(message, 0)
previous_priority = PRIORITY_DICT.get(self._current_announce, 0)
+ if not self.check_announce_or_not(message):
+ return
+
if priority == 3:
self._sound.stop()
self.play_sound(message)
@@ -100,3 +130,18 @@ def announce_going_to_depart_and_arrive(self, message):
# To stop repeat announcement
self.send_announce(message)
self._prev_depart_and_arrive_type = message
+
+ def publish_volume_callback(self):
+ self._sink = self._pulse.get_sink_by_name(self._pulse.server_info().default_sink_name)
+ self._get_volume_pub.publish(Float32(data=self._sink.volume.value_flat))
+
+ def set_volume(self, request, response):
+ try:
+ self._sink = self._pulse.get_sink_by_name(self._pulse.server_info().default_sink_name)
+ self._pulse.volume_set_all_chans(self._sink, request.volume)
+ with open(CURRENT_VOLUME_PATH, "w") as f:
+ f.write(f"{self._sink.volume.value_flat}\n")
+ response.status.code = ResponseStatus.SUCCESS
+ except Exception:
+ response.status.code = ResponseStatus.ERROR
+ return response
diff --git a/src/signage/src/signage/autoware_diagnostic.py b/src/signage/src/signage/autoware_diagnostic.py
new file mode 100644
index 0000000..ee8650a
--- /dev/null
+++ b/src/signage/src/signage/autoware_diagnostic.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Tier IV, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# ========================================================================
+# This code is used to public the diagnostic to autoware and let autoware
+# to decide the hazard level
+# ========================================================================
+
+import diagnostic_updater
+
+class AutowareDiagnostic():
+ def init_updater(self, node, name, update_function, hardware_id):
+ updater = diagnostic_updater.Updater(node, 1)
+ updater.setHardwareID(hardware_id)
+ updater.add(name, update_function)
+ return updater
diff --git a/src/signage/src/signage/autoware_interface.py b/src/signage/src/signage/autoware_interface.py
index 5152b2c..3286249 100644
--- a/src/signage/src/signage/autoware_interface.py
+++ b/src/signage/src/signage/autoware_interface.py
@@ -9,12 +9,12 @@
OperationModeState,
MotionState,
LocalizationInitializationState,
+ VelocityFactorArray,
)
import signage.signage_utils as utils
from tier4_debug_msgs.msg import Float64Stamped
from tier4_external_api_msgs.msg import DoorStatus
-
@dataclass
class AutowareInformation:
autoware_control: bool = False
@@ -31,6 +31,7 @@ class AutowareInterface:
def __init__(self, node):
self._node = node
self.information = AutowareInformation()
+ self.is_disconnected = False
sub_qos = rclpy.qos.QoSProfile(
history=rclpy.qos.QoSHistoryPolicy.KEEP_LAST,
@@ -81,6 +82,12 @@ def __init__(self, node):
self.sub_localization_initialization_state_callback,
api_qos,
)
+ self._sub_velocity_factors = node.create_subscription(
+ VelocityFactorArray,
+ "/api/planning/velocity_factors",
+ self.sub_velocity_factors_callback,
+ sub_qos,
+ )
self._autoware_connection_time = self._node.get_clock().now()
self._node.create_timer(2, self.reset_timer)
@@ -88,6 +95,9 @@ def reset_timer(self):
if utils.check_timeout(self._node.get_clock().now(), self._autoware_connection_time, 10):
self.information = AutowareInformation()
self._node.get_logger().error("Autoware disconnected", throttle_duration_sec=10)
+ self.is_disconnected = True
+ else:
+ self.is_disconnected = False
def sub_operation_mode_callback(self, msg):
try:
@@ -116,7 +126,6 @@ def sub_vehicle_door_callback(self, msg):
def sub_path_distance_callback(self, msg):
try:
- self._autoware_connection_time = self._node.get_clock().now()
self.information.goal_distance = msg.data
except Exception as e:
self._node.get_logger().error("Unable to get the goal distance, ERROR: " + str(e))
@@ -134,3 +143,9 @@ def sub_localization_initialization_state_callback(self, msg):
self._node.get_logger().error(
"Unable to get the localization init state, ERROR: " + str(e)
)
+
+ def sub_velocity_factors_callback(self, msg):
+ try:
+ self._autoware_connection_time = self._node.get_clock().now()
+ except Exception as e:
+ self._node.get_logger().error("Unable to get the velocity factors, ERROR: " + str(e))
diff --git a/src/signage/src/signage/heartbeat.py b/src/signage/src/signage/heartbeat.py
new file mode 100644
index 0000000..7c83269
--- /dev/null
+++ b/src/signage/src/signage/heartbeat.py
@@ -0,0 +1,20 @@
+# !/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from signage.autoware_diagnostic import AutowareDiagnostic
+from diagnostic_msgs.msg import DiagnosticStatus
+
+class Heartbeat:
+ def __init__(self, node):
+ self._node = node
+
+ self._diagnostic_updater = AutowareDiagnostic().init_updater(
+ self._node,
+ "/system/signage_connection : signage heartbeat",
+ self.handle_heartbeat_diagnostics,
+ "none",
+ )
+
+ def handle_heartbeat_diagnostics(self, stat):
+ stat.summary(DiagnosticStatus.OK, "signage is working")
+ return stat
\ No newline at end of file
diff --git a/src/signage/src/signage/parameter_interface.py b/src/signage/src/signage/parameter_interface.py
index a8ead50..9a50b6a 100644
--- a/src/signage/src/signage/parameter_interface.py
+++ b/src/signage/src/signage/parameter_interface.py
@@ -18,10 +18,23 @@ class SignageParameter:
monitor_width: int = 1920
monitor_height: int = 540
+@dataclass
+class AnnounceParameter:
+ emergency: bool = True
+ restart_engage: bool = True
+ door_close: bool = True
+ door_open: bool = True
+ engage: bool = True
+ arrived: bool = True
+ thank_you: bool = True
+ in_emergency: bool = True
+ going_to_depart: bool = True
+ going_to_arrive: bool = True
class ParameterInterface:
def __init__(self, node):
self.parameter = SignageParameter()
+ self.announce_settings = AnnounceParameter()
node.declare_parameter("signage_stand_alone", False)
node.declare_parameter("ignore_manual_driving", False)
@@ -64,3 +77,23 @@ def __init__(self, node):
self.parameter.monitor_height = (
node.get_parameter("monitor_height").get_parameter_value().integer_value
)
+
+ node.declare_parameter("announce.emergency", True)
+ node.declare_parameter("announce.restart_engage", True)
+ node.declare_parameter("announce.door_close", True)
+ node.declare_parameter("announce.door_open", True)
+ node.declare_parameter("announce.engage", True)
+ node.declare_parameter("announce.thank_you", True)
+ node.declare_parameter("announce.in_emergency", True)
+ node.declare_parameter("announce.going_to_depart", True)
+ node.declare_parameter("gannounce.oing_to_arrive", True)
+
+ announce_prefix = node.get_parameters_by_prefix("announce")
+
+ for key in announce_prefix.keys():
+ setattr(
+ self.announce_settings,
+ key,
+ announce_prefix[key].get_parameter_value().bool_value,
+ )
+
diff --git a/src/signage/src/signage/route_handler.py b/src/signage/src/signage/route_handler.py
index 2025054..f714b2e 100644
--- a/src/signage/src/signage/route_handler.py
+++ b/src/signage/src/signage/route_handler.py
@@ -141,7 +141,7 @@ def announce_engage_when_starting(self):
self._engage_trigger_time,
self._parameter.accept_start,
):
- self._announce_interface.send_announce("engage")
+ self._announce_interface.send_announce("restart_engage")
self._engage_trigger_time = self._node.get_clock().now()
if self._autoware.information.motion_state == MotionState.STARTING:
@@ -370,7 +370,9 @@ def view_mode_callback(self):
self._viewController.next_station_list = self._display_details.next_station_list
self._viewController.display_phrase = self._display_phrase
- if (
+ if self._autoware.is_disconnected:
+ view_mode = "emergency_stopped"
+ elif (
not self._autoware.information.autoware_control
and not self._parameter.ignore_manual_driving
):
diff --git a/src/signage/src/signage/signage.py b/src/signage/src/signage/signage.py
index 146e666..71f427a 100644
--- a/src/signage/src/signage/signage.py
+++ b/src/signage/src/signage/signage.py
@@ -7,6 +7,7 @@
import rclpy
from rclpy.node import Node
+from signage.heartbeat import Heartbeat
from signage.view_controller import ViewControllerProperty
from signage.announce_controller import AnnounceControllerProperty
from signage.autoware_interface import AutowareInterface
@@ -25,6 +26,8 @@ def main(args=None):
app = QApplication(sys.argv)
engine = QQmlApplicationEngine()
+ heartbeat = Heartbeat(node)
+ autoware_interface = AutowareInterface(node)
autoware_interface = AutowareInterface(node)
parameter_interface = ParameterInterface(node)
ros_service_interface = RosServiceInterface(node, parameter_interface)