#
# Copyright (c) 2020-2025 Julian Heinovski <heinovski@ccs-labs.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
import logging
from statistics import mean
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from plafosim.platooning_vehicle import PlatooningVehicle # noqa 401
LOG = logging.getLogger(__name__)
[docs]class Platoon:
"""
A collection of parameters for a specific platoon.
"""
[docs] def __init__(
self,
platoon_id: int,
formation: list,
desired_speed: float,
):
"""
Initialize a platoon instance.
Parameters
----------
platoon_id : int
The id of the platoon
formation : list
The list of PlatooningVehicles within the platoon
desired_speed : float
The platoon's desired driving speed
"""
self._platoon_id = platoon_id # the id of the platoon
#TODO convert to dict?
self._formation = formation # the current formation of the platoon
self._desired_speed = desired_speed # the current (desired) speed of the platoon
self._max_speed = None
self.update_max_speed()
self._max_acceleration = None
self.update_max_acceleration()
self._max_deceleration = None
self.update_max_deceleration()
@property
def platoon_id(self) -> int:
"""
Return the id of the platoon.
"""
return self._platoon_id
@property
def leader(self) -> 'PlatooningVehicle':
"""
Return the leading PlatoonVehicle of the platoon.
"""
return self._formation[0]
@property
def last(self) -> 'PlatooningVehicle':
"""
Return the last PlatooningVehicle of the platoon.
"""
return self._formation[-1]
@property
def formation(self) -> list:
"""
Return the complete formation of the platoon.
"""
return self._formation
@property
def member_ids(self) -> list:
"""
Return the ids of all platoon members.
"""
return [x._vid for x in self._formation]
@property
def desired_speed(self) -> float:
"""
Return the desired driving speed of the platoon.
"""
return self._desired_speed
@property
def speed(self) -> float:
"""
Return the current driving speed of the platoon.
"""
# HACK for keeping the speed up to date
return self.leader.speed
@property
def lane(self) -> int:
"""
Return the current lane of the platoon.
"""
# HACK for keeping the lane up to date
return self.leader.lane
@property
def max_speed(self) -> float:
"""
Return the maximum speed of the platoon.
The maximum speed is based on the slowest vehicle within the platoon.
"""
return self._max_speed
[docs] def update_max_speed(self):
"""
Update the maximum speed of the platoon.
The maximum speed is based on the slowest vehicle within the platoon.
"""
self._max_speed = min(v.max_speed for v in self._formation)
@property
def max_acceleration(self) -> float:
"""
Return the maximum acceleration of the platoon.
The maximum acceleration is based on the slowest vehicle within the platoon.
"""
return self._max_acceleration
[docs] def update_max_acceleration(self):
"""
Update the maximum acceleration of the platoon.
The maximum acceleration is based on the slowest vehicle within the platoon.
"""
self._max_acceleration = min(v.max_acceleration for v in self._formation)
@property
def max_deceleration(self) -> float:
"""
Return the maximum deceleration of the platoon.
The maximum deceleration is based on the slowest vehicle within the platoon.
"""
return self._max_deceleration
[docs] def update_max_deceleration(self):
"""
Update the maximum deceleration of the platoon.
The maximum deceleration is based on the slowest vehicle within the platoon.
"""
self._max_deceleration = min(v.max_deceleration for v in self._formation)
@property
def size(self) -> int:
"""
Return the size of the platoon.
"""
return len(self._formation)
@property
def position(self) -> int:
"""
Return the current position of the platoon.
"""
# HACK for keeping the position up to date
return self.leader.position
@property
def rear_position(self) -> int:
"""
Return the current rear position of the platoon.
"""
# HACK for keeping the rear position up to date
return self.last.rear_position
@property
def length(self) -> float:
"""
Return the length of the platoon.
"""
return self.position - self.rear_position
[docs] def get_member_index(self, vehicle: 'PlatooningVehicle') -> int:
"""
Return the index of a member within the platoon.
Parameters
----------
vehicle : PlatooningVehicle
The considered vehicle within the platoon.
Returns
-------
int : The index of the member within the platoon
"""
return self._formation.index(vehicle)
[docs] def get_front(self, vehicle: 'PlatooningVehicle'):
"""
Return the PlatooningVehicle in the front.
Parameters
----------
vehicle : PlatooningVehicle
The considered vehicle within the platoon.
Returns
-------
PlatooningVehicle : The member in the front
"""
if vehicle is not self.leader:
return self._formation[self.get_member_index(vehicle) - 1]
return None
[docs] def get_back(self, vehicle: 'PlatooningVehicle'):
"""
Return the PlatooningVehicle in the back.
Parameters
----------
vehicle : PlatooningVehicle
The considered vehicle within the platoon.
Returns
-------
PlatooningVehicle : The member in the back
"""
if vehicle is not self.last:
return self._formation[self.get_member_index(vehicle) + 1]
return None
[docs] def update_desired_speed(self):
"""
Update the desired driving speed of the platoon.
This is based on the desired driving speed of all members.
"""
old_desired_speed = self._desired_speed
self._desired_speed = min(mean([v._desired_speed for v in self._formation]), self.max_speed)
LOG.debug(f"Updated platoon {self.platoon_id}'s desired speed to {self.desired_speed} (from {old_desired_speed})")
[docs] def update_limits(self):
"""
Update mobility limits for the platoon.
"""
self.update_max_speed()
self.update_max_acceleration()
self.update_max_deceleration()
[docs] def update_cf_target_speed(self):
"""
Update the cf target speed for the platoon.
"""
for member in self._formation:
member._cf_target_speed = self._desired_speed
def __str__(self) -> str:
"""
Return the str representation of the platoon.
"""
return f"{self._platoon_id}: {self.member_ids}"