Outils › Importer des modèles

Outils

Cette page n’est pas encore traduite : voici la version anglaise.

Importing

Models from elsewhere, in arche-lang: an Arche project, a SysML v2 model, a CAN DBC, ASN.1 types, a robot (URDF, MJCF), a Capella project and Polarion requirements.

An Arche project#

archec import <arche-project> -o <dir> rewrites any Arche project in arche-lang, and archec diff <a> <b> compares two projects as data (key order, quoting and empty values do not count; list order does). The import builds what it wrote, compares it with the project, and fixes each difference on the declaration that made it — until the build gives the project back exactly — then writes the short forms and copies the files the project refers to (assets). What the language has no word for is written with attr key = value.

examples/kacao is the KaCAO project (antenna control on a Kria KR260) imported this way: seven model files, 211 components, 206 requirements with their verification blocks, 54 scenarios with alt/loop fragments, 1280 traces, French with English translations ("Libellé" en "Label") — and archec build gives back the very project it came from. A name in brackets in a text is a checked reference: [REQ-KAC-037], [kria.ps.app.tracking]. Arche gets it as it writes references (KRIA260.yaml#kria.ps.app.tracking), so nothing changes in the project.

A long text puts its translation on the next line, under it (fmt --simplify splits such lines):

description "Le PS doit faire tourner l'application…"
         en "The PS shall run the application…"

In VS Code, a workspace with several projects (examples/ has three) shows one root per project in the Model and Files views.

A SysML v2 model#

archec import model.sysml -o model.arche reads a model in SysML v2 textual notation (stdout without -o). The result is built at once, and its errors are listed. The parser is tolerant: it reads each member of a body, a header up to ; or a { … } body, and only then works out what it is. What it does not know is not an error.

SysML v2 arche-lang
the one package around the model project "Name" (its doc is the description); inner packages are packages
part def D :> B component D extends B; a part def only used by a part at the top (part car : Car) is system Car
part w : Wheel[4], part x { … } part w: Wheel[4], component x { … }
attribute mass = 1200 [kg], attribute g : Gear = Gear::park var mass: float64 = 1200 unit kg, var g: Gear = park
port def P { out item t : Torque; }, port p : ~P port-type P; in p: P receives Torque (~ turns the items around)
item def M, attribute def S { … }, attribute def T :> Real, enum def E message M, struct S, type T = float64, enum E
an item def also used as data (in cmd : Cmd; in an action, attribute c : Cmd) struct CmdData { … } and message Cmd: CmdData: a message is not a type, so the action takes cmd: CmdData
interface def I { end a : P; end b : ~P; } interface I { messages … } (the items of its ends' port defs)
connect a.p to b.q, connection c connect …, interface i : I connect … a.p -> b.q (turned to go from the out end), link c a.p -> b.q
flow of M from a.p to b.q a.p -> b.q { carries M }
exhibit state s : S { entry; then A; state A; … } states { state A; … }, the initial state first
transition first A accept e if g then B, accept e then B A -> B on e [g]; accept after 10 [s] is a trigger in text, on "after 10 [s]"
action a : Def { in x : Real; out y : Real; }, perform action fn a(x: float64) -> float64
requirement <'R1'> massReq /* Mass */ { doc /* … */ } requirement R1 "Mass" { text "…" }; a nested requirement derives its parent
requirement mass : MassReq; (a usage of a requirement def) requirement mass { text "…" }, the text its definition's doc when it has none of its own
satisfy R by x, verify R satisfies R on part x (or on the definition), verifies R
verification def T { objective { verify R; } } test-plan T { case T-1 }, and verified-by T on R
doc /* … */, // and /* … */ comments description "…", comments

Limits:

  • Names are global in arche-lang, so import and qualified names are dropped: A::B::C is C. Two things with the same name in different packages clash.
  • Types from the standard library (ScalarValues, ISQ, SI…) map to float64, int32, uint32, bool and string. A quantity keeps its unit from the value, not from its type.
  • Behaviour is Python in Arche: an entry/exit action is an empty on-enter/on-exit, a transition's do effect is a comment, and an action's steps are left out. A do action becomes the state's do only when the part has that action.
  • satisfy R by world.car.engine goes on the part engine of car's definition, so it holds for every car.
  • Expressions are not read: a guard is copied as it is, and a default value that is not a literal or a name is left out with a note.
  • Nothing is dropped silently: what has no place is a // sysml line N: … comment where it was (N its line in the SysML file), and a note line N: …. Left so: use cases, calcs, constraints, analysis, allocation, views, subject/require in requirements, connection defs, n-ary connections, a second state machine in one part. Metadata (@X, #X, metadata def) is left out with a note.
  • arche-lang has no multiplicity range: [2..4] becomes [4], and [*] on a part is left out.

A CAN DBC#

archec import bus.dbc -o bus.arche reads a CAN database and writes the constructs archec gen --target dbc writes a DBC from, so the one gives the other back: importing the DBC that gen writes for examples/autonomous-car and generating it again gives the same file, byte for byte.

DBC arche-lang
BO_ 256 EngineData: 8 ECM message EngineData { byte-order …; size 8 } — a wire format; the route's id 0x100
SG_ EngineSpeed : 7|16@0+ (0.25,0) [0|16383.75] "rpm" TCM EngineSpeed: float32 unit rpm at 0 bits 16 scale 0.25 — at counts along the frame as the wire format does (a Motorola signal from its most significant bit); the order of most signals is the message's byte-order, the others say their own
the sign (-), factor and offset signed, scale, offset: a scaled signal is a float32 (float64 past 24 bits); an unscaled one the smallest uint/int that holds it, a 1-bit one a bool
[min|max] range min..max, when it is not what the bits carry anyway (and not [0|0])
SIG_VALTYPE_ … : 1 / 2 float32 / float64, sent as its IEEE bits
VAL_ 512 Gear 0 "Park" 1 "Reverse" … enum Gear { Park = 0, Reverse = 1, … } (named after its VAL_TABLE_ when one is the same); a text that is not a name is the literal's label
CM_ BO_, CM_ SG_, CM_ BU_ the label of the message, of the field, of the node's component and processor
BA_ "GenMsgCycleTime" BO_ 256 10; period 10ms on the message (and gen writes it back as GenMsgCycleTime)
BU_: ECM TCM component ECM with an out port per frame it sends and an in port per frame it receives (can, or can-fd when a frame is longer than 8 bytes or the bus says CAN FD); a part of the logical system; a processor of the platform on the bus; a binary on it in the deployment
a frame and its receivers a link and a route over the bus for each receiver, all at the frame's id (gen writes them as one frame again)

Limits:

  • A multiplexed signal (m0, m1…) has no place: it is a // dbc line N: comment in its message, and the multiplexor (M) is a plain field. Signal groups, environment variables, value tables no signal uses, and attributes other than the cycle time and the bus type are comments too, each with a note.
  • Each receiver gets the whole frame: when its signals go to different nodes, gen lists every receiver on every signal. Vector__XXX (no receiver) is a part of its own, which gen does not list in BU_.
  • A frame sent by several nodes (BO_TX_BU_) is sent by the first.
  • A value table on a scaled or signed signal, or past 8 bits, stays a comment on its field. An IEEE float with a factor or an offset loses them (the language sends a float as its bits).
  • Two frames with one name are one message when their signals are the same, else the second is renamed (Name_2). An extended id is kept as it is, its bit 31 set, in the route's id.
  • The file's own comment (CM_ "…") is a comment at the top, which gen does not write back.

ASN.1#

archec import types.asn [more.asn…] -o types.arche reads ASN.1 type definitions (X.680, the part that says data). Each module is a package; a module's IMPORTS … FROM M is found among the modules of the files given. Names are global in arche-lang: a name two modules define is suffixed in the second (Speed_2), a hyphen is an underscore (Common-Types → Common_Types).

ASN.1 arche-lang
BOOLEAN, REAL bool, float64
INTEGER (0..30000), INTEGER the smallest uint/int that holds it, with range 0..30000; int64 unbounded
ENUMERATED { ok, degraded(5), failed } enum { ok = 0, degraded = 5, failed = 1 } — unnumbered items take the smallest number free, as X.680 says
OCTET STRING (SIZE (4)), OCTET STRING byte[4], bytes
BIT STRING (SIZE (8)) bool[8] (bool[] when the size varies)
IA5String, UTF8String, VisibleString, PrintableString… string, its kind and SIZE in a comment
SEQUENCE { a T, b U OPTIONAL, c V DEFAULT x }, SET { … } struct { a: T; b: optional U; c: V = x }
CHOICE { stop NULL, go Speed } union { stop; go: Speed }
SEQUENCE (SIZE (4)) OF T, SEQUENCE SIZE (1..16) OF T T[4], T[] (the bounds in a comment)
a SEQUENCE no other type uses message (what is sent), its fields inline; a CHOICE no other type uses: its union and message <Name>_MSG: <Name>
an inline SEQUENCE, CHOICE or ENUMERATED in a field a type of its own named after its place (Report_mode)
maxSpeed INTEGER ::= 30000 const maxSpeed = 30000 (INTEGER, BOOLEAN, REAL and string values)
X ::= Y, X ::= INTEGER (…) type X = Y, type X = uint16 range …

Not supported, each with a note naming the line (and, for a whole assignment, a // asn1 line N: comment where it was): information object classes and objects (CLASS, &field), parameterised types (Pair { T } ::= …), value sets, COMPONENTS OF, the values of anything but an INTEGER, BOOLEAN, REAL or string, named numbers on an INTEGER (kept as a comment), OBJECT IDENTIFIER and the time types (written as string), and every constraint but a value range, a list of single values (read as their range) and SIZE — FROM, PATTERN, CONTAINING, WITH COMPONENTS are left out. Tags ([3] IMPLICIT) and extension markers (..., [[ … ]]) say the encoding and how the type may grow: they are left out, the extension additions read as ordinary components. A type named in IMPORTS but in none of the files is bytes. The encoding (BER, PER, ACN) is not read: for a bit-exact layout, give the message a wire format.

A robot: URDF and MJCF#

archec import robot.urdf -o robot.arche reads a URDF robot, archec import scene.xml a MuJoCo model (MJCF; a .xml is read by its <robot> or <mujoco> root). The result is a component for the robot, its bodies nested inside it as the kinematic tree goes, each with the language's bodies — mass, shape, joint — so archec gen --target mujoco writes it back; a part commands that sends to its actuators; and a system with both. It is written as archec fmt writes and built at once.

component Pendulum "pendulum" {
  solver 1ms                                                   // <option timestep>
  component pole {
    prop hinge = {damping: 0.05}                               // what `joint` does not say
    physical {
      mass 0.3309144262kg                                      // from its geoms, as MuJoCo computes it
      shape capsule 0.02m, 0.5m at 0, 0, -0.25m
      joint hinge y at 0, 0, 1m range -1.570796327..1.570796327 = 0.5
    }

    component servo {
      kind actuator
      prop actuator = {type: position, joint: "hinge"}
      prop kp = 10

      in command: data receives SERVO_CMD                      // value: float64 unit rad range -1.5..1.5
    }
    component angle "angle (jointpos)" {
      kind sensor
      prop sensor = jointpos
      prop joint = "hinge"

      out value: data sends ANGLE                              // value: float64 unit rad
    }
  }
}
Source arche-lang
a URDF <link>, an MJCF <body> a component in its parent's, mass
its place in its parent, its joint (URDF revolute, continuous, prismatic, floating, fixed; MJCF hinge, slide, ball, free) joint hinge y at x, y, z rot r, p, y range lo..hi = start (URDF's origin rpy, MJCF's quat, euler, axisangle, xyaxes or zaxis as rot); the joint's damping, URDF's effort and velocity limits in prop <joint> = {…}
<inertial> mass, prop inertial = {pos, quat, diaginertia | fullinertia}; without one (MJCF), the mass of the geoms, as MuJoCo computes it
<collision>, <geom> the first the language can say is the body's shape (box, sphere, capsule, cylinder; rot when it is turned in its body); the others prop <name> = {geom: …} with MuJoCo's sizes, a URDF <visual> with visual: true
<site> prop <name> = {site: sphere, pos, size}
<transmission>, a <ros2_control> command interface, an MJCF actuator a part kind actuator in the body of its joint, in command receiving a message whose field has its unit (rad: position, rad/s: velocity) and its ctrlrange as range; prop kp, prop kv, prop actuator = {type, joint, gear…}; commands sends to it
a Gazebo or URDF <sensor>, an MJCF sensor or camera a part kind sensor in its body, prop sensor = imu | jointpos | accelerometer…, out value sending a message with its units, at its rate (period); width, height, channels, range as the gazebo target reads them
MJCF <include>, <default> classes, <frame> read in, resolved, folded into what they hold
MJCF <option timestep gravity>, the first <key> solver, prop gravity; each joint's start (= value)

Lengths are in m, angles in rad (MJCF degrees are turned into radians, Euler angles and axes into quaternions), masses in kg. A body's frame is the source's: its joint's rot turns it in its parent's (a turn about one axis as rot z 90deg, any other as roll, pitch, yaw), and its axis, its shape, its prop records and the bodies in it are in that frame, as the source says them. The rest — lights, materials, tendons, equalities, contact pairs, Gazebo plugins, mimic joints, the keyframes after the first — is a // urdf: or // mjcf: comment where it was, and a note; an attribute archec does not read is a note.

Limits: a xacro file must be expanded first (xacro robot.urdf.xacro > robot.urdf). A body has one shape and one joint: its other geoms and joints stay as records, which gen --target mujoco does not read. A mesh is its file name: its volume is not computed, so a body with only a mesh and no <inertial> has no mass. Actuator defaults are read from the element's own class entry, then general.

Capella#

archec capella exchanges a model with Capella 6.1, both ways. It runs the Arche app's own Capella converter (bundled in vendor/arche.js), so archec and the app's Capella dialog give the same files.

archec capella main.arche -o project.zip          # → a Capella project, as the archive Capella imports
archec capella main.arche -o project/             # → the same project as a folder
archec capella model.capella -o model/            # → arche-lang (the .capella's fragments and .aird beside it are read)
archec capella project.zip -o model/              # → arche-lang, from a Capella project exported as an archive
  • Export. The model is built, then each component goes to the Arcadia level its layer names (Operational, System, Logical, Physical, EPBS). Data, messages and interfaces go to the most abstract level that uses them. The .capella is checked against the Capella 6.1 metamodel before anything is written; a problem is listed and the exit code is 1. What Capella has no place for (a derives between two requirements, a scenario step on no exported exchange…) is listed with the reason. In Capella: File › Import › General › Existing Projects into Workspace.

  • Import. The model becomes the Arche project the app's import makes: one model per Arcadia level, laid out as the .aird draws it (--no-diagrams to skip the layout), with --prefix for the file names and --layers oa,sa,la,pa,epbs to keep only some levels. That project is then rewritten in arche-lang as archec import does, and built. Requirements and scenarios become requirement and scenario declarations; the rest of the model too:

    system-function fly "Fly" {                      // a function, its parts inside it
      system-function steer "Steer" {
        kind route
        allocated dd-sa.drone                         // allocated to a component, by path
        in order "Order" receives command             // its ports and what they carry
      }
    }
    function-flow read-stick.stick -> steer.order as orders { name "Orders"; carries command }
    function-chain branching "Branching" { step or { kind or }; edge or -> steer { when "armed" } }
    use-case fly-a-mission "Fly a mission" {          // a capability
      involves dd-sa.pilot, dd-sa.drone, steer, pilot-chain, command
    }
    property colour { type string; kinds system; group physical }   // a property value's definition
    component drone "Drone" { mass 1.5; prop certified = true; prop physical = {colour: "red"} }

    The functions and capabilities of the one level that has some go to functional.yaml and usecases.yaml; when several levels have functions, each keeps its file (file "dd-la-functions.yaml" on its functions, flows and chains), and a name both levels use is written as its address where it would be ambiguous. Missions stay attr of the capabilities file. --project dir keeps the intermediate Arche project.

  • Round trip. The import keeps where each element came from (the <prefix>-capella.yaml data block), so exporting it again gives the elements their Capella ids back: components, ports, exchanges, functions and their ports, functional exchanges, chains, capabilities, missions and property values. What the app's export makes anew (data type literals, state machine regions, chain involvements, scenario lifelines) gets new ids: about 70 to 80% of a model's ids survive.

Polarion#

archec polarion writes a Polarion project's work items as requirements, from a JSON export or straight from the server. It works for any Polarion project: the standard fields are known, a custom field is recognised by its name or said in polarion.yaml, and what has no place in the language is kept as attr, so nothing is lost.

archec polarion workitems.json --project main.arche -o spec/polarion.arche   # an export
POLARION_TOKEN=… archec polarion pull main.arche                            # the server
archec polarion pull main.arche --dry-run                                   # the request, not sent

In VS Code: Arche: Import from Polarion… does the same — it asks the server, the project and the query once (kept in polarion.yaml) and the token once (kept in VS Code's secret storage; Arche: Forget the Polarion Token removes it), writes the file, offers to include it, and builds.

The export is what the REST API answers to GET /rest/v1/projects/{p}/workitems?fields[workitems]=@all: one page {data: [...]}, an array of pages, or archec polarion pull --save workitems.json. archec import workitems.json does the same as archec polarion workitems.json.

polarion.yaml, beside main.arche (all optional):

url: https://polarion.example.com/polarion   # pull: the server
project: MYPROJ                              # pull: the project id
query: "type:requirement AND NOT status:rejected"
out: spec/polarion.arche                     # where the requirements go (else -o, else stdout)
id: id                                       # id (MYPROJ-123) | puid
documents:                                   # a Polarion document → the project's deliverable
  System Requirements: SRS
modules: [System]                            # only the documents whose id contains one of these
fields:                                      # a field → what it is in the language
  verifMethod: method                        #   description text rationale priority status
  testLevel: attr test-level                 #   category method owner origin, attr <name>, skip
  legacyType: skip
values:                                      # a property's Polarion values → the language's
  status: { inReview: proposed }
  method: { R: inspection }
derives: [parent, satisfies, refines]        # the link roles that are `derives`
Polarion arche-lang
id (or puid with id: puid) the requirement's id
title its name
description, a …Rationale field (XHTML) description, rationale, as plain text (paragraphs, line breaks, list items kept)
severity (must_have, should_have, nice_to_have, blocker…) or else priority (90 … 10) priority (critical, high, medium, low)
status (draft, open, inReview, approved, rejected…) status (draft, proposed, approved, implemented, verified, obsolete)
a …Category… field category (the first; all of them as attr categories)
a …Method… field (T, A, I, D, test, analysis…) method
an …Owner… field, else the assignee owner
the document (module) document: the deliverable documents: names, or one written for it
id, puid, outline number origin "Polarion MYPROJ-123 · REQ-0085 · SRS-4.2"
a link in a derives: role to a work item written here, or to a requirement of the project derives
any other link attr polarion-links = {describes: ["MYPROJ-9"], parent: ["MYPROJ-1"]}
any other field, a custom enum or user relationship attr <field-name> (costCenter → attr cost-center)

A value with no meaning in the language (a status weird) is written as a comment and named in a note: map it under values:. With --project (or pull main.arche), a requirement the project already declares is left out and links to it are derives; what the output file itself declared is rewritten, so pulling again updates it.