Skip to content

Commit 3f38ade

Browse files
VERSION 0.10.0
1 parent ba3a0e6 commit 3f38ade

30 files changed

+1543
-1098
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import os
2+
import sys
3+
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
4+
PROJECT_ROOT = os.path.abspath(os.path.join(THIS_DIR, os.pardir))
5+
SRC_DIR = os.path.join(PROJECT_ROOT, "src")
6+
if os.path.isdir(SRC_DIR) and SRC_DIR not in sys.path:
7+
sys.path.insert(0, SRC_DIR)
8+
9+
# Configure logging
10+
import logging
11+
logging.basicConfig(level=logging.INFO)
12+
13+
# Link Layer Imports
14+
from flexstack.linklayer.raw_link_layer import RawLinkLayer
15+
16+
# GeoNetworking imports
17+
from flexstack.geonet.router import Router as GNRouter
18+
from flexstack.geonet.mib import MIB
19+
from flexstack.geonet.gn_address import GNAddress, M, ST, MID
20+
21+
# BTP Router imports
22+
from flexstack.btp.router import Router as BTPRouter
23+
24+
# Location Service imports
25+
from flexstack.utils.static_location_service import ThreadStaticLocationService
26+
27+
# CA Basic Service imports
28+
from flexstack.facilities.ca_basic_service.ca_basic_service import (
29+
CooperativeAwarenessBasicService,
30+
)
31+
from flexstack.facilities.ca_basic_service.cam_transmission_management import (
32+
VehicleData,
33+
)
34+
35+
POSITION_COORDINATES = [41.386931, 2.112104]
36+
MAC_ADDRESS = b"\xaa\xbb\xcc\x11\x21\x11"
37+
STATION_ID = int(MAC_ADDRESS[-1])
38+
39+
40+
def main() -> None:
41+
# Instantiate a Location Service
42+
location_service = ThreadStaticLocationService(
43+
period=1000, latitude=POSITION_COORDINATES[0], longitude=POSITION_COORDINATES[1]
44+
)
45+
46+
# Instantiate a GN router
47+
mib = MIB(
48+
itsGnLocalGnAddr=GNAddress(
49+
m=M.GN_MULTICAST,
50+
st=ST.CYCLIST,
51+
mid=MID(MAC_ADDRESS),
52+
),
53+
)
54+
gn_router = GNRouter(mib=mib, sign_service=None)
55+
location_service.add_callback(gn_router.refresh_ego_position_vector)
56+
57+
# Instantiate a Link Layer
58+
link_layer = RawLinkLayer(
59+
"lo", MAC_ADDRESS, receive_callback=gn_router.gn_data_indicate
60+
)
61+
62+
gn_router.link_layer = link_layer
63+
64+
# Instantiate a BTP router
65+
btp_router = BTPRouter(gn_router)
66+
gn_router.register_indication_callback(btp_router.btp_data_indication)
67+
68+
# Instantiate a CA Basic Service
69+
vehicle_data = VehicleData()
70+
vehicle_data.station_id = STATION_ID # Station Id of the ITS PDU Header
71+
vehicle_data.station_type = 5 # Station Type as specified in ETSI TS 102 894-2 V2.3.1 (2024-08)
72+
vehicle_data.drive_direction = "forward"
73+
vehicle_data.vehicle_length = {
74+
"vehicleLengthValue": 1023, # as specified in ETSI TS 102 894-2 V2.3.1 (2024-08)
75+
"vehicleLengthConfidenceIndication": "unavailable",
76+
}
77+
vehicle_data.vehicle_width = 62
78+
79+
ca_basic_service = CooperativeAwarenessBasicService(
80+
btp_router=btp_router,
81+
vehicle_data=vehicle_data,
82+
)
83+
location_service.add_callback(ca_basic_service.cam_transmission_management.location_service_callback)
84+
85+
print("Press Ctrl+C to stop the program.")
86+
location_service.location_service_thread.join()
87+
88+
if __name__ == "__main__":
89+
main()

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "v2xflexstack"
7-
version = "0.9.22"
7+
version = "0.10.0"
88
authors = [
99
{ name = "Jordi Marias-i-Parella", email = "[email protected]" },
1010
{ name = "Daniel Ulied Guevara", email = "[email protected]" },

src/flexstack/btp/btp_header.py

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from .service_access_point import BTPDataRequest
2+
from dataclasses import dataclass
23

34

5+
@dataclass(frozen=True)
46
class BTPAHeader:
57
"""
68
BTP-A Header class.
@@ -15,21 +17,20 @@ class BTPAHeader:
1517
(16 bit integer) Source Port field of BTP-A Header
1618
"""
1719

18-
def __init__(self) -> None:
19-
self.destination_port = 0
20-
self.source_port = 0
20+
destination_port: int = 0
21+
source_port: int = 0
2122

22-
def initialize_with_request(self, request: BTPDataRequest) -> None:
23+
@classmethod
24+
def initialize_with_request(cls, request: BTPDataRequest) -> "BTPAHeader":
2325
"""
24-
Initializes the BTP-A Header with a GNDataRequest.
26+
Initialize a BTP-A Header from a BTPDataRequest.
2527
2628
Parameters
2729
----------
28-
request : GNDataRequest
29-
GNDataRequest to use.
30+
request : BTPDataRequest
31+
Request to use for initialization.
3032
"""
31-
self.destination_port = request.destination_port
32-
self.source_port = request.source_port
33+
return cls(destination_port=request.destination_port, source_port=request.source_port)
3334

3435
def encode_to_int(self) -> int:
3536
"""
@@ -53,19 +54,22 @@ def encode(self) -> bytes:
5354
"""
5455
return self.encode_to_int().to_bytes(4, byteorder='big')
5556

56-
def decode(self, data: bytes) -> None:
57+
@classmethod
58+
def decode(cls, data: bytes) -> "BTPAHeader":
5759
"""
58-
Decodes the BTP-A Header from bytes.
60+
Decodes the BTP-A Header from bytes and returns a new instance.
5961
6062
Parameters
6163
----------
6264
data : bytes
6365
Bytes to decode.
6466
"""
65-
self.destination_port = int.from_bytes(data[0:2], byteorder='big')
66-
self.source_port = int.from_bytes(data[2:4], byteorder='big')
67+
destination_port = int.from_bytes(data[0:2], byteorder='big')
68+
source_port = int.from_bytes(data[2:4], byteorder='big')
69+
return cls(destination_port=destination_port, source_port=source_port)
6770

6871

72+
@dataclass(frozen=True)
6973
class BTPBHeader:
7074
"""
7175
BTP-B Header class.
@@ -80,21 +84,20 @@ class BTPBHeader:
8084
(16 bit integer) Destination Port Info field of BTP-B Header
8185
"""
8286

83-
def __init__(self) -> None:
84-
self.destination_port = 0
85-
self.destination_port_info = 0
87+
destination_port: int = 0
88+
destination_port_info: int = 0
8689

87-
def initialize_with_request(self, request: BTPDataRequest) -> None:
90+
@classmethod
91+
def initialize_with_request(cls, request: BTPDataRequest) -> "BTPBHeader":
8892
"""
89-
Initializes the BTP-B Header with a GNDataRequest.
93+
Initialize a BTP-B Header from a BTPDataRequest.
9094
9195
Parameters
9296
----------
93-
request : GNDataRequest
94-
GNDataRequest to use.
97+
request : BTPDataRequest
98+
Request to use for initialization.
9599
"""
96-
self.destination_port = request.destination_port
97-
self.destination_port_info = request.destinaion_port_info
100+
return cls(destination_port=request.destination_port, destination_port_info=getattr(request, 'destination_port_info', 0))
98101

99102
def encode_to_int(self) -> int:
100103
"""
@@ -118,14 +121,16 @@ def encode(self) -> bytes:
118121
"""
119122
return self.encode_to_int().to_bytes(4, byteorder='big')
120123

121-
def decode(self, data: bytes) -> None:
124+
@classmethod
125+
def decode(cls, data: bytes) -> "BTPBHeader":
122126
"""
123-
Decodes the BTP-B Header from bytes.
127+
Decodes the BTP-B Header from bytes and returns a new instance.
124128
125129
Parameters
126130
----------
127131
data : bytes
128132
Bytes to decode.
129133
"""
130-
self.destination_port = int.from_bytes(data[0:2], byteorder='big')
131-
self.destination_port_info = int.from_bytes(data[2:4], byteorder='big')
134+
destination_port = int.from_bytes(data[0:2], byteorder='big')
135+
destination_port_info = int.from_bytes(data[2:4], byteorder='big')
136+
return cls(destination_port=destination_port, destination_port_info=destination_port_info)

src/flexstack/btp/router.py

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from __future__ import annotations
22
from collections.abc import Callable
3+
from types import MappingProxyType
34
import logging
45

5-
from .btp_header import BTPAHeader, BTPBHeader
6+
from .btp_header import BTPBHeader
67
from .service_access_point import BTPDataIndication, BTPDataRequest
78
from ..geonet.common_header import CommonNH
89
from ..geonet.service_access_point import GNDataIndication, GNDataRequest
@@ -17,20 +18,29 @@ class Router:
1718
1819
Attributes
1920
----------
20-
indication_callbacks : Dict[int, Callable[[BTPDataIndication], None]]
21-
Dictionary of indication callbacks. The key is the port and the value is the callback.
21+
indication_callbacks : MappingProxyType[int, Callable[[BTPDataIndication], None]] | None
22+
MappingProxyType of indication callbacks. The key is the port and the value is the callback.
2223
gn_router : GNRouter
2324
Geonetworking Router.
2425
"""
2526

2627
def __init__(self, gn_router: GNRouter) -> None:
2728
self.logging = logging.getLogger("btp")
28-
29-
self.indication_callbacks: dict[int, Callable[[BTPDataIndication], None]] = {}
29+
self.pre_indication_callbacks: dict[int,
30+
Callable[[BTPDataIndication], None]] = {}
31+
self.indication_callbacks: MappingProxyType[int, Callable[[
32+
BTPDataIndication], None]] | None = None
3033
self.gn_router = gn_router
3134

3235
self.logging.info("BTP Router Initialized!")
3336

37+
def freeze_callbacks(self):
38+
"""
39+
Freezes the indication callbacks. After calling this method, no more callbacks can be registered.
40+
"""
41+
self.indication_callbacks = MappingProxyType(
42+
dict(self.pre_indication_callbacks))
43+
3444
def register_indication_callback_btp(
3545
self, port: int, callback: Callable[[BTPDataIndication], None]
3646
) -> None:
@@ -44,7 +54,9 @@ def register_indication_callback_btp(
4454
callback : Callable[[BTPDataIndication], None]
4555
Callback to register.
4656
"""
47-
self.indication_callbacks[port] = callback
57+
if self.indication_callbacks is not None:
58+
raise RuntimeError("Cannot register callbacks after freezing")
59+
self.pre_indication_callbacks[port] = callback
4860
self.logging.info("Indication callback registered")
4961

5062
def btp_data_request(self, request: BTPDataRequest) -> None:
@@ -57,19 +69,22 @@ def btp_data_request(self, request: BTPDataRequest) -> None:
5769
BTPDataRequest to handle.
5870
"""
5971
if request.btp_type == CommonNH.BTP_B:
60-
header = BTPBHeader()
61-
header.destination_port_info = request.destinaion_port_info
62-
header.destination_port = request.destination_port
63-
gn_data_request = GNDataRequest()
64-
gn_data_request.upper_protocol_entity = request.btp_type
65-
gn_data_request.packet_transport_type = request.gn_packet_transport_type
66-
gn_data_request.area = request.gn_area
67-
gn_data_request.communication_profile = request.communication_profile
68-
gn_data_request.traffic_class = request.traffic_class
69-
gn_data_request.data = header.encode() + request.data
70-
gn_data_request.length = len(gn_data_request.data)
71-
72-
self.logging.debug("Sending BTP Data Request: %s", gn_data_request.data)
72+
header = BTPBHeader(
73+
destination_port=request.destination_port,
74+
destination_port_info=request.destination_port_info,
75+
)
76+
data = header.encode() + request.data
77+
gn_data_request = GNDataRequest(
78+
upper_protocol_entity=request.btp_type,
79+
packet_transport_type=request.gn_packet_transport_type,
80+
area=request.gn_area,
81+
communication_profile=request.communication_profile,
82+
traffic_class=request.traffic_class,
83+
data=data,
84+
length=len(data)
85+
)
86+
self.logging.debug(
87+
"Sending BTP Data Request: %s", gn_data_request.data)
7388
self.gn_router.gn_data_request(gn_data_request)
7489
elif request.btp_type == CommonNH.BTP_A:
7590
raise NotImplementedError("BTPADataRequest not implemented")
@@ -85,16 +100,20 @@ def btp_b_data_indication(self, gn_data_indication: GNDataIndication) -> None:
85100
gn_data_indication : GNDataIndication
86101
GNDataIndication to handle.
87102
"""
88-
indication = BTPDataIndication()
89-
indication.initialize_with_gn_data_indication(gn_data_indication)
90-
header = BTPBHeader()
91-
header.decode(gn_data_indication.data)
92-
indication.destination_port = header.destination_port
93-
indication.destinaion_port_info = header.destination_port_info
94-
for port, callback in self.indication_callbacks.items():
95-
if port == indication.destination_port:
96-
self.logging.debug("Sending BTP B Data Indication: %s", indication.data)
103+
indication = BTPDataIndication.initialize_with_gn_data_indication(
104+
gn_data_indication)
105+
header = BTPBHeader.decode(gn_data_indication.data)
106+
indication = indication.set_destination_port_and_info(
107+
destination_port=header.destination_port,
108+
destination_port_info=header.destination_port_info
109+
)
110+
if self.indication_callbacks:
111+
callback = self.indication_callbacks.get(
112+
indication.destination_port)
113+
if callback:
97114
callback(indication)
115+
else:
116+
raise RuntimeError("Indication callbacks not frozen")
98117

99118
def btp_a_data_indication(self, gn_data_indication: GNDataIndication) -> None:
100119
"""
@@ -105,8 +124,6 @@ def btp_a_data_indication(self, gn_data_indication: GNDataIndication) -> None:
105124
gn_data_indication : GNDataIndication
106125
GNDataIndication to handle.
107126
"""
108-
header = BTPAHeader()
109-
header.decode(gn_data_indication.data)
110127
raise NotImplementedError("BTPADataIndication not implemented")
111128

112129
def btp_data_indication(self, gn_data_indication: GNDataIndication) -> None:

0 commit comments

Comments
 (0)