DeterministicESPAsyncWebServer v6.28.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
j2735.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file j2735.h
6 * @brief SAE J2735 V2X - ASN.1 UPER primitive codec + Basic Safety Message core (DETWS_ENABLE_J2735).
7 *
8 * J2735 (the Vehicle-to-Everything message dictionary: BSM, SPaT, MAP) is serialized with ASN.1 **UPER**
9 * (Unaligned Packed Encoding Rules). UPER packs fields at the bit level with no padding, so the codec is
10 * a bit-writer / bit-reader plus the UPER rules for the common types:
11 *
12 * - a **constrained INTEGER** in [lo, hi] is the offset (value - lo) in exactly ceil(log2(range)) bits;
13 * - a **BOOLEAN** is one bit;
14 * - an unconstrained/length-prefixed **OCTET STRING** uses a length determinant then the bytes.
15 *
16 * This provides that bit-level UPER primitive layer (host-testable against hand-computed bit patterns)
17 * and, on top of it, the J2735 **BSMcore** position block (msgCnt, id, secMark, lat, long, elev, speed,
18 * heading) encode/decode - the high-rate safety kernel every BSM carries. The DSRC / C-V2X radio is an
19 * external module; this is the message codec. Pure, zero heap, no stdlib, host-testable.
20 */
21
22#ifndef DETERMINISTICESPASYNCWEBSERVER_J2735_H
23#define DETERMINISTICESPASYNCWEBSERVER_J2735_H
24
25#include "ServerConfig.h"
26#include <stddef.h>
27#include <stdint.h>
28
29#if DETWS_ENABLE_J2735
30
31/** @brief A UPER bit writer over a caller buffer (MSB-first within each octet). */
32struct UperWriter
33{
34 uint8_t *buf;
35 size_t cap;
36 size_t bit_pos; ///< bits written so far.
37 bool ok; ///< cleared on overflow.
38};
39
40/** @brief A UPER bit reader over a caller buffer. */
41struct UperReader
42{
43 const uint8_t *buf;
44 size_t nbits; ///< total bits available.
45 size_t bit_pos;
46 bool ok; ///< cleared on read past the end.
47};
48
49void uper_writer_init(UperWriter *w, uint8_t *buf, size_t cap);
50/** @return the number of whole octets produced (rounds the bit length up), or 0 if the writer overflowed. */
51size_t uper_writer_finish(UperWriter *w);
52
53/** @brief Write @p nbits low bits of @p value, MSB-first. */
54void uper_put_bits(UperWriter *w, uint32_t value, unsigned nbits);
55/** @brief Write a BOOLEAN (1 bit). */
56void uper_put_bool(UperWriter *w, bool v);
57/** @brief Write a constrained INTEGER in [lo, hi] as (value-lo) in ceil(log2(hi-lo+1)) bits. */
58void uper_put_cint(UperWriter *w, int64_t value, int64_t lo, int64_t hi);
59
60void uper_reader_init(UperReader *r, const uint8_t *buf, size_t nbits);
61/** @brief Read @p nbits bits, MSB-first, into the low bits of the result. */
62uint32_t uper_get_bits(UperReader *r, unsigned nbits);
63bool uper_get_bool(UperReader *r);
64/** @brief Read a constrained INTEGER in [lo, hi]. */
65int64_t uper_get_cint(UperReader *r, int64_t lo, int64_t hi);
66
67/** @brief Number of bits a constrained INTEGER in [lo, hi] occupies (0 when lo == hi). */
68unsigned uper_cint_bits(int64_t lo, int64_t hi);
69
70/** @brief The J2735 BSMcoreData safety kernel (values in J2735 units; see the SAE ranges). */
71struct J2735BsmCore
72{
73 uint8_t msg_count; ///< MsgCount 0..127.
74 uint32_t id; ///< TemporaryID (4 octets).
75 uint16_t sec_mark; ///< DSecond 0..65535 (ms of the minute; 65535 = unavailable).
76 int32_t lat; ///< Latitude 1/10 microdegree, -900000000..900000001.
77 int32_t lon; ///< Longitude 1/10 microdegree, -1799999999..1800000001.
78 int32_t elev; ///< Elevation, -4096..61439 (decimeters).
79 uint16_t speed; ///< Speed 0..8191 (0.02 m/s; 8191 = unavailable).
80 uint16_t heading; ///< Heading 0..28800 (0.0125 deg; 28800 = unavailable).
81};
82
83/**
84 * @brief UPER-encode a BSMcore block. @return octets written, or 0 on overflow.
85 *
86 * Encodes, in order: msgCnt [0..127], id (32 bits), secMark [0..65535], lat [-900000000..900000001],
87 * long [-1799999999..1800000001], elev [-4096..61439], speed [0..8191], heading [0..28800].
88 */
89size_t detws_j2735_bsm_core_encode(const J2735BsmCore *c, uint8_t *out, size_t cap);
90
91/** @brief UPER-decode a BSMcore block. @return true on success. */
92bool detws_j2735_bsm_core_decode(const uint8_t *in, size_t len, J2735BsmCore *c);
93
94/** @brief J2735 MovementPhaseState (the signal-group state in a SPaT MovementState). */
95enum class J2735PhaseState : uint8_t
96{
97 J2735_PHASE_DARK = 0, ///< unavailable / dark.
98 J2735_PHASE_STOP_THEN_PROCEED = 1, ///< flashing red.
99 J2735_PHASE_STOP_AND_REMAIN = 3, ///< red.
100 J2735_PHASE_PERMISSIVE_MOVEMENT_ALLOWED = 5, ///< permissive green.
101 J2735_PHASE_PROTECTED_MOVEMENT_ALLOWED = 6, ///< protected green.
102 J2735_PHASE_PERMISSIVE_CLEARANCE = 7, ///< permissive yellow.
103 J2735_PHASE_PROTECTED_CLEARANCE = 8, ///< protected yellow.
104 J2735_PHASE_CAUTION_CONFLICTING_TRAFFIC = 9 ///< flashing yellow.
105};
106
107/** @brief One SPaT MovementState: a signal group, its current phase, and the min/max end times. */
108struct J2735MovementState
109{
110 uint8_t signal_group; ///< SignalGroupID 0..255.
111 uint8_t phase; ///< J2735PhaseState (eventState 0..9).
112 uint16_t min_end_time; ///< TimeMark 0..36000 (tenths of a second in the hour; 36001 = undefined here).
113 uint16_t max_end_time; ///< TimeMark 0..36000.
114};
115
116/**
117 * @brief UPER-encode a SPaT MovementState list into @p out.
118 * @param states the movement states.
119 * @param count number of states (0..31; encoded as a 5-bit count).
120 * @return octets written, or 0 on overflow.
121 *
122 * Encodes count [0..31] then, per state: signalGroup [0..255], eventState [0..9], minEndTime [0..36000],
123 * maxEndTime [0..36000] - the timing core a vehicle uses for a countdown.
124 */
125size_t detws_j2735_spat_encode(const J2735MovementState *states, size_t count, uint8_t *out, size_t cap);
126
127/**
128 * @brief UPER-decode a SPaT MovementState list.
129 * @param out_states buffer for the decoded states.
130 * @param max_states capacity of @p out_states.
131 * @param out_count set to the number decoded.
132 * @return true on success (and the encoded count fit in @p max_states).
133 */
134bool detws_j2735_spat_decode(const uint8_t *in, size_t len, J2735MovementState *out_states, size_t max_states,
135 size_t *out_count);
136
137/** @brief One MAP lane: an id and an approach/egress flag (the minimal LaneID + directionalUse bit). */
138struct J2735Lane
139{
140 uint8_t lane_id; ///< LaneID 0..255.
141 bool is_ingress; ///< true = an approach (ingress) lane, false = egress.
142 int16_t node_x; ///< first node offset X, -2048..2047 (cm, node-XY offset).
143 int16_t node_y; ///< first node offset Y, -2048..2047 (cm).
144};
145
146/** @brief The MAP intersection-geometry core: an intersection id + a list of lanes. */
147struct J2735MapIntersection
148{
149 uint16_t intersection_id; ///< IntersectionID 0..65535.
150 uint16_t ref_lat; ///< reference point offset lat surrogate (0..65535 here for the codec).
151 uint16_t ref_lon; ///< reference point offset lon surrogate (0..65535 here).
152};
153
154/**
155 * @brief UPER-encode a MAP intersection: id + refLat + refLon, then a 5-bit lane count and each lane
156 * (laneID [0..255], directionalUse bit, nodeX [-2048..2047], nodeY [-2048..2047]).
157 * @param count number of lanes (0..31).
158 * @return octets written, or 0 on overflow.
159 */
160size_t detws_j2735_map_encode(const J2735MapIntersection *isect, const J2735Lane *lanes, size_t count, uint8_t *out,
161 size_t cap);
162
163/**
164 * @brief UPER-decode a MAP intersection. @return true on success (and the lane count fit @p max_lanes).
165 */
166bool detws_j2735_map_decode(const uint8_t *in, size_t len, J2735MapIntersection *isect, J2735Lane *out_lanes,
167 size_t max_lanes, size_t *out_count);
168
169#endif // DETWS_ENABLE_J2735
170#endif // DETERMINISTICESPASYNCWEBSERVER_J2735_H
User-facing configuration for DeterministicESPAsyncWebServer.