Reverse-engineer Context from source code
When implementation metadata describes an element's boundary, stigImportModel imports it into the CREATE Context model. The C++ reader consumes an existing compiled interface inventory, topics and annotations and explicitly writes Kotlin into the element's source directory. SYML and native Highway files use the same public task family.
element.importEnabled selects interface ownership and follows the element/group/default cascade:
true: code is authoritative for Context interface definitions. The importer produces an abstract Generated<Element>Context base with the current topics and interfaces. A concrete authored Context subclass and its canonical instance retain hierarchy, internal units, and references from requirements, design, and tests.
false: further code imports are disabled. Existing Kotlin source files remain part of the model.
Import is an explicit source edit. Compilation, checking and reporting consume the checked-in/imported Kotlin without refreshing external inputs. After refreshing the native inventory, run import and then check the resulting source diff. Invalid enabled importer inputs fail. Harness generation and P6 execution keep their separate lifecycle.
Both an element task and a same-named root aggregate can be invoked directly:
./gradlew :activities:example_activity:stigExtractAnalysisInterfaceInventory
./gradlew :activities:example_activity:stigImportModel
./gradlew :activities:example_activity:stigCheckModel
./gradlew stigGenerateHtml
Ordinary reporting uses the final command. Recurring model imports are configured in stigProject.model with source mappings, viewpoint selection and optional interface metadata. SYML is read directly; consumer converters supply native Highway JSON for other external formats.
Use STIG workflows for the public task contract and Gradle task API for exact options. The STIG skill helps configure ownership and resolve ambiguous or invalid imported interfaces at their source.
Core concepts
Elements
Element is the structural umbrella for nodes in your architecture. You typically define:
Item
SystemElement
Subsystem
Cluster
Component
Elements can be organized in a hierarchy using field parent.
An element's asil records the ASIL applied to that element, including any elevation required by its architectural context. Set the optional allocatedAsil manually when the allocated safety requirements are unavailable in the model and their maximum ASIL is lower than the element's asil. For example, an element developed at asil = ASIL.D can declare allocatedAsil = ASIL.B to record that its allocated safety requirements have a maximum ASIL of B. Both attributes use the ASIL value type. allocatedAsil = null means no separate allocation ASIL has been declared. The declared allocatedAsil must not exceed asil. When the element's requirements are available and all have an ASIL, their maximum must equal the declared allocatedAsil. The checks validate the declaration without computing a replacement attribute or changing failure classifications.
For example, an element with asil = ASIL.D and allocatedAsil = ASIL.B may provide an ASIL B interface, but an ASIL C output exceeds its declared allocation. With no allocatedAsil, the comparison uses asil. This applies to provided functions, provided service members and published data. Required services, called methods and subscribed data may have higher or lower ASIL than their consuming element. A finding concerning provision of a shared definition belongs to the local provided port or member use.
Interface points
InterfacePoint is the umbrella for “interface-side” concepts, such as:
ServiceInterface (interface definition with events/fields/methods)
ProvidedPort<S : ServiceInterface>/RequiredPort<S : ServiceInterface> (usage/endpoint typed by an interface)
Event, Field, Method, CheckedError
domain specializations like Register, Message, IPdu
ROS-style Topic, Publisher, Subscriber
An interface point's optional asil records the classification authored on that declaration. Its read-only effectiveAsil supplies the classification used by model checks and reports after model wiring. Any non-null authored value ends the upward lookup immediately, regardless of its ASIL level. Otherwise, STIG follows the referenced definition for a member use(...), Port or TopicPort, then the enclosing interface-point chain, and finally the owning element's asil. It uses the element's asil, even when allocatedAsil is lower. If the whole chain is unclassified, effectiveAsil is null and the missing-ASIL rule reports it; null does not mean QM.
For example, a Field without an authored ASIL inside a ServicePort group inherits that group's effective ASIL. A local use(field) inherits the field's effective ASIL. Declare the classification on the member or its enclosing interface; the use(..., asil = ...) overload is deprecated. Retained overrides must not exceed the member's effective ASIL, as checked by PortMemberUseAsilBoundRule. Shared definitions keep their own declaration ownership; a local use can fall back to its port and element without assigning that classification to the shared definition. The element's parent and an analysis's usedIn references do not supply classification defaults.
For structure.field21.subfield31, if field21.asil = ASIL.B and subfield31.asil is null, subfield31.effectiveAsil is B. A separate context rule, InterfacePointParentAsilBoundRule, checks that a child's effective ASIL does not exceed its enclosing interface point's effective ASIL. If the structure is classified A, the rule reports field21 for exceeding that bound. The inherited value of subfield31 remains B.
Select members through a service port
A port can select named members of its shared service definition with use. Declare each result as a Kotlin property, for example val statusValue = statusPort.use(statusInterface.value, name = "Local status"). The result is a PortMemberUse, an interface point owned by that port. Two ports using the same definition member have separate endpoint identities and can have separate FMEA. An interface-point analysis can use the local property as its subject. PortMemberUseRule reports HIGH when the selected member is outside the port's interface. With no explicit selections, the existing whole-interface behavior remains; with selections, ownership includes the selected members. Shared definition IDs do not change when a port uses them.
Connectors
Connector binds two interface points (from and to). Most common bindings are:
Types
Interface points reference data types via DataType subclasses:
Define a Kotlin class/object that extends DataType
Optionally provide implementationType when there is a C/C++/IDL name
Reference the data type instance from events/fields/parameters
Example:
data class ImageSize(val width: Int = 0, val height: Int = 0)
data class CameraImage(
val size: ImageSize = ImageSize(),
val encoding: String = ""
) : DataType(implementationType = "car::CameraImage")
class BatteryVoltage : DataType(implementationType = "BatteryVoltage") {
val volts: Double? = null
}
val cameraImage = CameraImage()
val batteryVoltage = BatteryVoltage()
Then:
This mirrors ROS/IDL usage: a concrete topic/type is declared once and reused.
ROS-like topics: publish and subscribe
Recommended approach: define a topic (payload) + a single shared instance, then reuse that instance on publishers/subscribers. This keeps the payload type consistent without a ServiceInterface layer.
What each topic artifact represents
Artifact | Represents physically | STIG metamodel entities | Examples | Example usage (one-liner) |
|---|
Type (DataType) | Data schema/type used by topics | DataType
| CameraImage, CalibrationCommand
| val cameraImage = CameraImage(...)
|
Topic | Topic definition with payload type | Topic
| RosCameraImageTopic, RosCalibrationCommandTopic
| class RosCameraImageTopic : Topic<CameraImage>(..., data = cameraImage)
|
Topic port | Publisher/subscriber endpoint | Publisher, Subscriber
| CameraImagePub, CameraImageSub
| val cameraImagePub: Publisher<RosCameraImageTopic> = Publisher(..., topic = rosCameraImageTopic)
|
Component | Software component boundary (publishers/subscribers live on the component) | Component
| RosCamera, RosFusion
| class RosCamera : Component { val imagePub = Publisher(...) }
|
Goal: define a single camera image topic and calibration command/status topics once, then reuse them via publishers/subscribers.
Define reusable payload types
Optional but keeps data aligned.
val cameraImage = CameraImage(
frameId = "front_left",
size = ImageSize(width = 1920, height = 1080),
encoding = "rgb8"
)
val calibrationCommand = CalibrationCommand()
val calibrationStatus = CalibrationStatus()
Define topics
class RosCameraImageTopic : Topic<CameraImage>(..., data = cameraImage)
class RosCalibrationCommandTopic : Topic<CalibrationCommand>(..., data = calibrationCommand)
class RosCalibrationStatusTopic : Topic<CalibrationStatus>(..., data = calibrationStatus)
Create shared topic instances
val rosCameraImageTopic = RosCameraImageTopic()
val rosCalibrationCommandTopic = RosCalibrationCommandTopic()
val rosCalibrationStatusTopic = RosCalibrationStatusTopic()
Use topics through publishers and subscribers
class RosCamera : Component {
val imagePub: Publisher<RosCameraImageTopic> = ...
val calibrationCmdSub: Subscriber<RosCalibrationCommandTopic> = ...
val calibrationStatusPub: Publisher<RosCalibrationStatusTopic> = ...
}
class RosFusion : Component { val cameraImageSub: Subscriber<RosCameraImageTopic> = ... }
class RosStateManager : Component {
val calibrationCmdPub: Publisher<RosCalibrationCommandTopic> = ...
val calibrationStatusSub: Subscriber<RosCalibrationStatusTopic> = ...
}
Access a topic through .topic
val imageTopic = rosFusion.cameraImageSub.topic
val imagePayload = imageTopic.data
Complete topic example
// Tutorial sources follow documentation navigation paths, independently of their example packages.
@file:Suppress("PackageDirectoryMismatch")
package com.stigway.docs.tutorial.context
import com.stigway.metamodels.highway.*
// Kotlin representations of the payload types (e.g., generated from IDL).
data class ImageSize(
val width: Int = 0,
val height: Int = 0
)
data class CameraImage(
val frameId: String = "",
val timestampNs: Long = 0L,
val size: ImageSize = ImageSize(),
val encoding: String = "",
val isBigEndian: Boolean = false,
val step: Int = 0,
val data: ByteArray = byteArrayOf(),
val exposureUs: Int = 0,
val gainDb: Double = 0.0
) : DataType(implementationType = "car::CameraImage") {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is CameraImage) return false
return frameId == other.frameId && timestampNs == other.timestampNs && size == other.size &&
encoding == other.encoding && isBigEndian == other.isBigEndian && step == other.step &&
data.contentEquals(other.data) && exposureUs == other.exposureUs && gainDb.compareTo(other.gainDb) == 0
}
override fun hashCode(): Int {
var result = frameId.hashCode()
result = 31 * result + timestampNs.hashCode()
result = 31 * result + size.hashCode()
result = 31 * result + encoding.hashCode()
result = 31 * result + isBigEndian.hashCode()
result = 31 * result + step
result = 31 * result + data.contentHashCode()
result = 31 * result + exposureUs
result = 31 * result + gainDb.hashCode()
return result
}
}
data class CalibrationCommand(
val cameraModel: String = "",
val distortionModel: String = "",
val imageSize: ImageSize = ImageSize(),
val fx: Double = 0.0,
val fy: Double = 0.0,
val cx: Double = 0.0,
val cy: Double = 0.0,
val distortionCoeffs: List<Double> = emptyList(),
val rectification: List<Double> = emptyList(),
val projection: List<Double> = emptyList(),
val calibrationTimestampNs: Long = 0L,
val serialNumber: String = ""
) : DataType(implementationType = "car::CalibrationCommand")
data class CalibrationStatus(
val code: UByte = 0.toUByte()
) : DataType(implementationType = "uint8")
// Realistic example payloads.
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val exampleCameraImage = CameraImage(
frameId = "front_left",
timestampNs = 1710001234567890L,
size = ImageSize(width = 1920, height = 1080),
encoding = "rgb8",
isBigEndian = false,
step = 1920 * 3,
data = byteArrayOf(0x10, 0x23, 0x45, 0x67, 0x7F, 0x00),
exposureUs = 8000,
gainDb = 6.0
)
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val exampleCalibrationCommand = CalibrationCommand(
cameraModel = "pinhole",
distortionModel = "plumb_bob",
imageSize = ImageSize(width = 1920, height = 1080),
fx = 1200.5,
fy = 1198.7,
cx = 960.0,
cy = 540.0,
distortionCoeffs = listOf(-0.11, 0.05, -0.001, 0.0003, 0.0),
rectification = listOf(
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0
),
projection = listOf(
1200.5, 0.0, 960.0, 0.0,
0.0, 1198.7, 540.0, 0.0,
0.0, 0.0, 1.0, 0.0
),
calibrationTimestampNs = 1710001000000000L,
serialNumber = "CAM-FL-01"
)
// Reusable data instances (align data across topics).
private val cameraImage: CameraImage = CameraImage(
frameId = "front_left",
size = ImageSize(width = 1920, height = 1080),
encoding = "rgb8"
)
private val calibrationCommand: CalibrationCommand = CalibrationCommand()
private val calibrationStatus: CalibrationStatus = CalibrationStatus()
// Topic definitions (payload type is defined on the topic itself).
class RosCameraImageTopic : Topic<CameraImage>(
name = "CameraImage",
description = "Camera image stream.",
asil = ASIL.B,
data = cameraImage
)
class RosCalibrationCommandTopic : Topic<CalibrationCommand>(
name = "CalibrationCommand",
description = "Calibration command.",
asil = ASIL.B,
data = calibrationCommand
)
class RosCalibrationStatusTopic : Topic<CalibrationStatus>(
name = "CalibrationStatus",
description = "Calibration status.",
asil = ASIL.B,
data = calibrationStatus
)
// Shared topic instances (single declaration reused by multiple publishers/subscribers).
val rosCameraImageTopic: RosCameraImageTopic = RosCameraImageTopic()
val rosCalibrationCommandTopic: RosCalibrationCommandTopic = RosCalibrationCommandTopic()
val rosCalibrationStatusTopic: RosCalibrationStatusTopic = RosCalibrationStatusTopic()
// Components and topic ports (publishers/subscribers).
class RosCamera : Component(
name = "RosCamera",
description = "Camera sensor component (ROS-style topics).",
asil = ASIL.B
) {
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val imagePub: Publisher<RosCameraImageTopic> = Publisher(
name = "CameraImagePub",
description = "Publishes camera images.",
asil = ASIL.B,
topic = rosCameraImageTopic
)
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val calibrationCmdSub: Subscriber<RosCalibrationCommandTopic> = Subscriber(
name = "CalibrationCmdSub",
description = "Receives calibration commands.",
asil = ASIL.B,
topic = rosCalibrationCommandTopic
)
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val calibrationStatusPub: Publisher<RosCalibrationStatusTopic> = Publisher(
name = "CalibrationStatusPub",
description = "Publishes calibration status.",
asil = ASIL.B,
topic = rosCalibrationStatusTopic
)
}
class RosFusion : Component(
name = "RosFusion",
description = "Object fusion component.",
asil = ASIL.B
) {
val cameraImageSub: Subscriber<RosCameraImageTopic> = Subscriber(
name = "CameraImageSub",
description = "Receives camera images.",
asil = ASIL.B,
topic = rosCameraImageTopic
)
}
class RosStateManager : Component(
name = "RosStateManager",
description = "System state manager.",
asil = ASIL.B
) {
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val calibrationCmdPub: Publisher<RosCalibrationCommandTopic> = Publisher(
name = "CalibrationCmdPub",
description = "Publishes calibration commands.",
asil = ASIL.B,
topic = rosCalibrationCommandTopic
)
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val calibrationStatusSub: Subscriber<RosCalibrationStatusTopic> = Subscriber(
name = "CalibrationStatusSub",
description = "Receives calibration status.",
asil = ASIL.B,
topic = rosCalibrationStatusTopic
)
}
val rosCamera: RosCamera = RosCamera()
val rosFusion: RosFusion = RosFusion()
val rosStateManager: RosStateManager = RosStateManager()
// Access via `.topic`:
val imageTopic = rosFusion.cameraImageSub.topic
// Example requirement
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val cameraImageRequirement: Requirement = Requirement(
name = "Provide Camera Image",
description = "${rosCamera.name} shall provide image ${imageTopic.name} with frameId " +
"${imageTopic.data.frameId} and size ${imageTopic.data.size.width}x${imageTopic.data.size.height}.",
asil = ASIL.B,
element = rosCamera,
verificationCriteria = """
Inspect an image published on the configured camera image topic. Its frameId, width,
and height match the topic data definition. An absent image or a mismatched field fails.
""".trimIndent(),
)
Direct native API methods
Use ProvidedMethod for a callable API exposed by a component and RequiredMethod for an operation it requires from another component. These are direct interface points: an InterfacePointAnalysis can reference nativeAdc.Adc_Init without an intermediate service definition or port.
Keep the source API spelling in the Kotlin property and name, including capitalization and underscores. For an existing provider, RequiredMethod(definition = nativeAdc.Adc_Init) creates a separate consumer endpoint while reusing the definition's signature and metadata. Its direction is required relative to the consumer; referencing it does not transfer ownership of the provided method. A required method can also declare its own signature when its provider is outside the model.
// Tutorial sources follow documentation navigation paths, independently of their example packages.
@file:Suppress("PackageDirectoryMismatch")
package com.stigway.docs.tutorial.context
import com.stigway.metamodels.highway.*
class NativeAdc : Component(
name = "Native ADC",
description = "ADC driver exposing a callable initialization API.",
asil = ASIL.B,
) {
@Suppress("PropertyName") // Preserve the exact source API spelling.
val Adc_Init: ProvidedMethod = ProvidedMethod(
name = "Adc_Init",
description = "Initialize the ADC driver before acquiring measurements.",
asil = ASIL.B,
functionType = "void Adc_Init(void)",
)
}
val nativeAdc: NativeAdc = NativeAdc()
class AdcStartup : Component(
name = "ADC startup",
description = "Startup controller requiring the ADC initialization API.",
asil = ASIL.B,
) {
@Suppress("PropertyName", "unused") // Preserve source API spelling; model members are loaded reflectively.
val Adc_Init: RequiredMethod = RequiredMethod(
definition = nativeAdc.Adc_Init,
)
}
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val adcStartup: AdcStartup = AdcStartup()
Method remains neutral for shared service definitions and operations without established direction. Real service contracts continue to use ServiceInterface, ProvidedPort/RequiredPort, and use(...). Both representations use the same provided/required report groups and model checks. Direction describes the component's role, not whether the function has input parameters or a return value.
Model the component's externally visible boundary. Functions declared only in private or explicitly internal headers belong to implementation detail; do not add them to public Context merely because they have external linkage or the component's name prefix. Header declarations establish signatures, while the component contract or reviewed caller/provider evidence establishes ownership and direction.
AUTOSAR-like services
Recommended approach: define a service interface + a single shared instance, then reuse that instance on provided/required ports. This avoids duplicating event/field/method metadata while keeping provided/required on the port.
ServicePort optionally groups members inside a service interface. It has no direction; group names such as Pub or Ctrl do not assign a provided/required role. That role comes from ProvidedPort or RequiredPort.
What each service artifact represents
Artifact | Represents physically | STIG metamodel entities | Examples | Example usage (one-liner) |
|---|
Type (DataType) | Data schema/type used by events, fields, and method parameters | DataType
| CameraImage, CalibrationMode
| val cameraImage = CameraImage(...)
|
Service interface | Interface definition containing members (events/fields/methods); provided/required lives on ports | ServiceInterface
| CameraImagesServiceInterface, CameraControlServiceInterface
| class CameraImagesServiceInterface : ServiceInterface(...) { val imageStream = Event(...); val calibrate = Method(...) }
|
Port | Usage endpoint of an interface (provided or required), with instance specifier/identifier | ProvidedPort, RequiredPort, InstanceSpecifier
| CameraImagesOut, CameraImagesIn
| val cameraImagesIn: RequiredPort<CameraImagesServiceInterface> = RequiredPort(..., serviceInterface = cameraImagesServiceInterface)
|
Component | Software component boundary (ports are its provided/required interfaces; requirements/design live elsewhere) | Component
| Camera, Fusion
| class Camera : Component { val imagesOut = ProvidedPort(...) }
|
Goal: define a single camera image event and calibration method once, then reuse them via provided/required ports.
Define reusable service types
Optional but keeps data aligned.
data class ImageSize(val width: Int = 0, val height: Int = 0)
data class CameraImage(
val size: ImageSize = ImageSize(),
val encoding: String = ""
) : DataType(implementationType = "car::CameraImage")
data class CalibrationMode(val value: UByte = 0.toUByte()) : DataType(implementationType = "uint8")
data class CalibrationStatus(val code: UByte = 0.toUByte()) : DataType(implementationType = "uint8")
val cameraImage = CameraImage(size = ImageSize(width = 1920, height = 1080), encoding = "rgb8")
val calibrationModeValue = CalibrationMode()
val calibrationStatus = CalibrationStatus()
Define service interfaces
class CameraImagesServiceInterface : ServiceInterface(...) {
val imageStream: Event = Event(...)
val lastImage: Field = Field(...)
}
class CameraControlServiceInterface : ServiceInterface(...) {
val calibrationMode: Field = Field(...)
val calibrate: Method = Method(...)
}
Create shared service-interface instances
val cameraImagesServiceInterface = CameraImagesServiceInterface()
val cameraControlServiceInterface = CameraControlServiceInterface()
Use services through provided and required ports
class Camera : Component { val imagesOut: ProvidedPort<CameraImagesServiceInterface> = ... }
class Fusion : Component { val cameraImagesIn: RequiredPort<CameraImagesServiceInterface> = ... }
class StateManager : Component { val cameraControlIn: RequiredPort<CameraControlServiceInterface> = ... }
Define method signatures
Define method parameters inside the Method object. For a short one-line signature, you can also use functionType.
val calibrate = object : Method(
name = "CameraDriver::calibrate",
// isSynchronous defaults to true
functionType = "void CameraDriver::calibrate(uint8, uint8&)"
) {
val mode = Parameter(name = "mode", data = calibrationModeValue, direction = ParameterDirection.IN)
val status = Parameter(name = "status", data = calibrationStatus, direction = ParameterDirection.OUT)
}
Access members through .serviceInterface
val imageEvent = fusion.cameraImagesIn.serviceInterface.imageStream
val calibrateMethod = stateManager.cameraControlIn.serviceInterface.calibrate
Complete service example
// Tutorial sources follow documentation navigation paths, independently of their example packages.
@file:Suppress("PackageDirectoryMismatch")
package com.stigway.docs.tutorial.context
import com.stigway.metamodels.highway.*
// Reusable data types (align data across Event/Field/Method parameters).
data class CalibrationMode(
val value: UByte = 0.toUByte()
) : DataType(implementationType = "uint8")
private val cameraImageService: CameraImage = CameraImage(
size = ImageSize(width = 1920, height = 1080),
encoding = "rgb8"
)
private val calibrationModeValue = CalibrationMode()
private val calibrationStatusService = CalibrationStatus()
// Service interfaces (define members directly).
class CameraImagesServiceInterface : ServiceInterface(
name = "CameraImagesService",
description = "Camera image streaming contract.",
asil = ASIL.B
) {
val imageStream: Event<CameraImage> = Event(
name = "CameraImageStream",
description = "Camera image stream.",
asil = ASIL.B,
data = cameraImageService
)
val lastImage: Field<CameraImage> = Field(
name = "LastImage",
description = "Last received image.",
asil = ASIL.B,
data = cameraImageService,
hasSetter = false
)
}
class CameraControlServiceInterface : ServiceInterface(
name = "CameraControlService",
description = "Camera calibration control contract.",
asil = ASIL.B
) {
val calibrationMode: Field<CalibrationMode> = Field(
name = "CalibrationMode",
description = "Active calibration mode.",
asil = ASIL.B,
data = calibrationModeValue
)
val calibrate: Method = object : Method(
name = "CameraDriver::calibrate",
description = "Start camera calibration.",
asil = ASIL.B
) {
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val mode: Parameter<CalibrationMode> = Parameter(
name = "mode",
description = "Calibration mode.",
asil = ASIL.B,
data = calibrationModeValue,
direction = ParameterDirection.IN
)
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val status: Parameter<CalibrationStatus> = Parameter(
name = "status",
description = "Calibration status.",
asil = ASIL.B,
data = calibrationStatusService,
direction = ParameterDirection.OUT
)
}
}
// Shared service interface instances (single declaration reused by multiple ports).
val cameraImagesServiceInterface: CameraImagesServiceInterface = CameraImagesServiceInterface()
val cameraControlServiceInterface: CameraControlServiceInterface = CameraControlServiceInterface()
// Components and ports (provided/required ports).
class Camera : Component(
name = "Camera",
description = "Camera sensor component.",
asil = ASIL.B
) {
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val imagesOut: ProvidedPort<CameraImagesServiceInterface> = ProvidedPort(
name = "CameraImagesOut",
description = "Provided camera images.",
asil = ASIL.B,
instanceSpecifier = InstanceSpecifier("Camera/CameraImagesOut"),
serviceInterface = cameraImagesServiceInterface
)
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val controlOut: ProvidedPort<CameraControlServiceInterface> = ProvidedPort(
name = "CameraControlOut",
description = "Provided camera control.",
asil = ASIL.B,
instanceSpecifier = InstanceSpecifier("Camera/CameraControlOut"),
serviceInterface = cameraControlServiceInterface
)
}
class Fusion : Component(
name = "Fusion",
description = "Object fusion component.",
asil = ASIL.B
) {
val cameraImagesIn: RequiredPort<CameraImagesServiceInterface> = RequiredPort(
name = "CameraImagesIn",
description = "Required camera images.",
asil = ASIL.B,
instanceSpecifier = InstanceSpecifier("Fusion/CameraImagesIn"),
serviceInterface = cameraImagesServiceInterface
)
}
class StateManager : Component(
name = "StateManager",
description = "System state manager.",
asil = ASIL.B
) {
val cameraControlIn: RequiredPort<CameraControlServiceInterface> = RequiredPort(
name = "CameraControlIn",
description = "Required camera control.",
asil = ASIL.B,
instanceSpecifier = InstanceSpecifier("StateManager/CameraControlIn"),
serviceInterface = cameraControlServiceInterface
)
}
val camera: Camera = Camera()
val fusion: Fusion = Fusion()
val stateManager: StateManager = StateManager()
// Access via `.serviceInterface`:
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val imageEvent = fusion.cameraImagesIn.serviceInterface.imageStream
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val calibrateMethod: Method = stateManager.cameraControlIn.serviceInterface.calibrate
Further examples
Direct interface points with implementation types
// Tutorial sources follow documentation navigation paths, independently of their example packages.
@file:Suppress("PackageDirectoryMismatch")
package com.stigway.docs.tutorial.context
import com.stigway.metamodels.highway.*
private val batteryVoltageCpp = DataType(implementationType = "BatteryVoltage")
private val batteryVoltageMsgCpp = DataType(implementationType = "BatteryVoltageMsg")
private val sensorErrorPayload = DataType(implementationType = "void")
class BatterySensor : Component(
name = "BatterySensor",
description = "Reads battery voltage and provides it to consumers.",
asil = ASIL.B
) {
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val voltage = Field(
name = "Voltage",
description = "Battery voltage in volts.",
asil = ASIL.B,
data = batteryVoltageCpp,
hasSetter = false
)
val sensorFailure: CheckedError = CheckedError(
name = "BatterySensorFailure",
description = "Battery sensor self-test failed.",
asil = ASIL.B,
data = sensorErrorPayload,
errorCode = 10
)
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val readNow: Method = Method(
name = "ReadBatteryNow",
description = "Trigger a synchronous battery read.",
asil = ASIL.B,
raises = listOf(sensorFailure)
)
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val voltageEvent = Event(
name = "VoltageEvent",
description = "Voltage update event.",
asil = ASIL.B,
data = batteryVoltageMsgCpp
)
}
val batterySensor: BatterySensor = BatterySensor()
Direct interface points with Kotlin payload types
// Tutorial sources follow documentation navigation paths, independently of their example packages.
@file:Suppress("PackageDirectoryMismatch")
package com.stigway.docs.tutorial.context
import com.stigway.metamodels.highway.*
class BatteryVoltage : DataType(implementationType = "BatteryVoltage") {
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val volts: Double? = null
}
private val batteryVoltage = BatteryVoltage()
class BatterySensorTyped : Component(
name = "BatterySensorTyped",
description = "Battery sensor with Kotlin-defined payload types.",
asil = ASIL.B
) {
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val voltageEvent: Event<BatteryVoltage> = Event(
name = "VoltageEvent",
description = "Voltage update event.",
asil = ASIL.B,
data = batteryVoltage
)
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val readNow: Method = Method(
name = "ReadNow",
description = "Trigger a synchronous read.",
asil = ASIL.B
)
}
val batterySensorTyped: BatterySensorTyped = BatterySensorTyped()
Direct interface points with imported Kotlin stubs
Define/import the Kotlin stubs:
// Tutorial sources follow documentation navigation paths, independently of their example packages.
@file:Suppress("PackageDirectoryMismatch")
package com.stigway.docs.tutorial.context
import com.stigway.metamodels.highway.DataType
/**
* Kotlin stubs that represent C++/IDL types.
*
* These can be hand-written or generated by your own pipeline.
* The important part is that they extend [DataType] and can include Kotlin members.
*/
open class RadarPointCloud : DataType(implementationType = "car::RadarPointCloud")
open class BrakeRequest : DataType(implementationType = "car::BrakeRequest")
Use them in an architecture model:
// Tutorial sources follow documentation navigation paths, independently of their example packages.
@file:Suppress("PackageDirectoryMismatch")
package com.stigway.docs.tutorial.context
import com.stigway.metamodels.highway.ASIL
import com.stigway.metamodels.highway.Component
import com.stigway.metamodels.highway.Event
val radarPointCloudImported = RadarPointCloud()
val cameraImageImported = CameraImage()
class SensorFusion : Component(
name = "SensorFusion",
description = "Fuses radar and camera data.",
asil = ASIL.B
) {
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val radarPointCloudIn: Event<RadarPointCloud> = Event(
name = "RadarPointCloudIn",
description = "Incoming radar point clouds.",
asil = ASIL.B,
data = radarPointCloudImported
)
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val cameraImageIn: Event<CameraImage> = Event(
name = "CameraImageIn",
description = "Incoming camera images.",
asil = ASIL.B,
data = cameraImageImported
)
}
val sensorFusion: SensorFusion = SensorFusion()
AUTOSAR-like services and ports
Important: ports do not delegate members; access interface members through .serviceInterface:
Define a service interface:
// Tutorial sources follow documentation navigation paths, independently of their example packages.
@file:Suppress("PackageDirectoryMismatch")
package com.stigway.docs.tutorial.context
import com.stigway.metamodels.highway.*
private val radarPointCloudService = RadarPointCloud()
private val radarUpdateRateService = DataType(implementationType = "uint32")
private val calibrationErrorPayload = DataType(implementationType = "void")
/**
* Service interface definition: declares which members exist.
*/
class RadarServiceInterface : ServiceInterface(
name = "RadarService",
description = "Radar service interface definition.",
asil = ASIL.B
) {
val pointCloudEvent = Event(
name = "PointCloudEvent",
description = "Published radar point cloud.",
asil = ASIL.B,
data = radarPointCloudService
)
val updateRate = Field(
name = "UpdateRate",
description = "Update rate in Hz.",
asil = ASIL.B,
data = radarUpdateRateService
)
val calibrationFailed: CheckedError = CheckedError(
name = "CalibrationFailed",
description = "Radar calibration failed.",
asil = ASIL.B,
data = calibrationErrorPayload,
errorCode = 1
)
val calibrate: Method = Method(
name = "Calibrate",
description = "Start radar calibration.",
asil = ASIL.B,
raises = listOf(calibrationFailed)
)
}
/** Shared service interface instance (architecture contract). */
val radarServiceInterface: RadarServiceInterface = RadarServiceInterface()
Use the service via ports on elements:
// Tutorial sources follow documentation navigation paths, independently of their example packages.
@file:Suppress("PackageDirectoryMismatch")
package com.stigway.docs.tutorial.context
import com.stigway.metamodels.highway.*
class RadarProvider : Component(
name = "RadarProvider",
description = "Provides radar point clouds to consumers.",
asil = ASIL.B
) {
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val radarOut: ProvidedPort<RadarServiceInterface> = ProvidedPort(
name = "RadarOut",
description = "Provided radar service port.",
asil = ASIL.B,
instanceSpecifier = InstanceSpecifier(
"RadarProvider/RadarOut"
),
serviceInterface = radarServiceInterface
)
}
class HpcConsumer : Component(
name = "HPC",
description = "Consumes radar data for decision logic.",
asil = ASIL.B
) {
val radarIn: RequiredPort<RadarServiceInterface> = RequiredPort(
name = "RadarIn",
description = "Required radar service port.",
asil = ASIL.B,
instanceSpecifier = InstanceSpecifier(
"HPC/RadarIn"
),
serviceInterface = radarServiceInterface
)
}
val provider: RadarProvider = RadarProvider()
val hpc: HpcConsumer = HpcConsumer()
// Ports do not delegate members; access service members through `.serviceInterface`:
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val ev = hpc.radarIn.serviceInterface.pointCloudEvent
val field = hpc.radarIn.serviceInterface.updateRate
val method: Method = hpc.radarIn.serviceInterface.calibrate
Structural groups inside a service
// Tutorial sources follow documentation navigation paths, independently of their example packages.
@file:Suppress("PackageDirectoryMismatch")
package com.stigway.docs.tutorial.context
import com.stigway.metamodels.highway.*
private val radarPointCloudGrouped = RadarPointCloud()
private val radarUpdateRateGrouped = DataType(implementationType = "uint32")
/** Structural grouping container inside a service. */
class RadarPubPort : ServicePort(
name = "Pub",
description = "Published outputs.",
asil = ASIL.B
) {
val pointCloud = Event(
name = "PointCloud",
description = "Radar point cloud stream.",
asil = ASIL.B,
data = radarPointCloudGrouped
)
}
class RadarCtrlPort : ServicePort(
name = "Ctrl",
description = "Control plane.",
asil = ASIL.B
) {
val updateRate = Field(
name = "UpdateRate",
description = "Update rate configuration.",
asil = ASIL.B,
data = radarUpdateRateGrouped
)
val calibrate: Method = Method(
name = "Calibrate",
description = "Start calibration.",
asil = ASIL.B
)
}
class RadarServiceGroupedInterface : ServiceInterface(
name = "RadarServiceGrouped",
description = "Radar service grouped into Pub/Ctrl.",
asil = ASIL.B
) {
val pub: RadarPubPort = RadarPubPort()
val ctrl: RadarCtrlPort = RadarCtrlPort()
}
val radarServiceGroupedInterface: RadarServiceGroupedInterface = RadarServiceGroupedInterface()
class RadarComponentGrouped : Component(
name = "RadarComponentGrouped",
description = "Radar component providing grouped service.",
asil = ASIL.B
) {
val radarOut: ProvidedPort<RadarServiceGroupedInterface> = ProvidedPort(
name = "RadarOut",
description = "Provided port typed by grouped radar service.",
asil = ASIL.B,
instanceSpecifier = InstanceSpecifier(
"RadarComponentGrouped/RadarOut"
),
serviceInterface = radarServiceGroupedInterface
)
}
val radarGrouped: RadarComponentGrouped = RadarComponentGrouped()
// Grouped access goes through `.serviceInterface`:
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val pointCloud = radarGrouped.radarOut.serviceInterface.pub.pointCloud
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val rate = radarGrouped.radarOut.serviceInterface.ctrl.updateRate
BodyController with registers and an I-PDU
// Tutorial sources follow documentation navigation paths, independently of their example packages.
@file:Suppress("PackageDirectoryMismatch")
package com.stigway.docs.tutorial.context
import com.stigway.metamodels.highway.*
private val brakePedalRaw = DataType(implementationType = "uint16")
private val brakePedalScaled = DataType(implementationType = "float32")
private val brakeLightRequestPdu = DataType(implementationType = "can::BrakeLightRequestPdu")
class BodyController : SystemElement(
name = "BodyController",
description = "Body controller system for brake pedal and brake lights.",
asil = ASIL.B
) {
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val brakePedalPositionRaw = Register(
name = "BrakePedalPositionRaw",
description = "Raw ADC reading for brake pedal position.",
asil = ASIL.B,
data = brakePedalRaw,
address = 0x1200L,
widthBits = 16,
hasSetter = false
)
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val brakePedalPositionScaled = Field(
name = "BrakePedalPositionScaled",
description = "Scaled pedal position (0..100%).",
asil = ASIL.B,
data = brakePedalScaled,
hasSetter = false
)
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val brakeLightRequest = IPdu(
name = "BrakeLightRequest",
description = "Brake light request I-PDU.",
asil = ASIL.B,
data = brakeLightRequestPdu,
pduId = 0x542,
cycleMs = 20
)
}
val bodyController: BodyController = BodyController()
ADC driver with application, OS, and hardware interfaces
// Tutorial sources follow documentation navigation paths, independently of their example packages.
@file:Suppress("PackageDirectoryMismatch")
package com.stigway.docs.tutorial.context
import com.stigway.metamodels.highway.*
private val adcRegisterValue = DataType(implementationType = "uint32")
class AdcAppServiceInterface : ServiceInterface(
name = "AdcAppService",
description = "ADC application-facing API.",
asil = ASIL.B
) {
val adcInit: Method = Method(
name = "adc_init",
description = "Initialize ADC.",
asil = ASIL.B
)
val adcRead: Method = Method(
name = "adc_read",
description = "Read ADC channel.",
asil = ASIL.B
)
}
class AdcOsServiceInterface : ServiceInterface(
name = "AdcOsService",
description = "ADC OS scheduling interface.",
asil = ASIL.B
) {
val adcSchedule: Method = Method(
name = "adc_schedule",
description = "Periodic ADC runnable.",
asil = ASIL.B
)
}
class AdcHwServiceInterface : ServiceInterface(
name = "AdcHwService",
description = "ADC hardware register interface.",
asil = ASIL.B
) {
val adcCtrl = Register(
name = "ADC_CTRL",
description = "ADC control register.",
asil = ASIL.B,
data = adcRegisterValue,
address = 0x1000L,
widthBits = 32
)
val adcStatus = Register(
name = "ADC_STATUS",
description = "ADC status register.",
asil = ASIL.B,
data = adcRegisterValue,
address = 0x1004L,
widthBits = 32,
hasSetter = false
)
}
val adcAppServiceInterface: AdcAppServiceInterface = AdcAppServiceInterface()
val adcOsServiceInterface: AdcOsServiceInterface = AdcOsServiceInterface()
val adcHwServiceInterface: AdcHwServiceInterface = AdcHwServiceInterface()
class AdcDriver : Component(
name = "AdcDriver",
description = "ADC driver: app/os APIs and HW register access.",
asil = ASIL.B
) {
val appPort: ProvidedPort<AdcAppServiceInterface> = ProvidedPort(
name = "AppPort",
description = "Provided app-facing ADC API.",
asil = ASIL.B,
instanceSpecifier = InstanceSpecifier(
"AdcDriver/AppPort"
),
serviceInterface = adcAppServiceInterface
)
val osPort: ProvidedPort<AdcOsServiceInterface> = ProvidedPort(
name = "OsPort",
description = "Provided OS scheduling API.",
asil = ASIL.B,
instanceSpecifier = InstanceSpecifier(
"AdcDriver/OsPort"
),
serviceInterface = adcOsServiceInterface
)
val hwPort: RequiredPort<AdcHwServiceInterface> = RequiredPort(
name = "HwPort",
description = "Required HW register access interface.",
asil = ASIL.B,
instanceSpecifier = InstanceSpecifier(
"AdcDriver/HwPort"
),
serviceInterface = adcHwServiceInterface
)
}
val adcDriver: AdcDriver = AdcDriver()
// Access via `.serviceInterface`:
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val adcReadMethod: Method = adcDriver.appPort.serviceInterface.adcRead
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val adcScheduleMethod: Method = adcDriver.osPort.serviceInterface.adcSchedule
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val adcCtrlRegister = adcDriver.hwPort.serviceInterface.adcCtrl
Radar, camera, HPC, and braking chain
// Tutorial sources follow documentation navigation paths, independently of their example packages.
@file:Suppress("PackageDirectoryMismatch")
package com.stigway.docs.tutorial.context
import com.stigway.metamodels.highway.ASIL
import com.stigway.metamodels.highway.Connector
import com.stigway.metamodels.highway.Event
import com.stigway.metamodels.highway.SystemElement
private val systemChainRadarPointCloud = RadarPointCloud()
private val systemChainCameraImage = CameraImage()
private val systemChainBrakeRequest = BrakeRequest()
class RadarSystem : SystemElement(
name = "RadarSystem",
description = "Radar sensor system producing object detections.",
asil = ASIL.B
) {
val radarPointCloudOut = Event(
name = "RadarPointCloudOut",
description = "Radar point cloud produced by radar system.",
asil = ASIL.B,
data = systemChainRadarPointCloud
)
}
class CameraSystem : SystemElement(
name = "CameraSystem",
description = "Camera sensor system producing images.",
asil = ASIL.B
) {
val imagesOut = Event(
name = "ImagesOut",
description = "Camera images produced by camera system.",
asil = ASIL.B,
data = systemChainCameraImage
)
}
class HpcSystem : SystemElement(
name = "HPCSystem",
description = "Sensor fusion and decision logic system.",
asil = ASIL.B
) {
val radarPointCloudIn = Event(
name = "RadarPointCloudIn",
description = "Radar point cloud consumed by HPC.",
asil = ASIL.B,
data = systemChainRadarPointCloud
)
val imagesIn = Event(
name = "ImagesIn",
description = "Images consumed by HPC.",
asil = ASIL.B,
data = systemChainCameraImage
)
val brakeRequestOut = Event(
name = "BrakeRequestOut",
description = "Brake request generated by HPC.",
asil = ASIL.B,
data = systemChainBrakeRequest
)
}
class BrakingSystem : SystemElement(
name = "BrakingSystem",
description = "Brake actuator system consuming brake requests.",
asil = ASIL.B
) {
val brakeRequestIn = Event(
name = "BrakeRequestIn",
description = "Brake request consumed by braking system.",
asil = ASIL.B,
data = systemChainBrakeRequest
)
}
val radarSys: RadarSystem = RadarSystem()
val cameraSys: CameraSystem = CameraSystem()
val hpcSys: HpcSystem = HpcSystem()
val brakingSys: BrakingSystem = BrakingSystem()
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val radarToHpc: Connector = Connector(
name = "Radar->HPC",
description = "Bind radar point cloud stream to HPC radar input.",
asil = ASIL.B,
from = radarSys.radarPointCloudOut,
to = hpcSys.radarPointCloudIn
)
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val cameraToHpc: Connector = Connector(
name = "Camera->HPC",
description = "Bind camera image stream to HPC image input.",
asil = ASIL.B,
from = cameraSys.imagesOut,
to = hpcSys.imagesIn
)
@Suppress("unused") // Tutorial declaration, retained for documentation and reflective model loading.
val hpcToBraking: Connector = Connector(
name = "HPC->Braking",
description = "Bind brake request output to braking request input.",
asil = ASIL.B,
from = hpcSys.brakeRequestOut,
to = brakingSys.brakeRequestIn
)