├── .cargo └── config.toml ├── .clang-format ├── .github └── workflows │ └── default.yml ├── .gitignore ├── CANape ├── CANape.ini ├── CanapeCmd.ini ├── XCP_104.aml ├── XCPlite.cna └── xcp_lite_autodetect.a2l ├── Cargo.toml ├── Clippy.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── XCP_104.aml ├── build.rs ├── examples ├── calibration_demo │ ├── CANape │ │ ├── .gitignore │ │ ├── CANape.ini │ │ ├── CanapeCmd.ini │ │ ├── FunctionFallback │ │ │ └── CycleTime.fup │ │ ├── xcp_demo.cna │ │ └── xcp_demo_autodetect.a2l │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs ├── heap_demo │ └── CANape │ │ └── .gitignore ├── hello_xcp │ ├── CANape.png │ ├── CANape │ │ ├── .gitignore │ │ ├── CANape.ini │ │ ├── CanapeCmd.ini │ │ ├── xcp_demo.cna │ │ └── xcp_demo_autodetect.a2l │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs ├── multi_thread_demo │ ├── .gitignore │ ├── CANape │ │ ├── CANape.ini │ │ ├── CanapeCmd.ini │ │ ├── multi_thread_demo.cna │ │ └── multi_thread_demo_autodetect.a2l │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs ├── point_cloud_demo │ ├── .gitignore │ ├── CANape │ │ ├── CANape.ini │ │ ├── CanapeCmd.ini │ │ ├── point_cloud.cna │ │ ├── point_cloud.gvc │ │ └── xcp_lite_autodetect.a2l │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs ├── rayon_demo │ ├── CANape │ │ ├── .gitignore │ │ ├── CANape.ini │ │ ├── CanapeCmd.ini │ │ ├── UpdateHTMLWindows.cns │ │ └── rayon_demo.cna │ ├── Cargo.toml │ ├── README.md │ ├── mandelbrot.a2l │ └── src │ │ └── main.rs ├── single_thread_demo │ ├── CANape │ │ ├── CANape.ini │ │ ├── CanapeCmd.ini │ │ └── single_thread.cna │ ├── Cargo.toml │ ├── README.md │ ├── single_thread_demo_autodetect.a2l │ └── src │ │ └── main.rs ├── struct_measurement_demo │ ├── CANape │ │ ├── .gitignore │ │ ├── CANape.ini │ │ ├── CanapeCmd.ini │ │ ├── FunctionFallback │ │ │ └── .gitignore │ │ ├── xcp_demo.cna │ │ ├── xcp_demo.gvc │ │ └── xcp_demo_autodetect.a2l │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs └── tokio_demo │ ├── CANape │ ├── CANape.ini │ ├── CanapeCmd.ini │ └── tokio_demo.cna │ ├── Cargo.toml │ ├── README.md │ ├── src │ └── main.rs │ └── tokio_demo_autodetect.a2l ├── rustfmt.toml ├── src ├── lib.rs ├── main.rs ├── metrics │ ├── counter.rs │ ├── histogram.rs │ └── mod.rs ├── registry │ ├── a2l │ │ ├── a2l_reader.rs │ │ ├── a2l_writer.md │ │ ├── a2l_writer.rs │ │ ├── aml_ifdata.rs │ │ └── mod.rs │ ├── data_model.md │ ├── mc_address.rs │ ├── mc_calseg.rs │ ├── mc_event.rs │ ├── mc_instance.rs │ ├── mc_registry.rs │ ├── mc_support.rs │ ├── mc_text.rs │ ├── mc_type.rs │ ├── mc_typedef.rs │ └── mod.rs └── xcp │ ├── cal.rs │ ├── cal │ └── cal_seg.rs │ ├── daq.rs │ ├── daq │ └── daq_event.rs │ ├── mod.rs │ └── xcplib.rs ├── tests ├── test_multi_thread.rs ├── test_single_thread.rs └── xcp_test_executor.rs ├── xcp_client ├── .gitignore ├── Cargo.toml ├── README.md ├── XCP_104.aml └── src │ ├── lib.rs │ ├── main.rs │ ├── xcp_client.rs │ └── xcp_test_executor.rs ├── xcp_idl_generator ├── Cargo.toml ├── src │ ├── domain.rs │ ├── gen │ │ ├── collection │ │ │ ├── cdr.rs │ │ │ └── mod.rs │ │ └── mod.rs │ ├── lib.rs │ ├── prelude.rs │ └── types.rs └── xcp_idl_generator_derive │ ├── Cargo.toml │ └── src │ └── lib.rs ├── xcp_lite.a2l ├── xcp_type_description ├── Cargo.toml ├── src │ ├── lib.rs │ └── prelude.rs └── xcp_type_description_derive │ ├── Cargo.toml │ └── src │ ├── lib.rs │ └── utils.rs └── xcplib ├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── Cargo.toml ├── README.md ├── build.sh ├── examples ├── c_demo │ ├── CANape │ │ ├── CANape.ini │ │ ├── CanapeCmd.ini │ │ ├── xcp_demo.cna │ │ └── xcp_demo_autodetect.a2l │ └── src │ │ └── main.c └── hello_xcp │ ├── CANape │ ├── CANape.ini │ ├── CanapeCmd.ini │ ├── xcp_demo.cna │ └── xcp_demo_autodetect.a2l │ └── src │ └── main.c ├── src ├── a2l.c ├── a2l.h ├── dbg_print.h ├── main_cfg.h ├── platform.c ├── platform.h ├── xcp.h ├── xcpAppl.c ├── xcpAppl.h ├── xcpEthServer.c ├── xcpEthServer.h ├── xcpEthTl.c ├── xcpEthTl.h ├── xcpLite.c ├── xcpLite.h ├── xcpQueue.h ├── xcpQueue32.c ├── xcpQueue64.c ├── xcp_cfg.h └── xcptl_cfg.h └── wrapper.h /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | # [target.x86_64-pc-windows-gnu] 2 | # linker = "x86_64-w64-mingw64-gcc" 3 | 4 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | IndentWidth: 4 4 | TabWidth: 4 5 | UseTab: Never 6 | ColumnLimit: 180 7 | 8 | # NOTE: Cpp is used for both C and C++ 9 | Language: Cpp 10 | SortIncludes: false 11 | ... 12 | 13 | -------------------------------------------------------------------------------- /.github/workflows/default.yml: -------------------------------------------------------------------------------- 1 | name: XCP_lite Default 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: ["**"] 7 | tags: ["**"] 8 | 9 | concurrency: 10 | group: ${{ (github.ref == 'refs/heads/main') && 'main' || format('{0}-{1}', github.workflow, github.ref) }} # concurrency does not include main branch 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | 15 | build: 16 | name: Build 17 | runs-on: ${{ matrix.os }} 18 | 19 | strategy: 20 | matrix: 21 | os: [production] 22 | fail-fast: false 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: actions-rust-lang/setup-rust-toolchain@v1 26 | with: 27 | components: rustfmt 28 | - name: Build binary 29 | run: | 30 | cargo build --verbose 31 | test: 32 | name: Test 33 | runs-on: ${{ matrix.os }} 34 | needs: [build] 35 | strategy: 36 | matrix: 37 | os: [production] 38 | fail-fast: false 39 | steps: 40 | - uses: actions/checkout@v4 41 | - uses: actions-rust-lang/setup-rust-toolchain@v1 42 | - name: Run tests 43 | run: cargo test --features=a2l_reader --features=serde -- --test-threads=1 --nocapture 44 | 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | /Cargo.lock 4 | 5 | # Misc 6 | .DS_Store 7 | *.tmp 8 | *.bin 9 | 10 | # Unnecessary CANape artefacts 11 | *.hex 12 | *.HEX 13 | *.mf4 14 | *.mdf 15 | *.TMP 16 | *.cnaxml 17 | RecycleBin.ini 18 | CANapeTmpMea.ini 19 | ~CANapeCurrentCfg.tmp 20 | ~CANapeCurrentCfg.gvc 21 | 22 | # Autogenerated by rust-bindgen 23 | # src/xcp/xcplib.rs 24 | 25 | # Ignore devcontainer 26 | .devcontainer 27 | 28 | 29 | # Artefacts generated by examples and tests 30 | test*.json 31 | test*.a2l 32 | /*.a2l 33 | /*.json 34 | mandelbrot.png 35 | 36 | 37 | /examples/heap_demo/* 38 | -------------------------------------------------------------------------------- /CANape/XCPlite.cna: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectorgrp/xcp-lite/f56fce9a54524a4dc8adf279fbdc6c0165b615f2/CANape/XCPlite.cna -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xcp_lite" 3 | version = "0.3.0" 4 | edition = "2024" 5 | resolver = "2" 6 | rust-version = "1.85.0" 7 | authors = ["RainerZ"] 8 | description = "Measurement and Calibration for Rust with XCP, based on XCPlite" 9 | readme = "README.md" 10 | keywords = ["XCP","Rust","Vector","ASAM","CANape","A2L"] 11 | license = "MIT OR Apache-2.0" 12 | homepage = "https://vector.com" 13 | repository = "https://github.com/vectorgrp/xcp-lite" 14 | categories = ["MC"] 15 | 16 | [workspace] 17 | members = [ 18 | "xcp_client", 19 | "examples/hello_xcp", 20 | "examples/calibration_demo", 21 | "examples/struct_measurement_demo", 22 | "examples/single_thread_demo", 23 | "examples/multi_thread_demo", 24 | "examples/point_cloud_demo", 25 | "examples/rayon_demo", 26 | "examples/tokio_demo", 27 | 28 | ] 29 | 30 | [workspace.package] 31 | version = "0.3.0" 32 | 33 | 34 | #-------------------------------------------------------------------------------------- 35 | 36 | [[example]] 37 | name = "xcp_client" 38 | path = "xcp_client/src/main.rs" 39 | 40 | [[example]] 41 | name = "hello_xcp" 42 | path = "examples/hello_xcp/src/main.rs" 43 | 44 | [[example]] 45 | name = "calibration_demo" 46 | path = "examples/calibration_demo/src/main.rs" 47 | 48 | [[example]] 49 | name = "struct_measurement_demo" 50 | path = "examples/struct_measurement_demo/src/main.rs" 51 | 52 | [[example]] 53 | name = "single_thread_demo" 54 | path = "examples/single_thread_demo/src/main.rs" 55 | 56 | [[example]] 57 | name = "multi_thread_demo" 58 | path = "examples/multi_thread_demo/src/main.rs" 59 | 60 | [[example]] 61 | name = "rayon_demo" 62 | path = "examples/rayon_demo/src/main.rs" 63 | 64 | [[example]] 65 | name = "tokio_demo" 66 | path = "examples/tokio_demo/src/main.rs" 67 | 68 | [[example]] 69 | name = "point_cloud_demo" 70 | path = "examples/point_cloud_demo/src/main.rs" 71 | 72 | 73 | 74 | 75 | #-------------------------------------------------------------------------------------- 76 | [features] 77 | 78 | 79 | #default = ["a2l_reader"] 80 | 81 | # Feature a2l_reader using a2lfile 82 | # Automatic check of the generated A2L file 83 | a2l_reader = ["dep:a2lfile"] 84 | 85 | 86 | #-------------------------------------------------------------------------------------- 87 | 88 | [dependencies] 89 | 90 | # Error handling 91 | thiserror = "1.0.64" 92 | #thiserror = "2.0" 93 | 94 | # Command line parser 95 | clap = { version = "4.5.9", features = ["derive"] } 96 | 97 | # A macro to generate structures which behave like bitflags 98 | bitflags = "2.6.0" 99 | 100 | # Logging 101 | log = "0.4.21" 102 | env_logger = "0.11.3" 103 | 104 | # Alloc stats 105 | stats_alloc = "0.1.10" 106 | 107 | # Collects build-information of your Rust crate, used to generate EPK 108 | build-info = "0.0.40" 109 | 110 | # A macro for declaring lazily evaluated statics 111 | lazy_static = "1.4" 112 | 113 | # Single assignment cells 114 | once_cell = "1.19.0" 115 | static_cell = "2.1.0" 116 | 117 | # More compact and efficient implementations of the standard synchronization primitives 118 | parking_lot = "0.12.3" 119 | 120 | # Regular expression matching for registry object search 121 | regex = "1.11.1" 122 | 123 | # A generic serialization/deserialization framework 124 | # Used to handle json parameter files (optional) 125 | serde = "1.0" 126 | serde_json = "1.0" 127 | 128 | # proc-macro A2L serializer for structs 129 | xcp_type_description = { path = "./xcp_type_description/"} 130 | xcp_type_description_derive = { path = "./xcp_type_description/xcp_type_description_derive/" } 131 | 132 | # proc-macro CDR IDL generator for structs 133 | xcp_idl_generator = { path = "./xcp_idl_generator/"} 134 | xcp_idl_generator_derive = { path = "./xcp_idl_generator/xcp_idl_generator_derive/"} 135 | 136 | # A2L checker (optional) 137 | a2lfile = { version="3.0.0", optional = true} 138 | 139 | #-------------------------------------------------------------------------------------- 140 | 141 | [dev-dependencies] 142 | 143 | anyhow = "1.0" 144 | rand = "0.9" 145 | 146 | # Alloc stats 147 | stats_alloc = "0.1.10" 148 | 149 | 150 | # XCP test client 151 | bytes = "1.6.0" 152 | byteorder = "1.5.0" 153 | regex = "1.11.1" 154 | tokio = { version = "1.37.0", features = ["full"] } 155 | xcp_client = { path = "xcp_client" } 156 | 157 | # A2L checker (optional) 158 | a2lfile = { version="3.0.0", optional = false} 159 | 160 | # dependencies for point_cloud demo example 161 | cdr = "0.2.4" 162 | 163 | # dependencies for rayon demo example 164 | rayon = "1.10.0" 165 | num = "0.4.3" 166 | image = "0.25.2" 167 | num_cpus = "1.16.0" 168 | 169 | 170 | 171 | [build-dependencies] 172 | cc = "1.0" 173 | build-info-build = "0.0.40" 174 | bindgen = "0.71.1" 175 | 176 | 177 | [profile.dev.package."*"] 178 | debug = false 179 | opt-level = 3 180 | 181 | [profile.dev] 182 | # panic = 'abort' 183 | # lto = true 184 | debug = true 185 | opt-level = 2 186 | 187 | [profile.release] 188 | panic = 'abort' 189 | debug = true 190 | lto = true 191 | opt-level = 3 192 | -------------------------------------------------------------------------------- /Clippy.toml: -------------------------------------------------------------------------------- 1 | avoid-breaking-exported-api = false 2 | disallowed-names = ["foo", "bar"] 3 | 4 | 5 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | build_info_build::build_script(); 3 | 4 | #[allow(unused_assignments, unused_mut)] // due to feature flag 5 | let mut _is_posix = true; 6 | #[cfg(target_os = "windows")] 7 | { 8 | _is_posix = false; 9 | } 10 | 11 | #[allow(unused_assignments, unused_mut)] // due to feature flag 12 | let mut is_release = true; 13 | #[cfg(debug_assertions)] 14 | { 15 | is_release = false; 16 | } 17 | 18 | // Generate C code bindings for xcplib 19 | let bindings = bindgen::Builder::default() 20 | .header("xcplib/wrapper.h") 21 | // 22 | //.clang_args(&["-target", "x86_64-pc-windows-msvc"]) 23 | .clang_arg("-Ixcplib/src") 24 | .clang_arg("-Ixcplib") 25 | .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) 26 | // 27 | .blocklist_type("T_CLOCK_INFO") 28 | .allowlist_type("tXcpDaqLists") 29 | // Protocol layer 30 | .allowlist_function("XcpInit") 31 | .allowlist_function("XcpDisconnect") 32 | // Transport layer 33 | .allowlist_function("XcpEthTlGetInfo") 34 | // Server 35 | .allowlist_function("XcpEthServerInit") 36 | .allowlist_function("XcpEthServerShutdown") 37 | .allowlist_function("XcpEthServerStatus") 38 | // DAQ 39 | .allowlist_function("XcpEvent") 40 | .allowlist_function("XcpEventExt") 41 | //.allowlist_function("XcpTriggerDaqEventAt") 42 | //.allowlist_function("XcpEventAt") 43 | //.allowlist_function("XcpEventExtAt") 44 | // Misc 45 | .allowlist_function("XcpSetLogLevel") 46 | .allowlist_function("XcpPrint") 47 | .allowlist_function("XcpSetEpk") 48 | .allowlist_function("XcpSendTerminateSessionEvent") 49 | // 50 | //.allowlist_function("ApplXcpGetAddr") 51 | .allowlist_function("ApplXcpSetA2lName") 52 | .allowlist_function("ApplXcpRegisterCallbacks") 53 | .allowlist_function("ApplXcpGetClock64") 54 | // 55 | .generate() 56 | .expect("Unable to generate bindings"); 57 | bindings.write_to_file("src/xcp/xcplib.rs").expect("Couldn't write bindings!"); 58 | 59 | // Build xcplib 60 | let mut builder = cc::Build::new(); 61 | let builder = builder 62 | .include("xcplib/src/") 63 | .file("xcplib/src/xcpAppl.c") 64 | .file("xcplib/src/platform.c") 65 | .file("xcplib/src/xcpLite.c") 66 | .file("xcplib/src/xcpQueue64.c") 67 | .file("xcplib/src/xcpEthTl.c") 68 | .file("xcplib/src/xcpEthServer.c") 69 | .flag("-std=c11"); 70 | 71 | if is_release { 72 | builder.flag("-O2"); 73 | } else { 74 | builder.flag("-O0").flag("-g"); 75 | } 76 | 77 | builder.compile("xcplib"); 78 | 79 | // Tell cargo to invalidate the built crate whenever any of these files changed. 80 | println!("cargo:rerun-if-changed=xcplib/c_test.c"); 81 | println!("cargo:rerun-if-changed=xcplib/wrapper.h"); 82 | println!("cargo:rerun-if-changed=xcplib/src/main_cfg.h"); 83 | println!("cargo:rerun-if-changed=xcplib/src/xcptl_cfg.h"); 84 | println!("cargo:rerun-if-changed=xcplib/src/xcp_cfg.h"); 85 | println!("cargo:rerun-if-changed=xcplib/src/a2l.h"); 86 | println!("cargo:rerun-if-changed=xcplib/src/a2l.c"); 87 | println!("cargo:rerun-if-changed=xcplib/src/xcpAppl.h"); 88 | println!("cargo:rerun-if-changed=xcplib/src/xcpAppl.c"); 89 | println!("cargo:rerun-if-changed=xcplib/src/platform.h"); 90 | println!("cargo:rerun-if-changed=xcplib/src/platform.c"); 91 | println!("cargo:rerun-if-changed=xcplib/src/xcpQueue.h"); 92 | println!("cargo:rerun-if-changed=xcplib/src/xcpQueue64.c"); 93 | println!("cargo:rerun-if-changed=xcplib/src/xcpEthTl.h"); 94 | println!("cargo:rerun-if-changed=xcplib/src/xcpEthTl.c"); 95 | println!("cargo:rerun-if-changed=xcplib/src/xcpEthServer.h"); 96 | println!("cargo:rerun-if-changed=xcplib/src/xcpEthServer.c"); 97 | println!("cargo:rerun-if-changed=xcplib/src/xcp.h"); 98 | println!("cargo:rerun-if-changed=xcplib/src/xcpLite.h"); 99 | println!("cargo:rerun-if-changed=xcplib/src/xcpLite.c"); 100 | } 101 | -------------------------------------------------------------------------------- /examples/calibration_demo/CANape/.gitignore: -------------------------------------------------------------------------------- 1 | *.HEX 2 | *.MDF 3 | *.CNAXML 4 | 5 | -------------------------------------------------------------------------------- /examples/calibration_demo/CANape/FunctionFallback/CycleTime.fup: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectorgrp/xcp-lite/f56fce9a54524a4dc8adf279fbdc6c0165b615f2/examples/calibration_demo/CANape/FunctionFallback/CycleTime.fup -------------------------------------------------------------------------------- /examples/calibration_demo/CANape/xcp_demo.cna: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectorgrp/xcp-lite/f56fce9a54524a4dc8adf279fbdc6c0165b615f2/examples/calibration_demo/CANape/xcp_demo.cna -------------------------------------------------------------------------------- /examples/calibration_demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calibration_demo" 3 | version = "0.3.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | log = "0.4.21" 8 | env_logger = "0.11.3" 9 | anyhow = "1.0" 10 | lazy_static = "1.4" 11 | serde = "1.0" 12 | serde_json = "1.0" 13 | 14 | xcp_lite = { path = "../../", features = [] } 15 | 16 | -------------------------------------------------------------------------------- /examples/calibration_demo/README.md: -------------------------------------------------------------------------------- 1 | # xcp-lite - calibration_demo 2 | 3 | Demonstrate various adjustable basic types, nested structs and multi dimensional User types such as a map based lookup table with shared axis and associated lookup functions with interpolation. 4 | This generates A2L objects like CURVE and MAP with AXIS_PTS. 5 | 6 | Note: 7 | CANape and a2lfile do currently not support the THIS keyword. As a workaround, we have to specifiy fully qualified names referencing to individual instances 8 | 9 | Run: 10 | 11 | ``` 12 | cargo run --example calibration_demo 13 | ``` 14 | 15 | Start the CANape project in the CANape folder or find some screenshots below 16 | 17 | ## CANape 18 | 19 | ![CANape](CANape1.png) 20 | 21 | ![CANape](CANape2.png) 22 | 23 | ## A2L file 24 | 25 | A measurement struct 26 | 27 | ```rust 28 | // Struct measurement variable on stack 29 | #[derive(Clone, Copy, XcpTypeDescription)] 30 | struct Lookup { 31 | input: f32, 32 | output_linear: f32, 33 | output_spline: f32, 34 | } 35 | let mut lookup = /* Box::default() */Lookup { 36 | input: 0.0, 37 | output_linear: 0.0, 38 | output_spline: 0.0, 39 | }; 40 | 41 | daq_register_struct!(lookup, event); 42 | ``` 43 | 44 | ``` 45 | /* struct Lookup */ 46 | /begin TYPEDEF_MEASUREMENT Lookup.input "" FLOAT32_IEEE NO_COMPU_METHOD 0 0 -1E32 1E32 /end TYPEDEF_MEASUREMENT 47 | /begin TYPEDEF_MEASUREMENT Lookup.output_linear "" FLOAT32_IEEE NO_COMPU_METHOD 0 0 -1E32 1E32 /end TYPEDEF_MEASUREMENT 48 | /begin TYPEDEF_MEASUREMENT Lookup.output_spline "" FLOAT32_IEEE NO_COMPU_METHOD 0 0 -1E32 1E32 /end TYPEDEF_MEASUREMENT 49 | /begin TYPEDEF_STRUCTURE Lookup "" 12 50 | /begin STRUCTURE_COMPONENT input Lookup.input 0 /end STRUCTURE_COMPONENT 51 | /begin STRUCTURE_COMPONENT output_linear Lookup.output_linear 4 /end STRUCTURE_COMPONENT 52 | /begin STRUCTURE_COMPONENT output_spline Lookup.output_spline 8 /end STRUCTURE_COMPONENT 53 | /end TYPEDEF_STRUCTURE 54 | 55 | ``` 56 | 57 | A user lookup table type as calibration map struct 58 | 59 | ```rust 60 | 61 | #[derive(Clone, Copy, XcpTypeDescription)] 62 | struct LookUpTable { 63 | #[axis(comment = "LookUpTable axis", min = "0", max = "10000")] 64 | input: [f32; 16], 65 | 66 | #[characteristic(comment = "LookUpTable values", axis = "CalPage.LookUpTable.input", min = "0", max = "10000")] 67 | output: [f32; 16], 68 | } 69 | 70 | // Default values for LookUpTable 71 | impl Default for LookUpTable { 72 | fn default() -> Self { LookUpTable::DEFAULT } 73 | } 74 | 75 | // 'Class' LookUpTable 76 | impl LookUpTable { 77 | const DEFAULT: LookUpTable = LookUpTable { 78 | input: [0.0, 1.0, 2.0, 5.0, 10.0, 220.0, 390.0, 730.0, 1000.0, 1880.0, 2770.0, 4110.0, 5000.0, 7010.0, 8640.0, 10000.0, ], 79 | output: [0.0, 1.0, 2.0, 5.0, 10.0, 20.0, 530.0, 100.0, 610.0, 210.0, 980.0, 330.0, 730.0, 180.0, 350.0, 0.0], 80 | }; 81 | 82 | fn new() -> Self { LookUpTable::DEFAULT } 83 | fn lookup(&self, input: f32) -> f32 { ... } 84 | } 85 | 86 | 87 | ``` 88 | 89 | ``` 90 | /begin AXIS_PTS CalPage.LookUpTable.input "LookUpTable axis" 0x80010008 NO_INPUT_QUANTITY A_F32 0 NO_COMPU_METHOD 16 0 10000 /end AXIS_PTS 91 | /begin CHARACTERISTIC CalPage.LookUpTable.output "LookUpTable values" CURVE 0x80010048 F32 0 NO_COMPU_METHOD 0 10000 92 | /begin AXIS_DESCR COM_AXIS NO_INPUT_QUANTITY NO_COMPU_METHOD 16 0.0 0.0 AXIS_PTS_REF CalPage.LookUpTable.input /end AXIS_DESCR 93 | /end CHARACTERISTIC 94 | 95 | ``` 96 | -------------------------------------------------------------------------------- /examples/heap_demo/CANape/.gitignore: -------------------------------------------------------------------------------- 1 | *.HEX 2 | -------------------------------------------------------------------------------- /examples/hello_xcp/CANape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectorgrp/xcp-lite/f56fce9a54524a4dc8adf279fbdc6c0165b615f2/examples/hello_xcp/CANape.png -------------------------------------------------------------------------------- /examples/hello_xcp/CANape/.gitignore: -------------------------------------------------------------------------------- 1 | *.HEX 2 | -------------------------------------------------------------------------------- /examples/hello_xcp/CANape/xcp_demo.cna: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectorgrp/xcp-lite/f56fce9a54524a4dc8adf279fbdc6c0165b615f2/examples/hello_xcp/CANape/xcp_demo.cna -------------------------------------------------------------------------------- /examples/hello_xcp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello_xcp" 3 | version = "0.3.0" 4 | edition = "2024" 5 | 6 | 7 | 8 | [dependencies] 9 | log = "0.4.21" 10 | env_logger = "0.11.3" 11 | anyhow = "1.0" 12 | lazy_static = "1.4" 13 | serde = "1.0" 14 | serde_json = "1.0" 15 | 16 | xcp_lite = { path = "../../", features = [] } 17 | 18 | 19 | [features] 20 | default = [] 21 | -------------------------------------------------------------------------------- /examples/hello_xcp/README.md: -------------------------------------------------------------------------------- 1 | # xcp_lite - hello_xcp 2 | 3 | Basic demo 4 | 5 | Run: 6 | ``` 7 | cargo run --example hello_xcp 8 | ``` 9 | 10 | Run the test XCP client in another terminal with the following command: 11 | ``` 12 | cargo run --example xcp_client 13 | ``` 14 | 15 | 16 | 17 | 18 | ## CANape 19 | 20 | 21 | ![CANape](CANape.png) 22 | 23 | 24 | 25 | ## A2L file 26 | 27 | The demo creates the A2L file below: 28 | 29 | ``` 30 | 31 | ASAP2_VERSION 1 71 32 | /begin PROJECT hello_xcp "" 33 | 34 | /begin HEADER "" VERSION "1.0" /end HEADER 35 | /begin MODULE hello_xcp "" 36 | 37 | /include "XCP_104.aml" 38 | 39 | ... 40 | 41 | /begin MOD_PAR "" 42 | 43 | EPK "EPK_" 44 | ADDR_EPK 0x80000000 45 | 46 | /begin MEMORY_SEGMENT 47 | epk "" DATA FLASH INTERN 0x80000000 4 -1 -1 -1 -1 -1 48 | /end MEMORY_SEGMENT 49 | 50 | /begin MEMORY_SEGMENT 51 | calseg "" DATA FLASH INTERN 0x80010000 8 -1 -1 -1 -1 -1 52 | /begin IF_DATA XCP 53 | /begin SEGMENT /* index: */ 1 /* pages: */ 2 /* ext: */ 0 0 0 54 | /begin CHECKSUM XCP_ADD_44 MAX_BLOCK_SIZE 0xFFFF EXTERNAL_FUNCTION "" /end CHECKSUM 55 | /begin PAGE 0x0 ECU_ACCESS_DONT_CARE XCP_READ_ACCESS_DONT_CARE XCP_WRITE_ACCESS_DONT_CARE /end PAGE 56 | /begin PAGE 0x1 ECU_ACCESS_DONT_CARE XCP_READ_ACCESS_DONT_CARE XCP_WRITE_ACCESS_NOT_ALLOWED /end PAGE 57 | /end SEGMENT 58 | /end IF_DATA 59 | /end MEMORY_SEGMENT 60 | 61 | /end MOD_PAR 62 | 63 | /begin IF_DATA XCP 64 | /begin PROTOCOL_LAYER 65 | 0x104 1000 2000 0 0 0 0 0 252 1468 BYTE_ORDER_MSB_LAST ADDRESS_GRANULARITY_BYTE 66 | OPTIONAL_CMD GET_COMM_MODE_INFO 67 | ... 68 | /end PROTOCOL_LAYER 69 | /begin DAQ 70 | DYNAMIC 0 1 0 OPTIMISATION_TYPE_DEFAULT ADDRESS_EXTENSION_FREE IDENTIFICATION_FIELD_TYPE_RELATIVE_BYTE GRANULARITY_ODT_ENTRY_SIZE_DAQ_BYTE 0xF8 OVERLOAD_INDICATION_PID 71 | /begin TIMESTAMP_SUPPORTED 72 | 0x1 SIZE_DWORD UNIT_1US TIMESTAMP_FIXED 73 | /end TIMESTAMP_SUPPORTED 74 | 75 | /begin EVENT "mainloop" "mainloop" 0 DAQ 0xFF 0 0 0 CONSISTENCY DAQ /end EVENT 76 | 77 | /end DAQ 78 | 79 | /begin XCP_ON_UDP_IP 0x104 5555 ADDRESS "127.0.0.1" /end XCP_ON_UDP_IP 80 | 81 | /end IF_DATA 82 | 83 | /begin GROUP Cal "" /begin REF_CHARACTERISTIC /end REF_CHARACTERISTIC /end GROUP 84 | 85 | /begin CHARACTERISTIC CalPage.delay "Task delay time in us" VALUE 0x80010000 U32 0 NO_COMPU_METHOD 0 1000000 PHYS_UNIT "us" /end CHARACTERISTIC 86 | /begin CHARACTERISTIC CalPage.max "Max counter value" VALUE 0x80010004 U16 0 NO_COMPU_METHOD 0 1023 /end CHARACTERISTIC 87 | /begin CHARACTERISTIC CalPage.min "Min counter value" VALUE 0x80010006 U16 0 NO_COMPU_METHOD 0 1023 /end CHARACTERISTIC 88 | /begin GROUP calseg "" /begin REF_CHARACTERISTIC CalPage.delay CalPage.max CalPage.min /end REF_CHARACTERISTIC /end GROUP 89 | 90 | 91 | /begin MEASUREMENT counter "" UWORD NO_COMPU_METHOD 0 0 0 65535 PHYS_UNIT "" ECU_ADDRESS 0x22 ECU_ADDRESS_EXTENSION 2 92 | /begin IF_DATA XCP /begin DAQ_EVENT FIXED_EVENT_LIST EVENT 0 /end DAQ_EVENT /end IF_DATA /end MEASUREMENT 93 | 94 | /end MODULE 95 | /end PROJECT 96 | 97 | ``` 98 | 99 | 100 | -------------------------------------------------------------------------------- /examples/hello_xcp/src/main.rs: -------------------------------------------------------------------------------- 1 | // hello_xcp 2 | // xcp-lite basic demo 3 | // 4 | // Demonstrates the usage of xcp-lite for Rust together with a CANape project 5 | // 6 | // Run the demo 7 | // cargo run --example hello_xcp 8 | // 9 | // Run the test XCP client in another terminal or start CANape with the project in folder examples/hello_xcp/CANape 10 | // cargo run --example xcp_client -- -m "counter" 11 | 12 | #[allow(unused_imports)] 13 | use log::{debug, error, info, trace, warn}; 14 | 15 | use xcp_lite::registry::*; 16 | use xcp_lite::*; 17 | 18 | //----------------------------------------------------------------------------- 19 | // Parameters 20 | 21 | const APP_NAME: &str = "hello_xcp"; 22 | const XCP_QUEUE_SIZE: u32 = 1024 * 64; // 64kB 23 | const MAINLOOP_CYCLE_TIME: u32 = 10000; // 10ms 24 | 25 | //----------------------------------------------------------------------------- 26 | // Command line arguments 27 | 28 | const DEFAULT_LOG_LEVEL: u8 = 3; // Info 29 | const DEFAULT_BIND_ADDR: std::net::Ipv4Addr = std::net::Ipv4Addr::new(0, 0, 0, 0); // ANY 30 | const DEFAULT_PORT: u16 = 5555; 31 | const DEFAULT_TCP: bool = false; // UDP 32 | 33 | use clap::Parser; 34 | 35 | #[derive(Parser, Debug)] 36 | #[command(version, about, long_about = None)] 37 | struct Args { 38 | /// Log level (Off=0, Error=1, Warn=2, Info=3, Debug=4, Trace=5) 39 | #[arg(short, long, default_value_t = DEFAULT_LOG_LEVEL)] 40 | log_level: u8, 41 | 42 | /// Bind address, default is ANY 43 | #[arg(short, long, default_value_t = DEFAULT_BIND_ADDR)] 44 | bind: std::net::Ipv4Addr, 45 | 46 | /// Use TCP as transport layer, default is UDP 47 | #[arg(short, long, default_value_t = DEFAULT_TCP)] 48 | tcp: bool, 49 | 50 | /// Port number 51 | #[arg(short, long, default_value_t = DEFAULT_PORT)] 52 | port: u16, 53 | 54 | /// Application name 55 | #[arg(short, long, default_value_t = String::from(APP_NAME))] 56 | name: String, 57 | } 58 | 59 | //----------------------------------------------------------------------------- 60 | // Demo calibration parameters 61 | 62 | // Define calibration parameters in a struct with semantic annotations to create the A2L file 63 | #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, XcpTypeDescription)] 64 | struct Params { 65 | #[characteristic(comment = "Start/stop counter")] 66 | counter_on: bool, 67 | 68 | #[characteristic(comment = "Max counter value")] 69 | #[characteristic(min = "0", max = "1023")] 70 | counter_max: u32, 71 | 72 | #[characteristic(comment = "Task delay time in s, ecu internal value as u32 in us")] 73 | #[characteristic(min = "0.00001", max = "2", unit = "s", factor = "0.000001")] 74 | delay: u32, 75 | 76 | #[characteristic(comment = "Demo array", min = "0", max = "100")] 77 | array: [u8; 4], 78 | 79 | #[characteristic(comment = "Demo matrix", min = "0", max = "100")] 80 | matrix: [[u8; 8]; 4], 81 | } 82 | 83 | // Default values for the calibration parameters 84 | const PARAMS: Params = Params { 85 | counter_on: true, 86 | counter_max: 100, 87 | delay: MAINLOOP_CYCLE_TIME, 88 | array: [0, 2, 5, 10], 89 | matrix: [[0, 0, 0, 0, 0, 0, 1, 2], [0, 0, 0, 0, 0, 0, 2, 3], [0, 0, 0, 0, 1, 1, 2, 3], [0, 0, 0, 1, 1, 2, 3, 4]], 90 | }; 91 | 92 | //----------------------------------------------------------------------------- 93 | 94 | fn main() -> anyhow::Result<()> { 95 | println!("XCP for Rust demo - hello_xcp - CANape project in ./examples/hello_xcp/CANape"); 96 | 97 | // Args 98 | let args = Args::parse(); 99 | let log_level = match args.log_level { 100 | 2 => log::LevelFilter::Warn, 101 | 3 => log::LevelFilter::Info, 102 | 4 => log::LevelFilter::Debug, 103 | 5 => log::LevelFilter::Trace, 104 | _ => log::LevelFilter::Error, 105 | }; 106 | 107 | // Logging 108 | env_logger::Builder::new() 109 | .target(env_logger::Target::Stdout) 110 | .filter_level(log_level) 111 | .format_timestamp(None) 112 | .format_module_path(false) 113 | .format_target(false) 114 | .init(); 115 | 116 | // XCP: Initialize the XCP server 117 | let app_name = args.name.as_str(); 118 | let app_revision = build_info::format!("EPK_{}", $.timestamp); 119 | let _xcp = Xcp::get() 120 | .set_app_name(app_name) 121 | .set_app_revision(app_revision) 122 | .set_log_level(args.log_level) 123 | .start_server( 124 | if args.tcp { XcpTransportLayer::Tcp } else { XcpTransportLayer::Udp }, 125 | args.bind.octets(), 126 | args.port, 127 | XCP_QUEUE_SIZE, 128 | )?; 129 | 130 | // XCP: Create a calibration segment wrapper with default values and register the calibration parameters 131 | let params = CalSeg::new("my_params", &PARAMS); 132 | params.register_fields(); 133 | 134 | // Demo measurement variable on stack 135 | let mut counter: u32 = 0; 136 | 137 | // XCP: Register a measurement event and bind measurement variables 138 | let event = daq_create_event!("my_event", 16); 139 | daq_register!(counter, event); 140 | 141 | loop { 142 | // XCP: Synchronize calibration parameters in cal_page and lock read access for consistency 143 | let params = params.read_lock(); 144 | 145 | if params.counter_on { 146 | counter += 1; 147 | if counter > params.counter_max { 148 | counter = 0; 149 | } 150 | } 151 | 152 | // XCP: Trigger timestamped measurement data acquisition 153 | event.trigger(); 154 | 155 | std::thread::sleep(std::time::Duration::from_micros(params.delay as u64)); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /examples/multi_thread_demo/.gitignore: -------------------------------------------------------------------------------- 1 | /Cargo.lock 2 | /target/ 3 | 4 | *.a2h 5 | -------------------------------------------------------------------------------- /examples/multi_thread_demo/CANape/multi_thread_demo.cna: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectorgrp/xcp-lite/f56fce9a54524a4dc8adf279fbdc6c0165b615f2/examples/multi_thread_demo/CANape/multi_thread_demo.cna -------------------------------------------------------------------------------- /examples/multi_thread_demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "multi_thread_demo" 3 | version = "0.3.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | anyhow = "1.0" 8 | log = "0.4.21" 9 | env_logger = "0.11.3" 10 | lazy_static = "1.4" 11 | serde = "1.0" 12 | serde_json = "1.0" 13 | 14 | xcp_lite = { path = "../../", features = [] } 15 | -------------------------------------------------------------------------------- /examples/multi_thread_demo/README.md: -------------------------------------------------------------------------------- 1 | # xcp-lite - Multi thread demo 2 | 3 | Demo how to use the xcp crate with in a multi threaded application 4 | 5 | 6 | 7 | ![CANape](CANape.png) 8 | 9 | -------------------------------------------------------------------------------- /examples/multi_thread_demo/src/main.rs: -------------------------------------------------------------------------------- 1 | // xcp-lite - multi_thread_demo 2 | 3 | #![allow(unused_imports)] 4 | 5 | use anyhow::Result; 6 | use log::{debug, error, info, trace, warn}; 7 | use std::net::Ipv4Addr; 8 | use std::{ 9 | f64::consts::PI, 10 | fmt::Debug, 11 | thread, 12 | time::{Duration, Instant}, 13 | }; 14 | 15 | use xcp_lite::registry::*; 16 | use xcp_lite::*; 17 | 18 | // Static application start time 19 | lazy_static::lazy_static! { 20 | static ref START_TIME: Instant = Instant::now(); 21 | } 22 | 23 | //----------------------------------------------------------------------------- 24 | // Parameters 25 | 26 | const APP_NAME: &str = "multi_thread_demo"; 27 | 28 | const XCP_QUEUE_SIZE: u32 = 1024 * 64; // 64kB 29 | const MAINLOOP_CYCLE_TIME_US: u32 = 10000; // 10ms 30 | 31 | //----------------------------------------------------------------------------- 32 | // Command line arguments 33 | 34 | const DEFAULT_LOG_LEVEL: u8 = 3; // Info 35 | const DEFAULT_BIND_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0); 36 | const DEFAULT_PORT: u16 = 5555; 37 | const DEFAULT_TCP: bool = false; // UDP 38 | 39 | use clap::Parser; 40 | 41 | #[derive(Parser, Debug)] 42 | #[command(version, about, long_about = None)] 43 | struct Args { 44 | /// Log level (Off=0, Error=1, Warn=2, Info=3, Debug=4, Trace=5) 45 | #[arg(short, long, default_value_t = DEFAULT_LOG_LEVEL)] 46 | log_level: u8, 47 | 48 | /// Bind address, default is ANY 49 | #[arg(short, long, default_value_t = DEFAULT_BIND_ADDR)] 50 | bind: Ipv4Addr, 51 | 52 | /// Use TCP as transport layer, default is UDP 53 | #[arg(short, long, default_value_t = DEFAULT_TCP)] 54 | tcp: bool, 55 | 56 | /// Port number 57 | #[arg(short, long, default_value_t = DEFAULT_PORT)] 58 | port: u16, 59 | 60 | /// Application name 61 | #[arg(short, long, default_value_t = String::from(APP_NAME))] 62 | name: String, 63 | } 64 | 65 | //----------------------------------------------------------------------------- 66 | // Demo calibration parameters 67 | 68 | #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, XcpTypeDescription)] 69 | struct Params { 70 | #[characteristic(comment = "Task delay time in s, ecu internal value as u32 in us")] 71 | #[characteristic(min = "0.00001", max = "2", unit = "s", factor = "0.000001")] 72 | delay: u32, 73 | 74 | #[characteristic(comment = "Amplitude of the sine signal")] 75 | #[characteristic(unit = "Volt")] 76 | #[characteristic(min = "0")] 77 | #[characteristic(max = "500")] 78 | ampl: f64, 79 | 80 | #[characteristic(comment = "Period of the sine signal")] 81 | #[characteristic(unit = "s")] 82 | #[characteristic(min = "0.001")] 83 | #[characteristic(max = "10")] 84 | period: f64, 85 | 86 | #[characteristic(comment = "Counter maximum value")] 87 | #[characteristic(min = "0", max = "255", step = "10")] 88 | counter_max: u32, 89 | } 90 | 91 | const CALPAGE1: Params = Params { 92 | delay: MAINLOOP_CYCLE_TIME_US, 93 | ampl: 100.0, 94 | period: 5.0, 95 | counter_max: 100, 96 | }; 97 | 98 | // Create a static cell for the calibration segment, which is shared between the threads 99 | // The alternative would be to move a clone of a CalSeg into each thread 100 | static CALSEG1: std::sync::OnceLock> = std::sync::OnceLock::new(); 101 | 102 | //----------------------------------------------------------------------------- 103 | // Demo task 104 | 105 | // A task executed in multiple threads sharing a calibration parameter segment 106 | fn task(id: u32) { 107 | // Get the static calibration segment 108 | let calseg1 = CALSEG1.get().unwrap().clone_calseg(); 109 | 110 | // Create a thread local event instance 111 | // The capacity of the event capture buffer is 16 bytes 112 | let mut event = daq_create_event_tli!("task", 16); 113 | println!("Task {id} started"); 114 | 115 | // Demo signals 116 | let mut counter: u32 = 0; 117 | let mut sine: f64; 118 | 119 | loop { 120 | let calseg1 = calseg1.read_lock(); 121 | 122 | thread::sleep(Duration::from_micros(calseg1.delay as u64)); 123 | 124 | // A counter wrapping at a value specified by a calibration parameter 125 | counter += 1; 126 | if counter > calseg1.counter_max { 127 | counter = 0 128 | } 129 | 130 | // A sine signal with amplitude and period from calibration parameters and an offset from thread id 131 | let time = START_TIME.elapsed().as_micros() as f64 * 0.000001; // s 132 | sine = (id as f64) * 10.0 + calseg1.ampl * ((PI * time) / calseg1.period).sin(); 133 | 134 | // Register them once for each task instance and associate to the task instance event 135 | // Copy the value to the event capture buffer 136 | daq_capture_tli!(counter, event); 137 | daq_capture_tli!(sine, event, "sine wave signal", "Volt", 1.0, 0.0); 138 | 139 | // Trigger the measurement event 140 | // Take a event timestamp send the captured data 141 | event.trigger(); 142 | } 143 | } 144 | 145 | //----------------------------------------------------------------------------- 146 | // Demo application main 147 | 148 | fn main() -> Result<()> { 149 | println!("XCPlite Multi Thread Demo"); 150 | 151 | // Args 152 | let args = Args::parse(); 153 | let log_level = match args.log_level { 154 | 2 => log::LevelFilter::Warn, 155 | 3 => log::LevelFilter::Info, 156 | 4 => log::LevelFilter::Debug, 157 | 5 => log::LevelFilter::Trace, 158 | _ => log::LevelFilter::Error, 159 | }; 160 | 161 | // Logging 162 | env_logger::Builder::new() 163 | .target(env_logger::Target::Stdout) 164 | .filter_level(log_level) 165 | .format_timestamp(None) 166 | .format_module_path(false) 167 | .format_target(false) 168 | .init(); 169 | 170 | // XCP: Initialize the XCP server 171 | let app_name = args.name.as_str(); 172 | let app_revision = build_info::format!("{}", $.timestamp); 173 | let xcp = Xcp::get() 174 | .set_app_name(app_name) 175 | .set_app_revision(app_revision) 176 | .set_log_level(args.log_level) 177 | .start_server( 178 | if args.tcp { XcpTransportLayer::Tcp } else { XcpTransportLayer::Udp }, 179 | args.bind.octets(), 180 | args.port, 181 | XCP_QUEUE_SIZE, 182 | )?; 183 | 184 | // Create a static calibration parameter set (will be a MEMORY_SEGMENT in A2L) from a const struct CALPAGE1 185 | // The calibration parameters are shared between the threads 186 | // Calibration segments have 2 pages, a constant default "FLASH" page and a mutable "RAM" page 187 | // FLASH or RAM can be switched at runtime (XCP set_cal_page), saved to json (XCP freeze) freeze and reinitialized from FLASH (XCP copy_cal_page) 188 | let params = CALSEG1.get_or_init(|| CalCell::new("multi_thread_params", &CALPAGE1)).clone_calseg(); 189 | params.register_fields(); // Register all struct fields (with meta data from annotations) in the A2L registry 190 | 191 | // Start multiple instances of the demo task 192 | // Each instance will create its own measurement variable and event instances 193 | let mut t = Vec::new(); 194 | for i in 0..=10 { 195 | t.push(thread::spawn({ 196 | move || { 197 | task(i); 198 | } 199 | })); 200 | } 201 | 202 | // Test: Generate A2L immediately (normally this happens on XCP tool connect) 203 | // Wait some time until all threads have registered their measurement signals and events 204 | thread::sleep(Duration::from_millis(1000)); 205 | xcp.finalize_registry().unwrap(); 206 | 207 | // Wait for the threads to finish 208 | t.into_iter().for_each(|t| t.join().unwrap()); 209 | 210 | // Stop the XCP server 211 | xcp.stop_server(); 212 | 213 | Ok(()) 214 | } 215 | -------------------------------------------------------------------------------- /examples/point_cloud_demo/.gitignore: -------------------------------------------------------------------------------- 1 | /Cargo.lock 2 | /target/ 3 | *.a2h 4 | 5 | -------------------------------------------------------------------------------- /examples/point_cloud_demo/CANape/point_cloud.cna: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectorgrp/xcp-lite/f56fce9a54524a4dc8adf279fbdc6c0165b615f2/examples/point_cloud_demo/CANape/point_cloud.cna -------------------------------------------------------------------------------- /examples/point_cloud_demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "point_cloud_demo" 3 | version = "0.3.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | anyhow = "1.0" 8 | log = "0.4.21" 9 | env_logger = "0.11.3" 10 | rayon = "1.10.0" 11 | num = "0.4.3" 12 | image = "0.25.2" 13 | 14 | serde = "1.0" 15 | serde_json = "1.0" 16 | lazy_static = "1.4" 17 | cdr = "0.2.4" 18 | 19 | xcp_lite = { path = "../../", features = [] } 20 | 21 | -------------------------------------------------------------------------------- /examples/point_cloud_demo/README.md: -------------------------------------------------------------------------------- 1 | # Point Cloud Demo 2 | 3 | Use CANape to visualize a point cloud 4 | 5 | ![CANape](CANape.png) 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/point_cloud_demo/src/main.rs: -------------------------------------------------------------------------------- 1 | // xcp-lite - point cloud demo 2 | // Visualize a dynamic list of 3D points in CANape 3 | 4 | use anyhow::Result; 5 | #[allow(unused_imports)] 6 | use log::{debug, error, info, trace, warn}; 7 | use std::net::Ipv4Addr; 8 | use std::{ 9 | f64::consts::PI, 10 | thread, 11 | time::{Duration, Instant}, 12 | }; 13 | 14 | use xcp_lite::registry::*; 15 | use xcp_lite::*; 16 | 17 | //----------------------------------------------------------------------------- 18 | // Parameters 19 | 20 | const APP_NAME: &str = "point_cloud"; 21 | 22 | const XCP_QUEUE_SIZE: u32 = 1024 * 64; // 64kB 23 | const MAINLOOP_CYCLE_TIME: u32 = 10000; // 10ms 24 | 25 | //----------------------------------------------------------------------------- 26 | // Command line arguments 27 | 28 | const DEFAULT_LOG_LEVEL: u8 = 3; // Info 29 | const DEFAULT_BIND_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0); 30 | const DEFAULT_PORT: u16 = 5555; 31 | const DEFAULT_TCP: bool = false; // UDP 32 | 33 | use clap::Parser; 34 | 35 | #[derive(Parser, Debug)] 36 | #[command(version, about, long_about = None)] 37 | struct Args { 38 | /// Log level (Off=0, Error=1, Warn=2, Info=3, Debug=4, Trace=5) 39 | #[arg(short, long, default_value_t = DEFAULT_LOG_LEVEL)] 40 | log_level: u8, 41 | 42 | /// Bind address, default is ANY 43 | #[arg(short, long, default_value_t = DEFAULT_BIND_ADDR)] 44 | bind: Ipv4Addr, 45 | 46 | /// Use TCP as transport layer, default is UDP 47 | #[arg(short, long, default_value_t = DEFAULT_TCP)] 48 | tcp: bool, 49 | 50 | /// Port number 51 | #[arg(short, long, default_value_t = DEFAULT_PORT)] 52 | port: u16, 53 | 54 | /// Application name 55 | #[arg(short, long, default_value_t = String::from(APP_NAME))] 56 | name: String, 57 | } 58 | 59 | //----------------------------------------------------------------------------- 60 | // Parameters 61 | 62 | const MAX_POINT_COUNT: usize = 20; 63 | const AMPL: f64 = 10.0; 64 | const PERIOD: f64 = 10.0; 65 | 66 | #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, XcpTypeDescription)] 67 | struct Params { 68 | #[characteristic(unit = "s")] 69 | #[characteristic(min = "0.001")] 70 | #[characteristic(max = "10")] 71 | period_x: f64, 72 | 73 | #[characteristic(unit = "m")] 74 | #[characteristic(min = "0.001")] 75 | #[characteristic(max = "100")] 76 | ampl_x: f64, 77 | 78 | #[characteristic(unit = "PI")] 79 | #[characteristic(min = "0.0")] 80 | #[characteristic(max = "1.0")] 81 | phi_x: f64, 82 | 83 | #[characteristic(unit = "s")] 84 | #[characteristic(min = "0.001")] 85 | #[characteristic(max = "10")] 86 | period_y: f64, 87 | 88 | #[characteristic(unit = "m")] 89 | #[characteristic(min = "0.001")] 90 | #[characteristic(max = "100")] 91 | ampl_y: f64, 92 | 93 | #[characteristic(unit = "PI")] 94 | #[characteristic(min = "0.0")] 95 | #[characteristic(max = "2.0")] 96 | phi_y: f64, 97 | 98 | #[characteristic(min = "1")] 99 | #[characteristic(max = "500")] 100 | point_count: u32, 101 | } 102 | 103 | const PARAMS_DEFAULT: Params = Params { 104 | period_x: PERIOD / 2.0, 105 | ampl_x: AMPL, 106 | phi_x: 0.0, 107 | period_y: PERIOD / 4.0, 108 | ampl_y: AMPL, 109 | phi_y: 0.0, 110 | point_count: MAX_POINT_COUNT as u32, 111 | }; 112 | 113 | // Create a static cell for the calibration parameters 114 | static PARAMS: std::sync::OnceLock> = std::sync::OnceLock::new(); 115 | 116 | //--------------------------------------------------------------------------------------- 117 | 118 | #[derive(Debug, serde::Serialize, IdlGenerator)] 119 | struct Point { 120 | x: f32, 121 | y: f32, 122 | z: f32, 123 | } 124 | 125 | #[derive(Debug, serde::Serialize, IdlGenerator)] 126 | struct PointCloud { 127 | points: Vec, 128 | } 129 | 130 | fn create_point_cloud() -> PointCloud { 131 | let params = PARAMS.get().unwrap().clone_calseg(); 132 | let params = params.read_lock(); 133 | let mut point_cloud = PointCloud { 134 | points: Vec::with_capacity(MAX_POINT_COUNT), 135 | }; 136 | 137 | for _ in 0..params.point_count { 138 | point_cloud.points.push(Point { x: 0.0, y: 0.0, z: 0.0 }); 139 | } 140 | calculate_point_cloud(&mut point_cloud, 0.0, 0.0, 0.0); 141 | point_cloud 142 | } 143 | 144 | fn calculate_point_cloud(point_cloud: &mut PointCloud, t: f64, phi: f64, h: f64) { 145 | let params = PARAMS.get().unwrap().clone_calseg(); 146 | let params = params.read_lock(); 147 | 148 | for (i, p) in point_cloud.points.iter_mut().enumerate() { 149 | let a_x: f64 = params.ampl_x; 150 | let a_y: f64 = params.ampl_y; 151 | let omega_x = 2.0 * PI / params.period_x; 152 | let omega_y = 2.0 * PI / params.period_y; 153 | let phi_x = 2.0 * PI / MAX_POINT_COUNT as f64 * i as f64 + phi; 154 | let phi_y = 2.0 * PI / MAX_POINT_COUNT as f64 * i as f64 + phi; 155 | 156 | p.x = (a_x * (omega_x * t + phi_x).cos()) as f32; 157 | p.y = (a_y * (omega_y * t + phi_y).sin()) as f32; 158 | //p.z = (h + (i as f64 * 0.05)) as f32; 159 | p.z = h as f32; 160 | } 161 | } 162 | 163 | //--------------------------------------------------------------------------------------- 164 | 165 | fn main() -> Result<()> { 166 | println!("point cloud demo"); 167 | 168 | // Args 169 | let args = Args::parse(); 170 | let log_level = match args.log_level { 171 | 2 => log::LevelFilter::Warn, 172 | 3 => log::LevelFilter::Info, 173 | 4 => log::LevelFilter::Debug, 174 | 5 => log::LevelFilter::Trace, 175 | _ => log::LevelFilter::Error, 176 | }; 177 | 178 | // Logging 179 | env_logger::Builder::new() 180 | .target(env_logger::Target::Stdout) 181 | .filter_level(log_level) 182 | .format_timestamp(None) 183 | .format_module_path(false) 184 | .format_target(false) 185 | .init(); 186 | 187 | // XCP: Initialize the XCP server 188 | let app_name = args.name.as_str(); 189 | let app_revision = build_info::format!("{}", $.timestamp); 190 | let _ = Xcp::get() 191 | .set_app_name(app_name) 192 | .set_app_revision(app_revision) 193 | .set_log_level(args.log_level) 194 | .start_server( 195 | if args.tcp { XcpTransportLayer::Tcp } else { XcpTransportLayer::Udp }, 196 | args.bind.octets(), 197 | args.port, 198 | XCP_QUEUE_SIZE, 199 | )?; 200 | 201 | // XCP: Get the calibration parameter set and register all struct fields (with meta data from annotations) in the A2L registry 202 | let params = PARAMS.get_or_init(|| CalCell::new("point_cloud_params", &PARAMS_DEFAULT)).clone_calseg(); 203 | params.register_fields(); 204 | 205 | let mut point_cloud = create_point_cloud(); 206 | let mut counter: u64 = 0; 207 | let mut phi = 0.0; 208 | let mut h = 0.0; 209 | let start_time = Instant::now(); 210 | let mut time = 0.0; 211 | info!("Created point cloud: MAX_POINT_COUNT = {}, size = {} bytes", MAX_POINT_COUNT, MAX_POINT_COUNT * 12 + 8); 212 | 213 | // XCP: Create a measurement variables and event with capture buffer for the point cloud 214 | let mut event_point_cloud = daq_create_event!("point_cloud", MAX_POINT_COUNT * 12 + 8); 215 | daq_register!(counter, event_point_cloud); 216 | daq_register!(phi, event_point_cloud); 217 | daq_register!(h, event_point_cloud); 218 | daq_register!(time, event_point_cloud); 219 | 220 | let mut point_count = params.read_lock().point_count; 221 | loop { 222 | thread::sleep(Duration::from_micros(MAINLOOP_CYCLE_TIME as u64)); 223 | time = start_time.elapsed().as_micros() as f64 * 0.000001; // s 224 | 225 | counter += 1; 226 | if counter > 256 { 227 | counter = 0; 228 | } 229 | 230 | phi += 2.0 * PI / MAX_POINT_COUNT as f64 * 0.001; 231 | if phi > 2.0 * PI / MAX_POINT_COUNT as f64 { 232 | phi = 0.0; 233 | } 234 | h += 0.01; 235 | if h > 20.0 { 236 | h = 0.0; 237 | } 238 | calculate_point_cloud(&mut point_cloud, time, phi, h); 239 | 240 | // Serialize point_cloud into the event capture buffer 241 | daq_serialize!(point_cloud, event_point_cloud, "point cloud demo"); 242 | 243 | // Trigger the measurement event 244 | event_point_cloud.trigger(); 245 | 246 | // Simply recreate the point cloud, when the number of points has changed 247 | let new_point_count = params.read_lock().point_count; 248 | if new_point_count != point_count { 249 | point_count = new_point_count; 250 | point_cloud = create_point_cloud(); 251 | } 252 | } 253 | // Ok(()) 254 | } 255 | -------------------------------------------------------------------------------- /examples/rayon_demo/CANape/.gitignore: -------------------------------------------------------------------------------- 1 | /tokio_demo.HEX 2 | /FLASH_tokio_demo.HEX 3 | -------------------------------------------------------------------------------- /examples/rayon_demo/CANape/UpdateHTMLWindows.cns: -------------------------------------------------------------------------------- 1 | UpdateHTMLWindows(); 2 | -------------------------------------------------------------------------------- /examples/rayon_demo/CANape/rayon_demo.cna: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectorgrp/xcp-lite/f56fce9a54524a4dc8adf279fbdc6c0165b615f2/examples/rayon_demo/CANape/rayon_demo.cna -------------------------------------------------------------------------------- /examples/rayon_demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rayon_demo" 3 | version = "0.3.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | log = "0.4.21" 8 | env_logger = "0.11.3" 9 | anyhow = "1.0" 10 | num_cpus = "1.16.0" 11 | rayon = "1.10.0" 12 | num = "0.4.3" 13 | image = "0.25.2" 14 | serde = "1.0" 15 | serde_json = "1.0" 16 | lazy_static = "1.4" 17 | 18 | xcp_lite = { path = "../../", features = [] } 19 | 20 | -------------------------------------------------------------------------------- /examples/rayon_demo/README.md: -------------------------------------------------------------------------------- 1 | # rayon_demo 2 | 3 | Use CANape to visualize start and stop of synchronous tasks in a rayon worker thread pool. 4 | Taken from the mandelbrot rayon example in the book "Programming Rust" by Jim Blandy and Jason Orendorff. 5 | 6 | Image is recalculated when a parameter is changed. 7 | 8 | ![CANape](CANape.png) 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/single_thread_demo/CANape/single_thread.cna: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectorgrp/xcp-lite/f56fce9a54524a4dc8adf279fbdc6c0165b615f2/examples/single_thread_demo/CANape/single_thread.cna -------------------------------------------------------------------------------- /examples/single_thread_demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "single_thread_demo" 3 | version = "0.3.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | log = "0.4.21" 8 | env_logger = "0.11.3" 9 | anyhow = "1.0" 10 | lazy_static = "1.4" 11 | serde = "1.0" 12 | serde_json = "1.0" 13 | 14 | xcp_lite = { path = "../../", features = [] } 15 | 16 | -------------------------------------------------------------------------------- /examples/single_thread_demo/README.md: -------------------------------------------------------------------------------- 1 | # xcp-lite - single_thread_demo 2 | 3 | Demo how to use the xcp crate with in a simple single threaded application 4 | -------------------------------------------------------------------------------- /examples/single_thread_demo/src/main.rs: -------------------------------------------------------------------------------- 1 | // xcp-lite - single_thread_demo 2 | #![allow(unused_imports)] 3 | 4 | use anyhow::Result; 5 | use log::{debug, error, info, trace, warn}; 6 | use std::net::Ipv4Addr; 7 | use std::{ 8 | f64::consts::PI, 9 | fmt::Debug, 10 | thread, 11 | time::{Duration, Instant}, 12 | }; 13 | 14 | use xcp_lite::registry::*; 15 | use xcp_lite::*; 16 | 17 | //----------------------------------------------------------------------------- 18 | // Parameters 19 | 20 | const APP_NAME: &str = "single_thread_demo"; 21 | const JSON_FILE: &str = "single_thread_demo.json"; // JSON file for calibration segment 22 | 23 | const XCP_QUEUE_SIZE: u32 = 1024 * 64; // 64kB 24 | const MAINLOOP_CYCLE_TIME: u32 = 10000; // 10ms 25 | 26 | // Static application start time 27 | lazy_static::lazy_static! { 28 | static ref START_TIME: Instant = Instant::now(); 29 | } 30 | 31 | //----------------------------------------------------------------------------- 32 | // Command line arguments 33 | 34 | const DEFAULT_LOG_LEVEL: u8 = 3; // Info 35 | const DEFAULT_BIND_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0); 36 | const DEFAULT_PORT: u16 = 5555; 37 | const DEFAULT_TCP: bool = false; // UDP 38 | 39 | use clap::Parser; 40 | 41 | #[derive(Parser, Debug)] 42 | #[command(version, about, long_about = None)] 43 | struct Args { 44 | /// Log level (Off=0, Error=1, Warn=2, Info=3, Debug=4, Trace=5) 45 | #[arg(short, long, default_value_t = DEFAULT_LOG_LEVEL)] 46 | log_level: u8, 47 | 48 | /// Bind address, default is ANY 49 | #[arg(short, long, default_value_t = DEFAULT_BIND_ADDR)] 50 | bind: Ipv4Addr, 51 | 52 | /// Use TCP as transport layer, default is UDP 53 | #[arg(short, long, default_value_t = DEFAULT_TCP)] 54 | tcp: bool, 55 | 56 | /// Port number 57 | #[arg(short, long, default_value_t = DEFAULT_PORT)] 58 | port: u16, 59 | 60 | /// Application name 61 | #[arg(short, long, default_value_t = String::from(APP_NAME))] 62 | name: String, 63 | } 64 | 65 | //----------------------------------------------------------------------------- 66 | // Demo calibration parameters 67 | 68 | // Define a struct with calibration parameters 69 | #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, XcpTypeDescription)] 70 | struct Params { 71 | #[characteristic(comment = "Amplitude of the sine signal in mV")] 72 | #[characteristic(unit = "mV")] 73 | #[characteristic(min = "0")] 74 | #[characteristic(max = "8000")] 75 | ampl: u16, 76 | 77 | #[characteristic(comment = "Period of the sine signal")] 78 | #[characteristic(unit = "s")] 79 | #[characteristic(min = "0.001")] 80 | #[characteristic(max = "10")] 81 | period: f64, 82 | 83 | #[characteristic(comment = "Counter maximum value")] 84 | #[characteristic(min = "0")] 85 | #[characteristic(max = "255")] 86 | counter_max: u32, 87 | 88 | #[characteristic(comment = "Task delay time in s, ecu internal value as u32 in us")] 89 | #[characteristic(min = "0.00001", max = "2", unit = "s", factor = "0.000001")] 90 | delay: u32, 91 | } 92 | 93 | // Default calibration values 94 | // This will be the FLASH page in the calibration memory segment 95 | const PARAMS: Params = Params { 96 | ampl: 1000, // mV 97 | period: 5.0, // s 98 | counter_max: 100, 99 | delay: MAINLOOP_CYCLE_TIME, 100 | }; 101 | 102 | //----------------------------------------------------------------------------- 103 | // Demo task 104 | 105 | // A task executed in multiple threads sharing a calibration parameter segment 106 | fn task(params: CalSeg) { 107 | // Demo signal 108 | let mut sine: i16 = 0; // mV 109 | 110 | // Create an event and register variables for measurement directly from stack 111 | let event = daq_create_event!("thread_loop", 16); 112 | daq_register!(sine, event, "sine wave signal, internal value in mV", "Volt", 0.001, 0.0); 113 | 114 | loop { 115 | // Lock the calibration segment for read access 116 | let params = params.read_lock(); 117 | 118 | // A sine signal with amplitude and period from calibration parameters 119 | // The value here is the internal value in mV as i16, CANape will convert it to Volt 120 | let time = START_TIME.elapsed().as_micros() as f64 * 0.000001; // s 121 | sine = ((params.ampl as f64) * ((PI * time) / params.period).sin()) as i16; 122 | let _ = sine; 123 | 124 | // Trigger the measurement event 125 | event.trigger(); 126 | 127 | thread::sleep(Duration::from_micros(params.delay as u64)); 128 | } 129 | } 130 | 131 | //----------------------------------------------------------------------------- 132 | // Demo application main 133 | 134 | fn main() -> Result<()> { 135 | println!("XCPlite Single Thread Demo"); 136 | 137 | // Args 138 | let args = Args::parse(); 139 | let log_level = match args.log_level { 140 | 2 => log::LevelFilter::Warn, 141 | 3 => log::LevelFilter::Info, 142 | 4 => log::LevelFilter::Debug, 143 | 5 => log::LevelFilter::Trace, 144 | _ => log::LevelFilter::Error, 145 | }; 146 | 147 | // Logging 148 | env_logger::Builder::new() 149 | .target(env_logger::Target::Stdout) 150 | .filter_level(log_level) 151 | .format_timestamp(None) 152 | .format_module_path(false) 153 | .format_target(false) 154 | .init(); 155 | 156 | // XCP: Initialize the XCP server 157 | let app_name = args.name.as_str(); 158 | let app_revision = build_info::format!("{}", $.timestamp); 159 | let _xcp = Xcp::get() 160 | .set_app_name(app_name) 161 | .set_app_revision(app_revision) 162 | .set_log_level(args.log_level) 163 | .start_server( 164 | if args.tcp { XcpTransportLayer::Tcp } else { XcpTransportLayer::Udp }, 165 | args.bind.octets(), 166 | args.port, 167 | XCP_QUEUE_SIZE, 168 | )?; 169 | 170 | // Create a calibration parameter set "calseg" 171 | // This will define a MEMORY_SEGMENT named "calseg" in A2L 172 | // Calibration segments have 2 pages, a constant default "FLASH" page and a mutable working "RAM" page 173 | // FLASH or RAM can be switched during runtime (XCP set_cal_page), saved to json (XCP freeze) freeze, reinitialized from FLASH (XCP copy_cal_page) 174 | let params = CalSeg::new( 175 | "calseg", // name of the calibration segment and the .json file 176 | &PARAMS, // default calibration values 177 | ); 178 | params.register_fields(); 179 | 180 | // Load calseg from json file 181 | if params.load(JSON_FILE).is_err() { 182 | params.save(JSON_FILE).unwrap(); 183 | } 184 | 185 | // Create a thread 186 | thread::spawn({ 187 | // Move a clone of the calibration parameters into the thread 188 | let params = CalSeg::clone(¶ms); 189 | move || { 190 | task(params); 191 | } 192 | }); 193 | 194 | // Measurement variable 195 | let mut counter: u32 = 0; 196 | 197 | // Create a measurement event 198 | // This will apear as measurement mode in the CANape measurement configuration 199 | let event = daq_create_event!("main_loop"); 200 | 201 | // Register local variables and associate them to the event 202 | daq_register!(counter, event); 203 | 204 | loop { 205 | // Lock the calibration segment for read access 206 | let calseg = params.read_lock(); 207 | 208 | // A saw tooth counter with a max value from a calibration parameter 209 | counter += 1; 210 | if counter > calseg.counter_max { 211 | counter = 0 212 | } 213 | 214 | // Trigger the measurement event 215 | // The measurement event timestamp is taken here and captured data is sent to CANape 216 | event.trigger(); 217 | 218 | thread::sleep(Duration::from_micros(calseg.delay as u64)); 219 | 220 | // Generate the A2L file once immediately 221 | // xcp.finalize_registry().unwrap(); 222 | } 223 | 224 | // Stop the XCP server 225 | // Xcp::stop_server(); 226 | 227 | // Ok(()) 228 | } 229 | -------------------------------------------------------------------------------- /examples/struct_measurement_demo/CANape/.gitignore: -------------------------------------------------------------------------------- 1 | *.HEX 2 | *.MDF 3 | *.CNAXML 4 | 5 | -------------------------------------------------------------------------------- /examples/struct_measurement_demo/CANape/FunctionFallback/.gitignore: -------------------------------------------------------------------------------- 1 | /CycleTime.fup 2 | -------------------------------------------------------------------------------- /examples/struct_measurement_demo/CANape/xcp_demo.cna: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectorgrp/xcp-lite/f56fce9a54524a4dc8adf279fbdc6c0165b615f2/examples/struct_measurement_demo/CANape/xcp_demo.cna -------------------------------------------------------------------------------- /examples/struct_measurement_demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "struct_measurement_demo" 3 | version = "0.3.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | log = "0.4.21" 8 | env_logger = "0.11.3" 9 | anyhow = "1.0" 10 | rand = "0.8" 11 | lazy_static = "1.4" 12 | 13 | # xcp_lite = { path = "../../", default-features = false } 14 | xcp_lite = { path = "../../" } 15 | -------------------------------------------------------------------------------- /examples/struct_measurement_demo/README.md: -------------------------------------------------------------------------------- 1 | # xcp_lite - struct_measurement_demo 2 | 3 | Demonstrate measurement of nested struct instances 4 | Make use of A2L objects INSTANCE, TYPEDEF_MEASUREMENT TYPEDEF_STRUCTURE and STRUCTURE_COMPONENT 5 | 6 | Run: 7 | ``` 8 | cargo run --example struct_measurement_demo 9 | ``` 10 | 11 | Start the CANape project in the CANape folder or find some screenshots below 12 | 13 | 14 | 15 | ## CANape 16 | 17 | 18 | ![CANape](CANape1.png) 19 | 20 | 21 | ![CANape](CANape2.png) 22 | 23 | 24 | ![CANape](CANape3.png) 25 | 26 | 27 | ![CANape](CANape4.png) 28 | 29 | 30 | ## A2L file 31 | 32 | Code: 33 | 34 | ``` rust 35 | 36 | #[derive(XcpTypeDescription, Debug, Clone, Copy)] 37 | struct Counters { 38 | #[measurement(comment = "counter", min = "0.0", max = "1000.0")] 39 | a: i16, 40 | #[measurement(comment = "counter*2", min = "0.0", max = "2000.0")] 41 | b: u64, 42 | #[measurement(comment = "counter*3", min = "0.0", max = "3000.0")] 43 | c: f64, 44 | } 45 | 46 | #[derive(XcpTypeDescription, Debug, Clone, Copy)] 47 | struct Point { 48 | #[measurement(comment = "x-coordinate", min = "-10.0", max = "10.0", unit = "m")] 49 | x: f32, 50 | #[measurement(comment = "y-coordinate", min = "-10.0", max = "10.0", unit = "m")] 51 | y: f32, 52 | #[measurement(comment = "z-coordinate", min = "-10.0", max = "10.0", unit = "m")] 53 | z: f32, 54 | } 55 | 56 | #[derive(XcpTypeDescription, Debug, Clone, Copy)] 57 | struct Data { 58 | 59 | // Scalar value 60 | cycle_counter: u32, 61 | 62 | // Scalar values with annotations for min, max, conversion rule, physical unit, ... 63 | #[measurement(comment = "cpu temperature in grad celcius", min = "-50", max = "150", offset = "-50.0", unit = "deg/celcius")] 64 | cpu_temperature: u8, 65 | #[measurement(comment = "mainloop cycle time in s, converted from us", factor = "0.000001", unit = "s")] 66 | cycle_time: u32, 67 | 68 | // Array of scalar value with conversion rule 69 | #[measurement(comment = "cycle_time distribution in %", min = "0", max = "100", unit = "%")] 70 | cycle_time_distribution: [u32; 100], 71 | 72 | // Single instance of a point 73 | #[measurement(comment = "A single point")] 74 | point: Point, 75 | 76 | // An array of points 77 | #[measurement(comment = "Array of 8 points")] 78 | point_array: [Point; 8], 79 | 80 | #[measurement(comment = "Matrix of 16*16 float values")] 81 | float_matrix: [[f32; 32]; 32], 82 | } 83 | 84 | 85 | ``` 86 | 87 | 88 | Generated A2L: 89 | 90 | ``` 91 | 92 | /* Typedefs */ 93 | /begin TYPEDEF_MEASUREMENT a "counter" SWORD IDENTITY 0 0 0 1000 /end TYPEDEF_MEASUREMENT 94 | /begin TYPEDEF_MEASUREMENT b "counter*2" A_UINT64 IDENTITY 0 0 0 2000 /end TYPEDEF_MEASUREMENT 95 | /begin TYPEDEF_MEASUREMENT c "counter*3" FLOAT64_IEEE NO_COMPU_METHOD 0 0 0 3000 /end TYPEDEF_MEASUREMENT 96 | /begin TYPEDEF_STRUCTURE Counters "" 24 97 | /begin STRUCTURE_COMPONENT a a 16 /end STRUCTURE_COMPONENT 98 | /begin STRUCTURE_COMPONENT b b 0 /end STRUCTURE_COMPONENT 99 | /begin STRUCTURE_COMPONENT c c 8 /end STRUCTURE_COMPONENT 100 | /end TYPEDEF_STRUCTURE 101 | /begin TYPEDEF_MEASUREMENT cycle_counter "" ULONG IDENTITY 0 0 0 4294967295 /end TYPEDEF_MEASUREMENT 102 | /begin COMPU_METHOD cpu_temperature "" LINEAR "%.0" "deg/celcius" COEFFS_LINEAR 1 -50 /end COMPU_METHOD 103 | /begin TYPEDEF_MEASUREMENT cpu_temperature "cpu temperature in grad celcius" UBYTE cpu_temperature 0 0 -50 150 /end TYPEDEF_MEASUREMENT 104 | /begin COMPU_METHOD cycle_time "" LINEAR "%.6" "s" COEFFS_LINEAR 0.000001 0 /end COMPU_METHOD 105 | /begin TYPEDEF_MEASUREMENT cycle_time "mainloop cycle time in s, converted from us" ULONG cycle_time 0 0 0 4294.9672949999995 /end TYPEDEF_MEASUREMENT 106 | /begin TYPEDEF_MEASUREMENT cycle_time_distribution "cycle_time distribution in %" ULONG IDENTITY 0 0 0 100 MATRIX_DIM 100 /end TYPEDEF_MEASUREMENT 107 | /begin TYPEDEF_MEASUREMENT x "x-coordinate" FLOAT32_IEEE NO_COMPU_METHOD 0 0 -10 10 /end TYPEDEF_MEASUREMENT 108 | /begin TYPEDEF_MEASUREMENT y "y-coordinate" FLOAT32_IEEE NO_COMPU_METHOD 0 0 -10 10 /end TYPEDEF_MEASUREMENT 109 | /begin TYPEDEF_MEASUREMENT z "z-coordinate" FLOAT32_IEEE NO_COMPU_METHOD 0 0 -10 10 /end TYPEDEF_MEASUREMENT 110 | /begin TYPEDEF_STRUCTURE Point "" 12 111 | /begin STRUCTURE_COMPONENT x x 0 /end STRUCTURE_COMPONENT 112 | /begin STRUCTURE_COMPONENT y y 4 /end STRUCTURE_COMPONENT 113 | /begin STRUCTURE_COMPONENT z z 8 /end STRUCTURE_COMPONENT 114 | /end TYPEDEF_STRUCTURE 115 | /begin TYPEDEF_MEASUREMENT float_matrix "Matrix of 16*16 float values" FLOAT32_IEEE NO_COMPU_METHOD 0 0 -100000000000000000000000000000000 100000000000000000000000000000000 MATRIX_DIM 32 32 /end TYPEDEF_MEASUREMENT 116 | /begin TYPEDEF_STRUCTURE Data "" 4616 117 | /begin STRUCTURE_COMPONENT cycle_counter cycle_counter 4592 /end STRUCTURE_COMPONENT 118 | /begin STRUCTURE_COMPONENT cpu_temperature cpu_temperature 4612 /end STRUCTURE_COMPONENT 119 | /begin STRUCTURE_COMPONENT cycle_time cycle_time 4596 /end STRUCTURE_COMPONENT 120 | /begin STRUCTURE_COMPONENT cycle_time_distribution cycle_time_distribution 4192 /end STRUCTURE_COMPONENT 121 | /begin STRUCTURE_COMPONENT point Point 4600 /end STRUCTURE_COMPONENT 122 | /begin STRUCTURE_COMPONENT point_array Point 4096 MATRIX_DIM 8 /end STRUCTURE_COMPONENT 123 | /begin STRUCTURE_COMPONENT float_matrix float_matrix 0 /end STRUCTURE_COMPONENT 124 | /end TYPEDEF_STRUCTURE 125 | /begin TYPEDEF_CHARACTERISTIC mainloop_cycle_time "Cycle time of the mainloop" VALUE U32 0 IDENTITY 100 10000 /end TYPEDEF_CHARACTERISTIC 126 | /begin TYPEDEF_CHARACTERISTIC counter_max "Counter wraparound" VALUE U16 0 IDENTITY 0 10000 /end TYPEDEF_CHARACTERISTIC 127 | /begin TYPEDEF_CHARACTERISTIC ampl "Amplitude of the sine signal" VALUE F64 0 NO_COMPU_METHOD 0 500 /end TYPEDEF_CHARACTERISTIC 128 | /begin TYPEDEF_CHARACTERISTIC period "Period of the sine signal" VALUE F64 0 NO_COMPU_METHOD 0.001 10 /end TYPEDEF_CHARACTERISTIC 129 | /begin TYPEDEF_STRUCTURE Parameters "" 24 130 | /begin STRUCTURE_COMPONENT mainloop_cycle_time mainloop_cycle_time 16 /end STRUCTURE_COMPONENT 131 | /begin STRUCTURE_COMPONENT counter_max counter_max 20 /end STRUCTURE_COMPONENT 132 | /begin STRUCTURE_COMPONENT ampl ampl 0 /end STRUCTURE_COMPONENT 133 | /begin STRUCTURE_COMPONENT period period 8 /end STRUCTURE_COMPONENT 134 | /end TYPEDEF_STRUCTURE 135 | 136 | 137 | /* Measurements */ 138 | /begin MEASUREMENT counter1 "" A_UINT64 IDENTITY 0 0 0 18446744073709552000 ECU_ADDRESS 0x3C ECU_ADDRESS_EXTENSION 3 /begin IF_DATA XCP /begin DAQ_EVENT FIXED_EVENT_LIST EVENT 0 /end DAQ_EVENT /end IF_DATA /end MEASUREMENT 139 | /begin MEASUREMENT counter2 "" ULONG IDENTITY 0 0 0 4294967295 ECU_ADDRESS 0x48 ECU_ADDRESS_EXTENSION 3 /begin IF_DATA XCP /begin DAQ_EVENT FIXED_EVENT_LIST EVENT 0 /end DAQ_EVENT /end IF_DATA /end MEASUREMENT 140 | /begin INSTANCE counters "" Counters 0xFFFFFFD4 ECU_ADDRESS_EXTENSION 3 /begin IF_DATA XCP /begin DAQ_EVENT FIXED_EVENT_LIST EVENT 0 /end DAQ_EVENT /end IF_DATA /end INSTANCE 141 | /begin INSTANCE data "" Data 0xFFFFEDA4 ECU_ADDRESS_EXTENSION 3 /begin IF_DATA XCP /begin DAQ_EVENT FIXED_EVENT_LIST EVENT 0 /end DAQ_EVENT /end IF_DATA /end INSTANCE 142 | /begin GROUP Measurements "" ROOT /begin SUB_GROUP mainloop /end SUB_GROUP /end GROUP 143 | /begin GROUP mainloop "" /begin REF_MEASUREMENT counter1 counter2 counters data /end REF_MEASUREMENT /end GROUP 144 | 145 | /* Axis */ 146 | 147 | /* Characteristics */ 148 | /begin INSTANCE parameters "" Parameters 0x80010000 /end INSTANCE 149 | 150 | 151 | ``` 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /examples/tokio_demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tokio_demo" 3 | version = "0.3.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | log = "0.4.21" 8 | env_logger = "0.11.3" 9 | chrono = "0.4.38" 10 | lazy_static = "1.4" 11 | once_cell = "1.19.0" 12 | rand = "0.9" 13 | 14 | 15 | tokio = { version = "1.37.0", features = ["full"] } 16 | 17 | serde = "1.0" 18 | serde_json = "1.0" 19 | 20 | xcp_lite = { path = "../../", features = [] } 21 | 22 | -------------------------------------------------------------------------------- /examples/tokio_demo/README.md: -------------------------------------------------------------------------------- 1 | # xcp-lite_demo 2 | 3 | Demo how to use the xcp crate with tokio based applications -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | 2 | max_width=180 3 | newline_style = "Unix" 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Crate xcp_lite 3 | // Path: src/lib.rs 4 | 5 | // 6 | // Note that the tests can not be executed in parallel 7 | // Use cargo test --features=a2l_reader -- --test-threads=1 --nocapture 8 | 9 | // This crate is a library 10 | #![crate_type = "lib"] 11 | // The library crate is named "xcp_lite" 12 | #![crate_name = "xcp_lite"] 13 | // 14 | // Disabled clippy lints 15 | //#![allow(non_snake_case)] 16 | #![allow(non_camel_case_types)] 17 | #![allow(non_upper_case_globals)] // bindgen 18 | #![allow(dead_code)] // bindgen 19 | 20 | // #![warn(clippy::pedantic)] 21 | // #![allow(clippy::doc_markdown)] 22 | // #![allow(clippy::missing_errors_doc)] 23 | // #![allow(clippy::missing_panics_doc)] 24 | // #![allow(clippy::must_use_candidate)] 25 | // #![allow(clippy::uninlined_format_args)] 26 | // #![allow(clippy::module_name_repetitions)] 27 | // #![allow(clippy::struct_field_names)] 28 | // #![allow(clippy::unreadable_literal)] 29 | // #![allow(clippy::if_not_else)] 30 | // #![allow(clippy::wildcard_imports)] 31 | // #![allow(clippy::cast_lossless)] 32 | // #![allow(clippy::ref_as_ptr)] 33 | // #![allow(clippy::ptr_as_ptr)] 34 | // #![allow(clippy::cast_possible_wrap)] 35 | // #![allow(clippy::trivially_copy_pass_by_ref)] 36 | // 37 | 38 | //----------------------------------------------------------------------------- 39 | 40 | // Submodule xcp 41 | mod xcp; 42 | #[doc(hidden)] 43 | pub use xcp::CalCell; 44 | #[doc(hidden)] 45 | pub use xcp::CalSeg; 46 | #[doc(inline)] 47 | pub use xcp::Xcp; 48 | #[doc(hidden)] // For integration test 49 | pub use xcp::XcpCalPage; 50 | #[doc(inline)] 51 | pub use xcp::XcpError; 52 | #[doc(hidden)] // For macro use only 53 | pub use xcp::XcpEvent; 54 | #[doc(inline)] 55 | pub use xcp::XcpTransportLayer; 56 | #[doc(hidden)] // For macro use only 57 | pub use xcp::daq::daq_event::DaqEvent; 58 | 59 | // Public submodule registry 60 | pub mod registry; 61 | pub use registry::McValueTypeTrait; 62 | 63 | // Public submodule metrics 64 | pub mod metrics; 65 | 66 | // Used by macros 67 | #[doc(hidden)] 68 | pub use xcp_idl_generator::prelude::*; 69 | #[doc(hidden)] 70 | pub use xcp_type_description::prelude::*; 71 | -------------------------------------------------------------------------------- /src/metrics/counter.rs: -------------------------------------------------------------------------------- 1 | use crate::Xcp; 2 | use crate::XcpEvent; 3 | use crate::registry::*; 4 | 5 | //------------------------------------------------------------------------------------------------- 6 | // Macros 7 | 8 | // Static state, thread local state not implemented yet 9 | #[allow(unused_macros)] 10 | #[macro_export] 11 | macro_rules! metrics_counter { 12 | ( $name: expr ) => {{ 13 | static __COUNTER_VALUE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); 14 | static __COUNTER_EVENT: std::sync::OnceLock = std::sync::OnceLock::new(); 15 | __COUNTER_VALUE.fetch_add(1, std::sync::atomic::Ordering::Relaxed); 16 | let event = __COUNTER_EVENT.get_or_init(|| xcp_lite::metrics::counter::register($name)); 17 | unsafe { 18 | event.trigger_ext(&__COUNTER_VALUE as *const _ as *const u8); 19 | } 20 | }}; 21 | } 22 | 23 | /// Register the counter with the given name as measurement variable with dynamic addressing mode (async access (polling) possible) 24 | /// # Panics 25 | /// If the name is not unique in global measurement and calibration object name space 26 | pub fn register(name: &'static str) -> XcpEvent { 27 | let event = Xcp::get().create_event_ext(name, false); 28 | let _ = get_lock().as_mut().unwrap().instance_list.add_instance( 29 | name, 30 | McDimType::new(McValueType::Ulonglong, 1, 1), 31 | McSupportData::new(McObjectType::Measurement), 32 | McAddress::new_event_dyn(event.get_id(), 0), 33 | ); 34 | event 35 | } 36 | -------------------------------------------------------------------------------- /src/metrics/mod.rs: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------------------- 2 | // Module metrics 3 | 4 | mod histogram; 5 | pub use histogram::Histogram; 6 | pub mod counter; 7 | -------------------------------------------------------------------------------- /src/registry/a2l/mod.rs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Module a2l 3 | // Read, write and check A2L files 4 | 5 | #[cfg(feature = "a2l_reader")] 6 | pub mod a2l_reader; 7 | pub mod a2l_writer; 8 | #[cfg(feature = "a2l_reader")] 9 | pub mod aml_ifdata; 10 | 11 | use super::*; 12 | 13 | impl Registry { 14 | //--------------------------------------------------------------------------------------------------------- 15 | // Check A2L file 16 | 17 | /// Check A2L file 18 | /// Syntax and consistency check an A2L file 19 | #[cfg(feature = "a2l_reader")] 20 | pub fn check_a2l>(&self, path: &P) -> Result { 21 | // Read A2L file into A2lFile 22 | let res = a2lfile::load(path, None, true); 23 | match res { 24 | Ok((a2l_file, log_msgs)) => { 25 | let mut warnings: u32 = 0; 26 | 27 | // Log messages 28 | for log_msg in log_msgs { 29 | log::warn!("A2L warning: {}", log_msg); 30 | warnings += 1; 31 | } 32 | 33 | // Perform an additional consistency check 34 | let log_msgs = a2l_file.check(); 35 | for log_msg in log_msgs { 36 | log::warn!("A2L check: {}", log_msg); 37 | warnings += 1; 38 | } 39 | 40 | Ok(warnings) 41 | } 42 | 43 | Err(e) => Err(format!("a2lfile::load failed: {:?}", e)), 44 | } 45 | } 46 | 47 | //--------------------------------------------------------------------------------------------------------- 48 | // Load A2L file 49 | 50 | /// Load A2L file into this registry 51 | /// # Arguments 52 | /// path - path to A2L file on disk 53 | /// print_warnings - print warnings to log 54 | /// strict - enable strict mode parsing 55 | /// check - perform additional consistency checks and print warnings to log 56 | /// flatten_typedefs - flatten nested typedefs to basic type instances with mangled names 57 | #[cfg(feature = "a2l_reader")] 58 | pub fn load_a2l>(&mut self, path: &P, print_warnings: bool, strict: bool, check: bool, flatten_typedefs: bool) -> Result { 59 | // 60 | // Read A2L file from file into a2lfile::A2lFile data structure 61 | let res = a2lfile::load(path, None, strict); 62 | match res { 63 | Ok((a2l_file, log_msgs)) => { 64 | let mut warnings: u32 = 0; 65 | 66 | // Print all log messages 67 | if print_warnings { 68 | for log_msg in log_msgs { 69 | log::warn!("A2L warning: {}", log_msg); 70 | warnings += 1; 71 | } 72 | } 73 | 74 | // Perform additional consistency checks on a2lfile::A2lFile 75 | if check { 76 | // let mut log_msgs = Vec::new(); 77 | // a2l_file.check(&mut log_msgs); 78 | let log_msgs = a2l_file.check(); 79 | for log_msg in log_msgs { 80 | log::warn!("A2L check: {}", log_msg); 81 | warnings += 1; 82 | } 83 | } 84 | 85 | // Load (merge) a2lfile::A2lFile data structure into this registry 86 | self.load_a2lfile(&a2l_file)?; 87 | 88 | // If requested, flatten nested typedefs to basic type instances with mangled names if required 89 | if flatten_typedefs { 90 | self.flatten_typedefs(); 91 | } 92 | 93 | Ok(warnings) 94 | } 95 | Err(e) => Err(format!("a2lfile::load failed: {:?}", e)), 96 | } 97 | } 98 | 99 | //--------------------------------------------------------------------------------------------------------- 100 | // Write A2L file 101 | 102 | /// Write registry to an A2L file 103 | /// if feature a2l_reader is enabled, option to check A2L file by rereading with with crate a2lfile 104 | /// For testing purposed only, uses a significant amount of memory allocations 105 | /// # Arguments 106 | /// path - path to A2L file on disk 107 | /// check - check A2L file after writing (by reading it again with a2lfile crate) 108 | pub fn write_a2l>(&self, path: &P, check: bool) -> Result<(), std::io::Error> { 109 | // Write to A2L file 110 | log::info!("Write A2L file {:?}", path.as_ref()); 111 | let a2l_file = std::fs::File::create(path)?; 112 | let writer: &mut dyn std::io::Write = &mut std::io::LineWriter::new(a2l_file); 113 | let mut a2l_writer = a2l_writer::A2lWriter::new(writer, self); 114 | let a2l_name = self.get_app_name(); 115 | assert!(!a2l_name.is_empty()); 116 | a2l_writer.write_a2l(a2l_name, a2l_name)?; 117 | 118 | // Check A2L file just written 119 | #[cfg(not(feature = "a2l_reader"))] 120 | if check { 121 | log::warn!("A2L file check not available, feature a2l_reader not enabled"); 122 | } 123 | #[cfg(feature = "a2l_reader")] 124 | if check { 125 | // let mut a2l_filepath: PathBuf = self.get_filename().into(); 126 | // a2l_filepath.set_extension("a2l"); 127 | log::info!("Check A2L file {:?}", path.as_ref()); 128 | match self.check_a2l(path) { 129 | Err(e) => { 130 | log::error!("A2l file check error: {}", e); 131 | } 132 | Ok(w) => { 133 | if w > 0 { 134 | log::warn!("A2L file check with {w} warnings !!"); 135 | } else { 136 | log::info!("A2L file check ok"); 137 | } 138 | } 139 | } 140 | } 141 | 142 | Ok(()) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/registry/data_model.md: -------------------------------------------------------------------------------- 1 | # Registry Data Model 2 | 3 | ```mermaid 4 | classDiagram 5 | 6 | class McRegistry { 7 | instance_list: *McInstance 8 | typedef_list: *McTypedef 9 | event_list: *McEvent 10 | calibration_segment_list: *McCalibrationSegment 11 | } 12 | 13 | class McCalibrationSegment { 14 | name: McIdentifier 15 | index: u16 16 | addr: u64 17 | size: u32 18 | } 19 | 20 | class McEvent { 21 | name: MCIdentifier 22 | index: u16 23 | id: u16 24 | target_cycle_time_ns: u32 25 | } 26 | 27 | class McSupportData { 28 | object_type: enum, 29 | qualifier: enum, 30 | factor: f64 31 | offset: f64 32 | min: f64 33 | max: f64 34 | step: f64 35 | comment: McText 36 | x_axis_ref: McIdentifier 37 | y_axis_ref: McIdentifier 38 | 39 | } 40 | 41 | class McInstance { 42 | name: McIdentifier 43 | dim_type: McDimType 44 | address: McAddress 45 | 46 | } 47 | 48 | class McTypeDef { 49 | name: McIdentifier 50 | fields: *TypeDefField 51 | size: u32 52 | } 53 | 54 | class McTypeDefField { 55 | name: Identifier 56 | dim_type: DimType 57 | offset: u16 58 | } 59 | 60 | class McDimType { 61 | value_type: McValueType 62 | x_dim: u16 63 | y_dim: u16 64 | mc_support_data: McSupportData 65 | } 66 | 67 | class McValueType { 68 | basic_type: McScalarValueType 69 | blob: McIdentifier 70 | typedef: McIdentifier 71 | } 72 | 73 | class McAddress { 74 | addr_ext: u8 75 | addr: u32 76 | offset: i64 77 | calibration_segment: McIdentifier 78 | event: McIdentifier 79 | } 80 | 81 | McRegistry ..> McInstance 82 | McRegistry ..> McTypeDef 83 | McRegistry ..> McEvent 84 | McRegistry ..> McCalibrationSegment 85 | 86 | McAddress ..> McCalibrationSegment: << calseg_id >> 87 | McAddress ..> McEvent: << event_id >> 88 | 89 | McInstance *-- McDimType 90 | McInstance *-- McAddress 91 | 92 | McTypeDef ..> McTypeDefField 93 | 94 | McTypeDefField *-- McDimType 95 | 96 | 97 | McDimType *-- McValueType 98 | McDimType *-- McSupportData 99 | 100 | McValueType ..> McTypeDef 101 | 102 | ``` 103 | -------------------------------------------------------------------------------- /src/registry/mc_calseg.rs: -------------------------------------------------------------------------------- 1 | // Module mc_calseg 2 | // Types: 3 | // McTypeDef, McTypeDefField 4 | 5 | use std::borrow::Cow; 6 | 7 | use serde::Deserialize; 8 | use serde::Serialize; 9 | 10 | use super::McAddress; 11 | use super::McIdentifier; 12 | use super::Registry; 13 | use super::RegistryError; 14 | 15 | //------------------------------------------------------------------------------------------------- 16 | // Calibration segments 17 | 18 | // A range of continuous memory which contains only calibration parameters 19 | // Calibration parameters belong to a calibration segment when their address is in this range 20 | // Calibration parameters will never be changed by the application 21 | // The calibration tool is then able to modify contents of the calibration segment in a thread safe way 22 | #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize)] 23 | pub struct McCalibrationSegment { 24 | pub name: McIdentifier, // Unique name of the calibration segment 25 | pub index: u16, // Unique index of the calibration segment, used for relative addressing 26 | pub addr: u32, // Start address 27 | pub addr_ext: u8, // Address extension 28 | pub size: u32, // Size in bytes 29 | } 30 | 31 | impl McCalibrationSegment { 32 | pub fn new>(name: T, index: u16, addr: u32, addr_ext: u8, size: u32) -> McCalibrationSegment { 33 | let name: McIdentifier = name.into(); 34 | McCalibrationSegment { 35 | name, 36 | index, 37 | addr, 38 | addr_ext, 39 | size, 40 | } 41 | } 42 | 43 | /// Get the calibration segment name 44 | pub fn get_name(&self) -> &'static str { 45 | self.name.as_str() 46 | } 47 | 48 | /// Get the full indexed name of the calibration segment 49 | /// The calibration segment name may not be unique, segments with the same name may be created by multiple thread instances of a task, this is indicated by index > 0 50 | /// The name is prefixed with the application name if prefix_names is set 51 | pub fn get_prefixed_name(&self, registry: &Registry) -> Cow<'static, str> { 52 | if registry.prefix_names { 53 | Cow::Owned(format!("{}.{}", registry.get_app_name(), self.name)) 54 | } else { 55 | Cow::Borrowed(self.name.as_str()) 56 | } 57 | } 58 | } 59 | 60 | //---------------------------------------------------------------------------------------------- 61 | // McCalibrationSegmentList 62 | 63 | #[derive(Debug, Default, Serialize, Deserialize)] 64 | pub struct McCalibrationSegmentList(Vec); 65 | 66 | impl McCalibrationSegmentList { 67 | pub fn new() -> Self { 68 | McCalibrationSegmentList(Vec::with_capacity(8)) 69 | } 70 | 71 | pub fn len(&self) -> usize { 72 | self.0.len() 73 | } 74 | pub fn is_empty(&self) -> bool { 75 | self.0.is_empty() 76 | } 77 | pub fn push(&mut self, object: McCalibrationSegment) { 78 | self.0.push(object); 79 | } 80 | 81 | pub fn sort_by_name(&mut self) { 82 | self.0.sort_by(|a, b| a.name.cmp(&b.name)); 83 | } 84 | 85 | /// Add a calibration segment 86 | pub fn add_cal_seg>(&mut self, name: T, index: u16, size: u32) -> Result<(), RegistryError> { 87 | if self.find_cal_seg_by_index(index).is_some() { 88 | let error_msg = format!("Duplicate calibration segment index {}!", index); 89 | log::error!("{}", error_msg); 90 | return Err(RegistryError::Duplicate(error_msg)); 91 | } 92 | let (addr_ext, addr) = McAddress::get_calseg_ext_addr_base(index); 93 | self.add_a2l_cal_seg(name, index, addr_ext, addr, size) 94 | } 95 | 96 | /// Add a calibration segment by name, index, address extension and address 97 | pub fn add_a2l_cal_seg>(&mut self, name: T, index: u16, addr_ext: u8, addr: u32, size: u32) -> Result<(), RegistryError> { 98 | let name: McIdentifier = name.into(); 99 | 100 | // Length of calseg should be %4 to avoid problems with CANape and checksum calculations 101 | // McAddress should also be %4 102 | if size % 4 != 0 { 103 | log::warn!("Calibration segment size should be multiple of 4"); 104 | } 105 | 106 | // Check if name already exists and panic 107 | for s in &self.0 { 108 | if s.name == name { 109 | log::warn!("Duplicate calibration segment {}!", name); 110 | return Err(RegistryError::Duplicate(name.to_string())); 111 | } 112 | } 113 | 114 | log::debug!("Registry add_cal_seg: {} {} {}:0x{:08X}-{} ", name, index, addr_ext, addr, size); 115 | 116 | self.push(McCalibrationSegment::new(name, index, addr, addr_ext, size)); 117 | Ok(()) 118 | } 119 | 120 | /// Find a calibration segment by name 121 | pub fn find_cal_seg(&self, name: &str) -> Option<&McCalibrationSegment> { 122 | self.into_iter().find(|i| i.name == name) 123 | } 124 | 125 | /// Find a calibration segment name by address of a calibration parameter in the segment 126 | /// Returns the name of the calibration segment 127 | pub fn find_cal_seg_by_address(&self, addr: u32) -> Option { 128 | self.into_iter().find(|i| i.addr <= addr && addr < i.addr + i.size).map(|s| s.name) 129 | } 130 | 131 | /// Find a calibration segment name by index 132 | /// Returns the name of the calibration segment 133 | pub fn find_cal_seg_by_index(&self, index: u16) -> Option { 134 | self.into_iter().find(|i| i.index == index).map(|s| s.name) 135 | } 136 | 137 | /// Get calibration segment index by name 138 | /// Index ist used to build addressing information in the XCP protocol 139 | pub fn get_cal_seg_index(&self, name: &str) -> Option { 140 | for s in self { 141 | if s.name == name { 142 | return Some(s.index); 143 | } 144 | } 145 | None 146 | } 147 | } 148 | 149 | //------------------------------------------------------------------------------------------------- 150 | // McCalibrationSegmentListIterator 151 | 152 | /// Iterator for EventList 153 | pub struct McCalibrationSegmentListIterator<'a> { 154 | index: usize, 155 | list: &'a McCalibrationSegmentList, 156 | } 157 | 158 | impl<'a> McCalibrationSegmentListIterator<'_> { 159 | pub fn new(list: &'a McCalibrationSegmentList) -> McCalibrationSegmentListIterator<'a> { 160 | McCalibrationSegmentListIterator { index: 0, list } 161 | } 162 | } 163 | 164 | impl<'a> Iterator for McCalibrationSegmentListIterator<'a> { 165 | type Item = &'a McCalibrationSegment; 166 | 167 | fn next(&mut self) -> Option { 168 | let index = self.index; 169 | if index < self.list.0.len() { 170 | self.index += 1; 171 | Some(&self.list.0[index]) 172 | } else { 173 | None 174 | } 175 | } 176 | } 177 | 178 | impl<'a> IntoIterator for &'a McCalibrationSegmentList { 179 | type Item = &'a McCalibrationSegment; 180 | type IntoIter = McCalibrationSegmentListIterator<'a>; 181 | 182 | fn into_iter(self) -> McCalibrationSegmentListIterator<'a> { 183 | McCalibrationSegmentListIterator::new(self) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/registry/mc_event.rs: -------------------------------------------------------------------------------- 1 | // Module mc_event 2 | // Types: 3 | // McTypeDef, McTypeDefField 4 | 5 | use std::borrow::Cow; 6 | 7 | use serde::Deserialize; 8 | use serde::Serialize; 9 | 10 | use crate::registry::Registry; 11 | use crate::registry::RegistryError; 12 | 13 | use super::McIdentifier; 14 | 15 | //---------------------------------------------------------------------------------------------- 16 | // McEvent 17 | 18 | /// An event which may trigger consistent data acqisition 19 | /// Events have unique id called id number 20 | /// The name is not unique, events with the same name may be created by multiple thread instances of a task, this is indicated by index > 0 21 | #[derive(Debug, Default, Serialize, Deserialize)] 22 | pub struct McEvent { 23 | pub name: McIdentifier, // Name of the event, not unique name 24 | pub index: u16, // Instance index 1..n, 0 if single instance 25 | pub id: u16, // Unique event id number used in A2L and XCP protocol, unique event identifier for an application 26 | pub target_cycle_time_ns: u32, // 0 -> no cycle time = sporadic event 27 | } 28 | 29 | impl McEvent { 30 | /// Create a new event with name, instance index and cycle time in ns 31 | pub fn new>(name: T, index: u16, id: u16, target_cycle_time_ns: u32) -> Self { 32 | let name: McIdentifier = name.into(); 33 | McEvent { 34 | name, 35 | index, 36 | id, 37 | target_cycle_time_ns, 38 | } 39 | } 40 | 41 | /// Get the event name 42 | pub fn get_name(&self) -> &'static str { 43 | self.name.as_str() 44 | } 45 | 46 | /// Get the full indexed name of the event 47 | /// The event name may not be unique, events with the same name may be created by multiple thread instances of a task, this is indicated by index > 0 48 | pub fn get_unique_name(&self, registry: &Registry) -> Cow<'static, str> { 49 | if self.index > 0 { 50 | if registry.prefix_names { 51 | Cow::Owned(format!("{}.{}_{}", registry.get_app_name(), self.name, self.index)) 52 | } else { 53 | Cow::Owned(format!("{}_{}", self.name, self.index)) 54 | } 55 | } else { 56 | if registry.prefix_names { 57 | Cow::Owned(format!("{}_{}", self.name, self.index)) 58 | } else { 59 | Cow::Borrowed(self.name.as_str()) 60 | } 61 | } 62 | } 63 | } 64 | 65 | //---------------------------------------------------------------------------------------------- 66 | // McEventList 67 | 68 | #[derive(Debug, Default, Serialize, Deserialize)] 69 | pub struct McEventList(Vec); 70 | 71 | impl McEventList { 72 | pub fn new() -> Self { 73 | McEventList(Vec::with_capacity(100)) 74 | } 75 | 76 | /// Add an XCP event with name, index and cycle time in ns 77 | pub fn add_event(&mut self, event: McEvent) -> Result<(), RegistryError> { 78 | log::debug!("Registry add_event: {:?} ", event); 79 | 80 | // Error if event with same name and index already exists 81 | if self.find_event(&event.name, event.index).is_some() { 82 | return Err(RegistryError::Duplicate(event.name.to_string())); 83 | } 84 | // Error if event with same unique id (id) already exists 85 | if self.find_event_id(event.id).is_some() { 86 | return Err(RegistryError::Duplicate(event.name.to_string())); 87 | } 88 | 89 | self.push(event); 90 | Ok(()) 91 | } 92 | 93 | pub fn len(&self) -> usize { 94 | self.0.len() 95 | } 96 | pub fn is_empty(&self) -> bool { 97 | self.0.is_empty() 98 | } 99 | pub fn push(&mut self, object: McEvent) { 100 | self.0.push(object); 101 | } 102 | 103 | pub fn sort_by_name(&mut self) { 104 | self.0.sort_by(|a, b| a.name.cmp(&b.name)); 105 | } 106 | 107 | pub fn sort_by_id(&mut self) { 108 | self.0.sort_by(|a, b| a.id.cmp(&b.id)); 109 | } 110 | 111 | /// Find an event by name 112 | pub fn find_event(&self, name: &str, index: u16) -> Option<&McEvent> { 113 | self.0.iter().find(|e| e.index == index && e.name == name) 114 | } 115 | 116 | /// find an event by id 117 | pub fn find_event_id(&self, id: u16) -> Option<&McEvent> { 118 | self.0.iter().find(|e| e.id == id) 119 | } 120 | } 121 | 122 | //------------------------------------------------------------------------------------------------- 123 | // EventListIterator 124 | 125 | /// Iterator for EventList 126 | pub struct McEventListIterator<'a> { 127 | index: usize, 128 | list: &'a McEventList, 129 | } 130 | 131 | impl<'a> McEventListIterator<'_> { 132 | pub fn new(list: &'a McEventList) -> McEventListIterator<'a> { 133 | McEventListIterator { index: 0, list } 134 | } 135 | } 136 | 137 | impl<'a> Iterator for McEventListIterator<'a> { 138 | type Item = &'a McEvent; 139 | 140 | fn next(&mut self) -> Option { 141 | let index = self.index; 142 | if index < self.list.0.len() { 143 | self.index += 1; 144 | Some(&self.list.0[index]) 145 | } else { 146 | None 147 | } 148 | } 149 | } 150 | 151 | impl<'a> IntoIterator for &'a McEventList { 152 | type Item = &'a McEvent; 153 | type IntoIter = McEventListIterator<'a>; 154 | 155 | fn into_iter(self) -> McEventListIterator<'a> { 156 | McEventListIterator::new(self) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/registry/mc_typedef.rs: -------------------------------------------------------------------------------- 1 | // Module mc_typedef 2 | // Types: 3 | // McTypeDef, McTypeDefField 4 | 5 | use serde::Deserialize; 6 | use serde::Serialize; 7 | 8 | use super::McDimType; 9 | use super::McIdentifier; 10 | use super::McSupportData; 11 | use super::McValueType; 12 | use super::RegistryError; 13 | 14 | //------------------------------------------------------------------------------------------------- 15 | // McTypeDef 16 | 17 | // Type definition for McValueType::TypeDef(type_name) 18 | #[derive(Debug, Serialize, Deserialize)] 19 | pub struct McTypeDef { 20 | pub name: McIdentifier, 21 | pub fields: McTypeDefFieldList, // Fields of the struct type_name 22 | pub size: usize, // Size of the struct type_name in bytes 23 | } 24 | 25 | impl McTypeDef { 26 | pub fn new>(type_name: T, size: usize) -> McTypeDef { 27 | let type_name: McIdentifier = type_name.into(); 28 | McTypeDef { 29 | name: type_name, 30 | fields: McTypeDefFieldList::new(), 31 | size, 32 | } 33 | } 34 | 35 | pub fn get_name(&self) -> &'static str { 36 | self.name.as_str() 37 | } 38 | 39 | pub fn find_field(&self, name: &str) -> Option<&McTypeDefField> { 40 | self.fields.into_iter().find(|field| field.name == name) 41 | } 42 | 43 | pub fn add_field>(&mut self, name: T, dim_type: McDimType, mc_support_data: McSupportData, offset: u16) -> Result<(), RegistryError> { 44 | let name: McIdentifier = name.into(); 45 | 46 | // Error if duplicate field name 47 | if self.find_field(name.as_str()).is_some() { 48 | return Err(RegistryError::Duplicate(name.to_string())); 49 | } 50 | 51 | // Add field 52 | self.fields.push(McTypeDefField::new(name, dim_type, mc_support_data, offset)); 53 | Ok(()) 54 | } 55 | } 56 | 57 | //---------------------------------------------------------------------------------------------- 58 | // McTypeDefFieldList 59 | 60 | #[derive(Debug, Default, Serialize, Deserialize)] 61 | pub struct McTypeDefList(Vec); 62 | 63 | impl McTypeDefList { 64 | pub fn new() -> Self { 65 | McTypeDefList(Vec::with_capacity(16)) 66 | } 67 | 68 | pub fn len(&self) -> usize { 69 | self.0.len() 70 | } 71 | pub fn get(&self, index: usize) -> Option<&McTypeDef> { 72 | self.0.get(index) 73 | } 74 | pub fn get_mut(&mut self, index: usize) -> &mut McTypeDef { 75 | &mut self.0[index] 76 | } 77 | pub fn is_empty(&self) -> bool { 78 | self.0.is_empty() 79 | } 80 | pub fn push(&mut self, object: McTypeDef) { 81 | self.0.push(object); 82 | } 83 | pub fn clear(&mut self) { 84 | self.0.clear(); 85 | } 86 | pub fn find_typedef_mut(&mut self, name: &str) -> Option<&mut McTypeDef> { 87 | self.0.iter_mut().find(|i| i.name == name) 88 | } 89 | pub fn find_typedef(&self, name: &str) -> Option<&McTypeDef> { 90 | self.0.iter().find(|i| i.name == name) 91 | } 92 | 93 | pub fn sort_by_name(&mut self) { 94 | self.0.sort_by(|a, b| a.name.cmp(&b.name)); 95 | } 96 | } 97 | 98 | //------------------------------------------------------------------------------------------------- 99 | // TypeDefListIterator 100 | 101 | /// Iterator for TypeDefList 102 | pub struct McTypeDefListIterator<'a> { 103 | index: usize, 104 | list: &'a McTypeDefList, 105 | } 106 | 107 | impl<'a> McTypeDefListIterator<'_> { 108 | pub fn new(list: &'a McTypeDefList) -> McTypeDefListIterator<'a> { 109 | McTypeDefListIterator { index: 0, list } 110 | } 111 | } 112 | 113 | impl<'a> Iterator for McTypeDefListIterator<'a> { 114 | type Item = &'a McTypeDef; 115 | 116 | fn next(&mut self) -> Option { 117 | let index = self.index; 118 | if index < self.list.0.len() { 119 | self.index += 1; 120 | Some(&self.list.0[index]) 121 | } else { 122 | None 123 | } 124 | } 125 | } 126 | 127 | impl<'a> IntoIterator for &'a McTypeDefList { 128 | type Item = &'a McTypeDef; 129 | type IntoIter = McTypeDefListIterator<'a>; 130 | 131 | fn into_iter(self) -> McTypeDefListIterator<'a> { 132 | McTypeDefListIterator::new(self) 133 | } 134 | } 135 | 136 | // Just pass iter_mut up 137 | impl McTypeDefList { 138 | pub fn iter_mut(&mut self) -> impl Iterator { 139 | self.0.iter_mut() 140 | } 141 | } 142 | 143 | //------------------------------------------------------------------------------------------------- 144 | // McTypeDefField 145 | 146 | #[derive(Debug, Serialize, Deserialize)] 147 | pub struct McTypeDefField { 148 | pub name: McIdentifier, 149 | pub dim_type: McDimType, // Type name and matrix dimensions, recursion here if McValueType::TypeDef 150 | pub mc_support_data: McSupportData, // Metadata for the field 151 | pub offset: u16, // Offset of the field in the struct ABI 152 | } 153 | 154 | impl McTypeDefField { 155 | pub fn new>(field_name: T, dim_type: McDimType, mc_support_data: McSupportData, offset: u16) -> McTypeDefField { 156 | McTypeDefField { 157 | name: field_name.into(), 158 | dim_type, 159 | mc_support_data, 160 | offset, 161 | } 162 | } 163 | 164 | pub fn get_name(&self) -> &'static str { 165 | self.name.as_str() 166 | } 167 | 168 | /// Check if the value type is a typedef and return the typedef name if it is 169 | pub fn get_typedef_name(&self) -> Option<&'static str> { 170 | match self.dim_type.value_type { 171 | McValueType::TypeDef(typedef_name) => Some(typedef_name.as_str()), 172 | _ => None, 173 | } 174 | } 175 | 176 | /// Get the offset of the field in the struct ABI 177 | pub fn get_offset(&self) -> u16 { 178 | self.offset 179 | } 180 | 181 | /// Get type 182 | pub fn get_dim_type(&self) -> &McDimType { 183 | &self.dim_type 184 | } 185 | 186 | /// Get metadata 187 | pub fn get_mc_support_data(&self) -> &McSupportData { 188 | &self.mc_support_data 189 | } 190 | } 191 | 192 | //---------------------------------------------------------------------------------------------- 193 | // McTypeDefFieldList 194 | 195 | #[derive(Debug, Default, Serialize, Deserialize)] 196 | pub struct McTypeDefFieldList(Vec); 197 | 198 | impl McTypeDefFieldList { 199 | pub fn new() -> Self { 200 | McTypeDefFieldList(Vec::with_capacity(8)) 201 | } 202 | 203 | pub fn len(&self) -> usize { 204 | self.0.len() 205 | } 206 | pub fn is_empty(&self) -> bool { 207 | self.0.is_empty() 208 | } 209 | pub fn push(&mut self, object: McTypeDefField) { 210 | self.0.push(object); 211 | } 212 | 213 | pub fn find_typedef_field(&self, name: &str) -> Option<&McTypeDefField> { 214 | self.0.iter().find(|i| i.name == name) 215 | } 216 | } 217 | 218 | //------------------------------------------------------------------------------------------------- 219 | // TypeDefFieldListIterator 220 | 221 | /// Iterator for TypeDefFieldList 222 | pub struct McTypeDefFieldListIterator<'a> { 223 | index: usize, 224 | list: &'a McTypeDefFieldList, 225 | } 226 | 227 | impl<'a> McTypeDefFieldListIterator<'_> { 228 | pub fn new(list: &'a McTypeDefFieldList) -> McTypeDefFieldListIterator<'a> { 229 | McTypeDefFieldListIterator { index: 0, list } 230 | } 231 | } 232 | 233 | impl<'a> Iterator for McTypeDefFieldListIterator<'a> { 234 | type Item = &'a McTypeDefField; 235 | 236 | fn next(&mut self) -> Option { 237 | let index = self.index; 238 | if index < self.list.0.len() { 239 | self.index += 1; 240 | Some(&self.list.0[index]) 241 | } else { 242 | None 243 | } 244 | } 245 | } 246 | 247 | impl<'a> IntoIterator for &'a McTypeDefFieldList { 248 | type Item = &'a McTypeDefField; 249 | type IntoIter = McTypeDefFieldListIterator<'a>; 250 | 251 | fn into_iter(self) -> McTypeDefFieldListIterator<'a> { 252 | McTypeDefFieldListIterator::new(self) 253 | } 254 | } 255 | 256 | // Just pass iter_mut up 257 | impl McTypeDefFieldList { 258 | pub fn iter_mut(&mut self) -> impl Iterator { 259 | self.0.iter_mut() 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/xcp/daq.rs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Module daq 3 | // Data acquisition types 4 | 5 | //----------------------------------------------------------------------------- 6 | // Submodules 7 | 8 | // DAQ event 9 | pub mod daq_event; 10 | -------------------------------------------------------------------------------- /src/xcp/xcplib.rs: -------------------------------------------------------------------------------- 1 | /* automatically generated by rust-bindgen 0.71.1 */ 2 | 3 | unsafe extern "C" { 4 | pub fn XcpInit(); 5 | } 6 | unsafe extern "C" { 7 | pub fn XcpSetEpk(epk: *const ::std::os::raw::c_char); 8 | } 9 | unsafe extern "C" { 10 | pub fn XcpDisconnect(); 11 | } 12 | unsafe extern "C" { 13 | pub fn XcpEventExt(event: u16, base: *const u8) -> u8; 14 | } 15 | unsafe extern "C" { 16 | pub fn XcpEvent(event: u16); 17 | } 18 | unsafe extern "C" { 19 | pub fn XcpSendTerminateSessionEvent(); 20 | } 21 | unsafe extern "C" { 22 | pub fn XcpPrint(str_: *const ::std::os::raw::c_char); 23 | } 24 | unsafe extern "C" { 25 | pub fn XcpSetLogLevel(level: u8); 26 | } 27 | unsafe extern "C" { 28 | pub fn ApplXcpGetClock64() -> u64; 29 | } 30 | unsafe extern "C" { 31 | pub fn ApplXcpRegisterCallbacks( 32 | cb_connect: ::std::option::Option bool>, 33 | cb_prepare_daq: ::std::option::Option u8>, 34 | cb_start_daq: ::std::option::Option u8>, 35 | cb_stop_daq: ::std::option::Option, 36 | cb_freeze_daq: ::std::option::Option u8>, 37 | cb_get_cal_page: ::std::option::Option u8>, 38 | cb_set_cal_page: ::std::option::Option u8>, 39 | cb_freeze_cal: ::std::option::Option u8>, 40 | cb_init_cal: ::std::option::Option u8>, 41 | cb_read: ::std::option::Option u8>, 42 | cb_write: ::std::option::Option u8>, 43 | cb_flush: ::std::option::Option u8>, 44 | ); 45 | } 46 | unsafe extern "C" { 47 | pub fn ApplXcpSetA2lName(name: *const ::std::os::raw::c_char); 48 | } 49 | unsafe extern "C" { 50 | #[doc = " Initialize the XCP on Ethernet server instance.\n @pre User has called XcpInit.\n @param address Address to bind to.\n @param port Port to bind to.\n @param use_tcp Use TCP if true, otherwise UDP.\n @param measurement_queue Optional external memory to place the measurement queue.\n Pass NULL if server should allocate it.\n @param measurement_queue_size Measurement queue size in bytes. Includes the bytes occupied by the queue header.\n @return True on success, otherwise false."] 51 | pub fn XcpEthServerInit(address: *const u8, port: u16, use_tcp: bool, measurement_queue: *mut ::std::os::raw::c_void, measurement_queue_size: u32) -> bool; 52 | } 53 | unsafe extern "C" { 54 | #[doc = " Shutdown the XCP on Ethernet server instance."] 55 | pub fn XcpEthServerShutdown() -> bool; 56 | } 57 | unsafe extern "C" { 58 | #[doc = " Get the XCP on Ethernet server instance status.\n @return True if the server is running, otherwise false."] 59 | pub fn XcpEthServerStatus() -> bool; 60 | } 61 | unsafe extern "C" { 62 | pub fn XcpEthTlGetInfo(isTCP: *mut bool, mac: *mut u8, addr: *mut u8, port: *mut u16); 63 | } 64 | -------------------------------------------------------------------------------- /xcp_client/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /xcp_client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xcp_client" 3 | version = "0.3.0" 4 | edition = "2024" 5 | resolver = "2" 6 | authors = ["RainerZ"] 7 | description = "Rust implementation of ASAM XCP" 8 | readme = "README.md" 9 | keywords = ["XCP","ASAM","CANape","A2L"] 10 | license = "MIT" 11 | homepage = "https://vector.com" 12 | repository = "https://github.com/vectorgrp/xcp-lite" 13 | categories = ["measurement and calibration"] 14 | 15 | # more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | clap = { version = "4.5.9", features = ["derive"] } 19 | log = "0.4.21" 20 | env_logger = "0.11.3" 21 | parking_lot = "0.12.3" 22 | tokio = { version = "1.37.0", features = ["full"] } 23 | bytes = "1.6.0" 24 | byteorder = "1.5.0" 25 | regex = "1.11.1" 26 | lazy_static = "1.4" 27 | 28 | a2lfile = { version="3.0.0" } 29 | xcp_lite = { path = "../", features = ["a2l_reader"] } 30 | 31 | [build-dependencies] 32 | #cc = "1.0" 33 | #build-info-build = "0.0.40" 34 | #bindgen = "0.71.1" 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /xcp_client/README.md: -------------------------------------------------------------------------------- 1 | # xcp_client 2 | XCP client implementation in Rust 3 | 4 | Used for integration testing xcp-lite. 5 | Partial XCP implementation hard-coded for xcp-lite testing. 6 | Using tokio and a2lfile. 7 | 8 | xcp-lite-rdm % cargo r --example xcp_client -- -h 9 | 10 | Usage: xcp_client [OPTIONS] 11 | 12 | Options: 13 | -l, --log-level 14 | Log level (Off=0, Error=1, Warn=2, Info=3, Debug=4, Trace=5) [default: 2] 15 | -d, --dest-addr 16 | XCP server address [default: 127.0.0.1:5555] 17 | -p, --port 18 | XCP server port number [default: 5555] 19 | -b, --bind-addr 20 | Bind address, master port number [default: 0.0.0.0:9999] 21 | --print-a2l 22 | Print detailled A2L infos 23 | --list-mea 24 | Lists all measurement variables 25 | --list-cal 26 | Lists all calibration variables 27 | -m, --measurement-list ... 28 | Specifies the variables names for DAQ measurement, 'all' or a list of names separated by space 29 | -a, --a2l-filename 30 | A2L filename, default is upload A2L file 31 | -h, --help 32 | Print help 33 | -V, --version 34 | Print version 35 | 36 | 37 | 38 | ``` rust 39 | 40 | // Create xcp_client 41 | let mut xcp_client = XcpClient::new("127.0.0.1:5555", "0.0.0.0:0"); 42 | 43 | // Connect to the XCP server 44 | let res = xcp_client.connect(DaqDecoder::new(), ServTextDecoder::new()).await?; 45 | 46 | // Upload A2L file or read A2L file 47 | xcp_client.upload_a2l(false).await?; 48 | xcp_client.read_a2l("test.a2l",false)?; 49 | 50 | // Calibration 51 | // Create a calibration object for CalPage1.counter_max 52 | if let Ok(counter_max) = xcp_client.create_calibration_object("CalPage1.counter_max").await 53 | { 54 | // Get current value 55 | let v = xcp_client.get_value_u64(counter_max); 56 | info!("CalPage1.counter_max = {}", v); 57 | 58 | // Set value to 1000 59 | info!("CalPage1.counter_max = {}", v); 60 | xcp_client.set_value_u64(counter_max, 1000).await?; 61 | } 62 | 63 | // Measurement 64 | // Create a measurement for signal counter:u32 65 | xcp_client.init_measurement().await?; 66 | xcp_client.create_measurement_object("counter").await?; 67 | xcp_client.start_measurement().await?; 68 | sleep(Duration::from_secs(1)).await; 69 | xcp_client.stop_measurement().await?; 70 | 71 | // Disconnect 72 | xcp_client.disconnect().await?); 73 | 74 | 75 | ``` -------------------------------------------------------------------------------- /xcp_client/src/lib.rs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Crate xcp_client 3 | // Path: xcp_client/src/lib.rs 4 | // xcp_client is a library crate that provides an XCP on ETH client implementation for integration testing 5 | 6 | // This crate is a library 7 | #![crate_type = "lib"] 8 | // The library crate is named "xcp_client" 9 | #![crate_name = "xcp_client"] 10 | 11 | pub mod xcp_client; 12 | -------------------------------------------------------------------------------- /xcp_idl_generator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xcp_idl_generator" 3 | version = "0.3.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | ctor = "0.2.8" 8 | #ctor = "0.4" 9 | lazy_static = "1.4" 10 | regex = "1.10.6" 11 | xcp_idl_generator_derive = { path = "./xcp_idl_generator_derive/" } 12 | 13 | [lib] 14 | path = "src/lib.rs" 15 | -------------------------------------------------------------------------------- /xcp_idl_generator/src/domain.rs: -------------------------------------------------------------------------------- 1 | pub const VECTOR_NAMESPACE: &'static &str = &"Vector"; 2 | -------------------------------------------------------------------------------- /xcp_idl_generator/src/gen/collection/cdr.rs: -------------------------------------------------------------------------------- 1 | use crate::STRUCTS; 2 | use crate::domain::VECTOR_NAMESPACE; 3 | use crate::r#gen::Generator; 4 | use crate::r#gen::TypeMapping; 5 | use crate::types::Struct; 6 | use regex::Regex; 7 | use std::sync::Once; 8 | 9 | //TODO Move to common package 10 | fn extract_types(input: &str) -> Vec<&str> { 11 | let re = Regex::new(r"[^\w]+").unwrap(); 12 | re.split(input).filter(|s| !s.is_empty()).collect() 13 | } 14 | 15 | pub struct CdrGenerator; 16 | 17 | impl CdrGenerator { 18 | #[allow(clippy::new_without_default)] 19 | pub fn new() -> Self { 20 | Self {} 21 | } 22 | 23 | fn translate_fields(&self, input: &Struct) -> String { 24 | input 25 | .fields() 26 | .iter() 27 | .map(|field| { 28 | let mut translated_type = field.value_type().to_string(); 29 | 30 | for (key, value) in self.type_mapping().iter() { 31 | translated_type = translated_type.replace(key, value); 32 | } 33 | 34 | format!("\"{} {};\"", translated_type, field.name()) 35 | }) 36 | .collect::>() 37 | .join("\n") 38 | } 39 | } 40 | 41 | impl Generator for CdrGenerator { 42 | fn generate(&self, input: &Struct) -> String { 43 | let type_name = input.type_name(); 44 | let fields_str = self.translate_fields(input); 45 | 46 | let mut translation = format!( 47 | r#" 48 | /begin ANNOTATION ANNOTATION_LABEL "ObjectDescription" ANNOTATION_ORIGIN "application/dds" 49 | /begin ANNOTATION_TEXT 50 | " " 51 | "{VECTOR_NAMESPACE}::{type_name}" 52 | "" 53 | "module {VECTOR_NAMESPACE} {{" 54 | " struct {type_name} {{" 55 | {fields_str} 56 | " }};" 57 | "}};" 58 | /end ANNOTATION_TEXT 59 | /end ANNOTATION 60 | "# 61 | ); 62 | 63 | let struct_collection = STRUCTS.lock().unwrap(); 64 | 65 | let mut processed: Vec<&str> = Vec::new(); 66 | 67 | for field in input.fields().iter() { 68 | let extracted_type_tree = extract_types(field.value_type()); 69 | 70 | for value_type in extracted_type_tree.iter() { 71 | match self.type_mapping().get(value_type) { 72 | None => { 73 | if processed.contains(value_type) { 74 | continue; 75 | } 76 | 77 | let s_slice: &str = value_type; 78 | let description = struct_collection.get(s_slice).unwrap(); 79 | 80 | let inner_type_name = description.type_name(); 81 | let inner_fields_str = self.translate_fields(description); 82 | 83 | let idl_str = format!( 84 | r#""struct {inner_type_name} {{" 85 | {inner_fields_str} 86 | "}};"# 87 | ); 88 | 89 | let tag = format!("module {VECTOR_NAMESPACE} {{"); 90 | translation = translation.replace(&tag, &format!("module {VECTOR_NAMESPACE} {{\"\n{}", idl_str)); 91 | 92 | processed.push(value_type); 93 | } 94 | Some(_) => { /* Rust primitive -> Ignored */ } 95 | } 96 | } 97 | } 98 | 99 | translation 100 | } 101 | 102 | //TODO Add other type mappings 103 | fn type_mapping(&self) -> &'static TypeMapping { 104 | static mut MAPPING: Option = None; 105 | static INIT: Once = Once::new(); 106 | 107 | // @@@@ UNSAFE - Mutable static, TODO 108 | unsafe { 109 | INIT.call_once(|| { 110 | let mut mapping = TypeMapping::new(); 111 | mapping.insert("u32", "uint32"); 112 | mapping.insert("f32", "float"); 113 | mapping.insert("Vec", "sequence"); 114 | 115 | MAPPING = Some(mapping); 116 | }); 117 | #[allow(static_mut_refs)] 118 | MAPPING.as_ref().unwrap() //TODO Error Handling?? 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /xcp_idl_generator/src/gen/collection/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cdr; 2 | 3 | use super::{Generator, IDL, Struct}; 4 | use cdr::CdrGenerator; 5 | use std::{ 6 | collections::HashMap, 7 | sync::{Arc, Once}, 8 | }; 9 | 10 | pub struct GeneratorCollection(HashMap); 11 | 12 | impl GeneratorCollection { 13 | fn new() -> Self { 14 | let mut map = HashMap::new(); 15 | map.insert(IDL::CDR, create_arc_generator(CdrGenerator::new())); 16 | GeneratorCollection(map) 17 | } 18 | 19 | pub fn generate(idl_type: &IDL, input: &Struct) -> Option { 20 | let instance = GeneratorCollection::instance(); 21 | let generator = instance.get(idl_type).expect("Generator not found for IDL type"); 22 | 23 | Some(generator.generate(input)) 24 | } 25 | 26 | pub fn instance() -> &'static GeneratorCollection { 27 | static mut INSTANCE: Option = None; 28 | static INIT: Once = Once::new(); 29 | 30 | // @@@@ UNSAFE - Mutable static, TODO 31 | unsafe { 32 | INIT.call_once(|| { 33 | INSTANCE = Some(GeneratorCollection::new()); 34 | }); 35 | #[allow(static_mut_refs)] 36 | INSTANCE.as_ref().unwrap() 37 | } 38 | } 39 | 40 | fn get(&self, idl_type: &IDL) -> Option<&ArcGenerator> { 41 | self.0.get(idl_type) 42 | } 43 | } 44 | 45 | type ArcGenerator = Arc; 46 | 47 | fn create_arc_generator(generator: T) -> ArcGenerator 48 | where 49 | T: Generator + Send + Sync + 'static, 50 | { 51 | Arc::new(generator) as ArcGenerator 52 | } 53 | -------------------------------------------------------------------------------- /xcp_idl_generator/src/gen/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::types::*; 2 | use std::collections::HashMap; 3 | 4 | pub mod collection; 5 | 6 | pub trait Generator { 7 | fn generate(&self, input: &Struct) -> String; 8 | fn type_mapping(&self) -> &'static TypeMapping; 9 | } 10 | 11 | #[derive(Debug)] 12 | pub struct TypeMapping(HashMap<&'static str, &'static str>); 13 | 14 | impl TypeMapping { 15 | #[allow(clippy::new_without_default)] 16 | pub fn new() -> Self { 17 | TypeMapping(HashMap::new()) 18 | } 19 | 20 | pub fn insert(&mut self, key: &'static str, value: &'static str) { 21 | self.0.insert(key, value); 22 | } 23 | 24 | pub fn iter(&self) -> impl Iterator + '_ { 25 | //pub fn iter<'a>(&'a self) -> impl Iterator + 'a { 26 | self.0.iter().map(|(k, v)| (*k, *v)) 27 | } 28 | 29 | pub fn get(&self, key: &str) -> Option<&&str> { 30 | self.0.get(key) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /xcp_idl_generator/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod domain; 2 | pub mod r#gen; 3 | pub mod prelude; 4 | pub mod types; 5 | 6 | use std::{collections::HashMap, sync::Mutex}; 7 | 8 | use types::Struct; 9 | 10 | use lazy_static::lazy_static; 11 | 12 | lazy_static! { 13 | pub static ref STRUCTS: Mutex> = Mutex::new(HashMap::new()); 14 | } 15 | 16 | pub trait IdlGenerator { 17 | fn description(&self) -> &'static Struct; 18 | } 19 | -------------------------------------------------------------------------------- /xcp_idl_generator/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::r#gen::Generator; 2 | pub use crate::r#gen::collection::GeneratorCollection; 3 | pub use crate::types::{Field, FieldList, IDL, Struct}; 4 | 5 | pub use crate::{IdlGenerator, STRUCTS}; 6 | pub use xcp_idl_generator_derive::IdlGenerator; 7 | 8 | pub extern crate ctor; 9 | pub extern crate lazy_static; 10 | 11 | pub use ctor::ctor; 12 | pub use lazy_static::lazy_static; 13 | -------------------------------------------------------------------------------- /xcp_idl_generator/src/types.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct Struct(&'static str, FieldList); 3 | 4 | impl Struct { 5 | pub fn new(name: &'static str, fields: FieldList) -> Self { 6 | Struct(name, fields) 7 | } 8 | 9 | pub fn type_name(&self) -> &str { 10 | self.0 11 | } 12 | 13 | pub fn fields(&self) -> &FieldList { 14 | &self.1 15 | } 16 | } 17 | 18 | #[derive(Debug)] 19 | pub struct FieldList(Vec); 20 | 21 | impl FieldList { 22 | #[allow(clippy::new_without_default)] 23 | pub fn new() -> Self { 24 | Self(Vec::new()) 25 | } 26 | 27 | pub fn with_capacity(capacity: usize) -> Self { 28 | Self(Vec::with_capacity(capacity)) 29 | } 30 | 31 | pub fn push(&mut self, field: Field) { 32 | self.0.push(field); 33 | } 34 | 35 | pub fn iter(&self) -> impl Iterator { 36 | self.0.iter() 37 | } 38 | } 39 | 40 | #[derive(Debug)] 41 | pub struct Field(&'static str, &'static str); 42 | 43 | impl Field { 44 | pub fn new(name: &'static str, field_type: &'static str) -> Self { 45 | Field(name, field_type) 46 | } 47 | 48 | pub fn name(&self) -> &str { 49 | self.0 50 | } 51 | 52 | pub fn value_type(&self) -> &str { 53 | self.1 54 | } 55 | } 56 | #[allow(clippy::upper_case_acronyms)] 57 | #[derive(Eq, Hash, PartialEq)] 58 | pub enum IDL { 59 | CDR, 60 | } 61 | -------------------------------------------------------------------------------- /xcp_idl_generator/xcp_idl_generator_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xcp_idl_generator_derive" 3 | version = "0.3.0" 4 | edition = "2024" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | syn = { version = "1.0", features = ["full"] } 11 | #syn = { version = "2.0", features = ["full"] } 12 | 13 | quote = "1.0" 14 | lazy_static = "1.4" -------------------------------------------------------------------------------- /xcp_idl_generator/xcp_idl_generator_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | 3 | use proc_macro::{Span, TokenStream}; 4 | use quote::{ToTokens, quote}; 5 | use syn::{Data, DeriveInput, Ident, parse_macro_input}; 6 | 7 | #[proc_macro_derive(IdlGenerator)] 8 | pub fn idl_generator_derive(input: TokenStream) -> TokenStream { 9 | let input = parse_macro_input!(input as DeriveInput); 10 | let value_type = &input.ident; 11 | 12 | let generate = match input.data { 13 | Data::Struct(data_struct) => { 14 | let register_function_name = Ident::new(&format!("register_{}", value_type), Span::call_site().into()); 15 | 16 | let field_handlers: Vec<_> = data_struct 17 | .fields 18 | .iter() 19 | .map(|field| { 20 | let field_name = &field.ident.as_ref().unwrap(); 21 | let field_data_type = &field.ty; 22 | 23 | let f_name_str = field_name.to_string(); 24 | let f_type_str = field_data_type.into_token_stream().to_string(); 25 | let f_type_str = f_type_str.replace(" ", ""); 26 | 27 | quote! { 28 | struct_fields.push(Field::new( 29 | #f_name_str, 30 | #f_type_str 31 | )); 32 | } 33 | }) 34 | .collect(); 35 | 36 | quote! { 37 | impl IdlGenerator for #value_type { 38 | fn description(&self) -> &'static Struct { 39 | let structs = STRUCTS.lock().unwrap(); 40 | let struct_ref = structs.get(stringify!(#value_type)).unwrap(); 41 | struct_ref 42 | } 43 | } 44 | 45 | #[ctor::ctor] 46 | #[allow(non_snake_case)] 47 | fn #register_function_name() { 48 | let mut struct_fields = FieldList::new(); 49 | #(#field_handlers)* 50 | 51 | static mut STRUCT_INSTANCE: Option = None; 52 | static mut INITIALIZED: bool = false; 53 | 54 | // @@@@ UNSAFE - Mutable static 55 | unsafe { 56 | // Prevent the user from calling the register function multiple times 57 | if INITIALIZED { 58 | panic!("The register function has already been called."); 59 | } 60 | 61 | STRUCT_INSTANCE = Some(Struct::new(stringify!(#value_type), struct_fields)); 62 | let struct_ref = STRUCT_INSTANCE.as_ref().unwrap(); 63 | STRUCTS.lock().unwrap().insert(stringify!(#value_type), struct_ref); 64 | 65 | INITIALIZED = true; 66 | } 67 | } 68 | } 69 | } 70 | _ => panic!("IdlGenerator macro only supports structs"), 71 | }; 72 | 73 | generate.into() 74 | } 75 | -------------------------------------------------------------------------------- /xcp_type_description/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xcp_type_description" 3 | version = "0.3.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | lazy_static = "1.4" 8 | xcp_type_description_derive = { path = "./xcp_type_description_derive/" } 9 | 10 | [lib] 11 | path = "src/lib.rs" -------------------------------------------------------------------------------- /xcp_type_description/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod prelude; 2 | 3 | use std::vec::IntoIter; 4 | 5 | pub trait XcpTypeDescription { 6 | fn type_description(&self, flat: bool) -> Option { 7 | let _ = flat; 8 | None 9 | } 10 | } 11 | 12 | /// FieldDescriptor contains properties and attributes for a struct field 13 | #[derive(Debug)] 14 | pub struct FieldDescriptor { 15 | // Datatype 16 | name: &'static str, // Identifier of the field 17 | struct_descriptor: Option, // Inner StructDescriptor 18 | value_type: &'static str, // u8, u16, u32, u64, i8, i16, i32, i74, f32, f64, bool, InnerStruct, [InnerStruct; x_dim], [[InnerStruct; x_dim]; _dim] 19 | 20 | // Attributes 21 | classifier: &'static str, // "axis", "characteristic", "measurement" or empty "" 22 | qualifier: &'static str, //"volatile", "readonly" 23 | comment: &'static str, 24 | min: Option, 25 | max: Option, 26 | step: Option, 27 | factor: f64, 28 | offset: f64, 29 | unit: &'static str, 30 | x_dim: u16, 31 | y_dim: u16, 32 | x_axis_ref: &'static str, 33 | y_axis_ref: &'static str, 34 | addr_offset: u16, 35 | } 36 | 37 | impl FieldDescriptor { 38 | #[allow(clippy::too_many_arguments)] 39 | pub fn new( 40 | name: &'static str, 41 | struct_descriptor: Option, 42 | value_type: &'static str, 43 | classifier: &'static str, // "axis", "characteristic", "measurement" or empty "" 44 | qualifier: &'static str, //"volatile", "readonly" 45 | comment: &'static str, 46 | min: Option, 47 | max: Option, 48 | step: Option, 49 | factor: f64, 50 | offset: f64, 51 | unit: &'static str, 52 | x_dim: u16, 53 | y_dim: u16, 54 | x_axis_ref: &'static str, 55 | y_axis_ref: &'static str, 56 | addr_offset: u16, 57 | ) -> Self { 58 | FieldDescriptor { 59 | name, 60 | struct_descriptor, 61 | value_type, 62 | classifier, 63 | qualifier, 64 | comment, 65 | min, 66 | max, 67 | step, 68 | factor, 69 | offset, 70 | unit, 71 | x_dim, 72 | y_dim, 73 | x_axis_ref, 74 | y_axis_ref, 75 | addr_offset, 76 | } 77 | } 78 | 79 | pub fn name(&self) -> &'static str { 80 | self.name 81 | } 82 | pub fn struct_descriptor(&self) -> Option<&StructDescriptor> { 83 | self.struct_descriptor.as_ref() 84 | } 85 | 86 | pub fn value_type(&self) -> &'static str { 87 | self.value_type 88 | } 89 | 90 | pub fn comment(&self) -> &'static str { 91 | self.comment 92 | } 93 | 94 | pub fn min(&self) -> Option { 95 | self.min 96 | } 97 | pub fn max(&self) -> Option { 98 | self.max 99 | } 100 | pub fn step(&self) -> Option { 101 | self.step 102 | } 103 | pub fn factor(&self) -> f64 { 104 | self.factor 105 | } 106 | pub fn offset(&self) -> f64 { 107 | self.offset 108 | } 109 | pub fn unit(&self) -> &'static str { 110 | self.unit 111 | } 112 | 113 | pub fn x_dim(&self) -> u16 { 114 | if self.x_dim == 0 { 1 } else { self.x_dim } 115 | } 116 | pub fn y_dim(&self) -> u16 { 117 | if self.y_dim == 0 { 1 } else { self.y_dim } 118 | } 119 | 120 | pub fn x_axis_ref(&self) -> Option<&'static str> { 121 | if self.is_axis() || self.x_axis_ref.is_empty() { None } else { Some(self.x_axis_ref) } 122 | } 123 | pub fn y_axis_ref(&self) -> Option<&'static str> { 124 | if self.is_axis() || self.y_axis_ref.is_empty() { None } else { Some(self.y_axis_ref) } 125 | } 126 | 127 | pub fn is_measurement(&self) -> bool { 128 | self.classifier == "measurement" 129 | } 130 | pub fn is_characteristic(&self) -> bool { 131 | self.classifier == "characteristic" 132 | } 133 | pub fn is_axis(&self) -> bool { 134 | self.classifier == "axis" 135 | } 136 | pub fn is_volatile(&self) -> bool { 137 | self.qualifier == "volatile" 138 | } 139 | pub fn is_readonly(&self) -> bool { 140 | self.qualifier == "readonly" 141 | } 142 | pub fn addr_offset(&self) -> u16 { 143 | self.addr_offset 144 | } 145 | 146 | pub fn set_addr_offset(&mut self, offset: u16) { 147 | self.addr_offset = offset; 148 | } 149 | 150 | pub fn set_name(&mut self, name: &'static str) { 151 | self.name = name; 152 | } 153 | } 154 | 155 | // The XcpTypeDescription trait implementation for Rust primitives is 156 | // simply a blanket (empty) trait implementation. This macro is used 157 | // to automatically generate the implementation for Rust primitives 158 | macro_rules! impl_xcp_type_description_for_primitive { 159 | ($($t:ty),*) => { 160 | $( 161 | impl XcpTypeDescription for $t {} 162 | )* 163 | }; 164 | } 165 | 166 | impl_xcp_type_description_for_primitive!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize, f32, f64, bool, char); 167 | 168 | // The implementation of the XcpTypeDescription trait for 169 | // arrays is also a blanket (empty) trait implementation 170 | impl XcpTypeDescription for [T; N] {} 171 | 172 | /// StructDescriptor is a vec of FieldDescriptor 173 | /// It it created with the XcpTypeDescription proc-macro trait 174 | #[derive(Debug, Default)] 175 | pub struct StructDescriptor { 176 | name: &'static str, 177 | size: usize, 178 | fields: Vec, 179 | } 180 | 181 | impl StructDescriptor { 182 | pub fn new(name: &'static str, size: usize) -> Self { 183 | StructDescriptor { name, size, fields: Vec::new() } 184 | } 185 | 186 | pub fn name(&self) -> &'static str { 187 | self.name 188 | } 189 | 190 | pub fn size(&self) -> usize { 191 | self.size 192 | } 193 | 194 | pub fn push(&mut self, field_descriptor: FieldDescriptor) { 195 | self.fields.push(field_descriptor); 196 | } 197 | 198 | pub fn sort(&mut self) { 199 | self.fields.sort_by(|a, b| a.name.cmp(b.name)); 200 | } 201 | 202 | pub fn iter(&self) -> std::slice::Iter { 203 | self.fields.iter() 204 | } 205 | } 206 | 207 | impl IntoIterator for StructDescriptor { 208 | type Item = FieldDescriptor; 209 | type IntoIter = IntoIter; 210 | 211 | fn into_iter(self) -> Self::IntoIter { 212 | self.fields.into_iter() 213 | } 214 | } 215 | 216 | impl Extend for StructDescriptor { 217 | fn extend>(&mut self, iter: T) { 218 | self.fields.extend(iter); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /xcp_type_description/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::{FieldDescriptor, StructDescriptor, XcpTypeDescription}; 2 | pub use xcp_type_description_derive::XcpTypeDescription; 3 | -------------------------------------------------------------------------------- /xcp_type_description/xcp_type_description_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xcp_type_description_derive" 3 | version = "0.3.0" 4 | edition = "2024" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | syn = { version = "1.0", features = ["full"] } 11 | #syn = { version = "2.0", features = ["full"] } 12 | quote = "1.0" 13 | impls = "1.0.3" 14 | proc-macro2 = "1.0" -------------------------------------------------------------------------------- /xcp_type_description/xcp_type_description_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | 3 | mod utils; 4 | 5 | use proc_macro::TokenStream; 6 | use quote::quote; 7 | use syn::{Data, DeriveInput, Expr, parse_macro_input}; 8 | 9 | use utils::*; 10 | 11 | // proc macro XcpTypeDescription 12 | // With attributes measurement, characteristic, axis 13 | // Example: 14 | // #[measurement(min = "0", max = "255")] 15 | #[proc_macro_derive(XcpTypeDescription, attributes(measurement, characteristic, axis))] 16 | pub fn xcp_type_description_derive(input: TokenStream) -> TokenStream { 17 | let input = parse_macro_input!(input as DeriveInput); 18 | let value_type = &input.ident; 19 | let generate = match input.data { 20 | Data::Struct(data_struct) => generate_type_description_impl(data_struct, value_type), 21 | _ => panic!("XcpTypeDescription macro only supports structs"), 22 | }; 23 | generate.into() 24 | } 25 | 26 | fn generate_type_description_impl(data_struct: syn::DataStruct, value_type: &syn::Ident) -> proc_macro2::TokenStream { 27 | let field_handlers = data_struct.fields.iter().map(|field| { 28 | let field_name = &field.ident; // Field identifier 29 | let field_data_type = &field.ty; // Field type 30 | let (x_dim, y_dim) = dimensions(field_data_type); //Dimension of type is array 31 | 32 | // Field attributes 33 | // #[axis/characteristic/measurement(...)] attr = access_type, comment, min, max, step, factor, offset, unit, x_axis, y_axis 34 | let field_attributes = &field.attrs; 35 | let (classifier, qualifier, comment, min, max, step, factor, offset, unit, x_axis_ref, y_axis_ref) = parse_field_attributes(field_attributes, field_data_type); 36 | let classifier_str = classifier.to_str(); // "characteristic" or "axis" or "undefined" from field attribute 37 | let min_token = syn::parse_str::(format!("{:?}", min).as_str()).unwrap(); 38 | let max_token = syn::parse_str::(format!("{:?}", max).as_str()).unwrap(); 39 | let step_token = syn::parse_str::(format!("{:?}", step).as_str()).unwrap(); 40 | 41 | quote! { 42 | // Offset is the address of the field relative to the address of the struct 43 | let addr_offset = std::mem::offset_of!(#value_type, #field_name) as u16; 44 | 45 | // Check if the type of the field implements the XcpTypeDescription trait 46 | // If this is the case, the type_description is a nested struct and its name must 47 | // be prefixed by the name of the parent. Consider the following: 48 | // struct Child { id: u32 } 49 | // struct Parent { child : Child } -> the name of Child.id type_description should be Parent.Child.id 50 | if let Some(inner_type_description) = <#field_data_type as XcpTypeDescription>::type_description(&self.#field_name,flat) { 51 | if flat { 52 | type_description.extend(inner_type_description.into_iter().map(|mut inner| { 53 | let mangled_name = Box::new(format!("{}.{}", stringify!(#field_name), inner.name())); 54 | inner.set_name(Box::leak(mangled_name).as_str()); // Mangle names, leak the string, will be forever in the registry 55 | inner.set_addr_offset(addr_offset + inner.addr_offset()); // Add offsets 56 | inner 57 | })); 58 | } else { 59 | type_description.push(FieldDescriptor::new( 60 | stringify!(#field_name), // Field identifier 61 | Some(inner_type_description), // Inner StructDescriptor 62 | stringify!(#field_data_type), 63 | #classifier_str, // "characteristic", "measurement" or "axis" or "" from field attribute 64 | #qualifier, 65 | #comment, 66 | #min_token, 67 | #max_token, 68 | #step_token, 69 | #factor, 70 | #offset, 71 | #unit, 72 | #x_dim, 73 | #y_dim, 74 | #x_axis_ref, 75 | #y_axis_ref, 76 | addr_offset, 77 | )); 78 | } 79 | } 80 | // If the type does not implement the XcpTypeDescription trait, we can simply create a new FieldDescriptor from it 81 | else { 82 | //info!("Push type description for field {}: field_data_type={}[{}][{}]", stringify!(#field_name),stringify!(#field_data_type),#y_dim,#x_dim); 83 | type_description.push(FieldDescriptor::new( 84 | stringify!(#field_name), // Field identifier 85 | //format!("{}.{}", stringify!(#value_type), stringify!(#field_name)), 86 | None, 87 | stringify!(#field_data_type), 88 | #classifier_str, // "characteristic", "measurement" or "axis" or "undefined" from field attribute 89 | #qualifier, 90 | #comment, 91 | #min_token, 92 | #max_token, 93 | #step_token, 94 | #factor, 95 | #offset, 96 | #unit, 97 | #x_dim, 98 | #y_dim, 99 | #x_axis_ref, 100 | #y_axis_ref, 101 | addr_offset, 102 | )); 103 | } 104 | } 105 | }); 106 | 107 | quote! { 108 | impl XcpTypeDescription for #value_type { 109 | fn type_description(&self, flat: bool) -> Option { 110 | let mut type_description = StructDescriptor::new(stringify!(#value_type),std::mem::size_of::<#value_type>()); 111 | #(#field_handlers)* 112 | Some(type_description) 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /xcp_type_description/xcp_type_description_derive/src/utils.rs: -------------------------------------------------------------------------------- 1 | use syn::{Attribute, Lit, Meta, NestedMeta, Type, TypeArray}; 2 | 3 | #[derive(PartialEq)] 4 | pub enum FieldAttribute { 5 | Undefined, 6 | Measurement, 7 | Characteristic, 8 | Axis, 9 | } 10 | 11 | impl FieldAttribute { 12 | pub fn to_str(&self) -> &'static str { 13 | match *self { 14 | FieldAttribute::Measurement => "measurement", 15 | FieldAttribute::Axis => "axis", 16 | FieldAttribute::Characteristic => "characteristic", 17 | 18 | FieldAttribute::Undefined => "undefined", 19 | } 20 | } 21 | } 22 | 23 | #[allow(clippy::type_complexity)] 24 | pub fn parse_field_attributes( 25 | attributes: &Vec, 26 | _field_type: &Type, 27 | ) -> (FieldAttribute, String, String, Option, Option, Option, f64, f64, String, String, String) { 28 | // attribute 29 | let mut field_attribute: FieldAttribute = FieldAttribute::Undefined; // characteristic, axis, measurement 30 | 31 | // key-value pairs 32 | let mut qualifier = String::new(); 33 | let mut comment = String::new(); 34 | let mut factor: Option = Some(1.0); 35 | let mut offset: Option = Some(0.0); 36 | let mut min: Option = None; 37 | let mut max: Option = None; 38 | let mut step: Option = None; 39 | let mut unit = String::new(); 40 | let mut x_axis = String::new(); 41 | let mut y_axis = String::new(); 42 | 43 | for attribute in attributes { 44 | // 45 | // Attributes not prefixed with characteristic, axis or typedef 46 | // are accepted but ignored as they likely belong to different derive macros 47 | if attribute.path.is_ident("characteristic") { 48 | field_attribute = FieldAttribute::Characteristic; 49 | } else if attribute.path.is_ident("axis") { 50 | field_attribute = FieldAttribute::Axis; 51 | } else if attribute.path.is_ident("measurement") { 52 | field_attribute = FieldAttribute::Measurement; 53 | } else { 54 | continue; 55 | } 56 | 57 | let meta_list = match attribute.parse_meta() { 58 | Ok(Meta::List(list)) => list, 59 | _ => panic!("Expected a list of attributes for type_description"), 60 | }; 61 | 62 | for nested in meta_list.nested { 63 | let name_value = match nested { 64 | NestedMeta::Meta(Meta::NameValue(nv)) => nv, 65 | _ => panic!("Expected name-value pairs in type_description"), 66 | }; 67 | 68 | let key = name_value.path.get_ident().unwrap_or_else(|| panic!("Expected identifier in type_description")).to_string(); 69 | 70 | //TODO Figure out how to handle with Num after changing min,max,unit to range 71 | let value = match &name_value.lit { 72 | Lit::Str(s) => s.value(), 73 | _ => panic!("Expected string literal for key: {} in type_description", key), 74 | }; 75 | 76 | match key.as_str() { 77 | "qualifier" => parse_str(&value, &mut qualifier), 78 | "comment" => parse_str(&value, &mut comment), 79 | "factor" => parse_f64(&value, &mut factor), 80 | "offset" => parse_f64(&value, &mut offset), 81 | "min" => parse_f64(&value, &mut min), 82 | "max" => parse_f64(&value, &mut max), 83 | "step" => parse_f64(&value, &mut step), 84 | "unit" => parse_str(&value, &mut unit), 85 | "x_axis" | "axis" => { 86 | if field_attribute != FieldAttribute::Axis { 87 | parse_str(&value, &mut x_axis) 88 | } 89 | } 90 | "y_axis" => { 91 | if field_attribute != FieldAttribute::Axis { 92 | parse_str(&value, &mut y_axis) 93 | } 94 | } 95 | _ => panic!("Unsupported type description attribute item: {}", key), 96 | } 97 | } 98 | } 99 | 100 | (field_attribute, qualifier, comment, min, max, step, factor.unwrap(), offset.unwrap(), unit, x_axis, y_axis) 101 | } 102 | 103 | pub fn dimensions(ty: &Type) -> (u16, u16) { 104 | match ty { 105 | Type::Array(TypeArray { elem, len, .. }) => { 106 | let length = match len { 107 | syn::Expr::Lit(expr_lit) => { 108 | if let Lit::Int(lit_int) = &expr_lit.lit { 109 | lit_int.base10_parse::().unwrap() 110 | } else { 111 | panic!("Expected an integer literal for array length"); 112 | } 113 | } 114 | _ => panic!("Expected an integer literal for array length"), 115 | }; 116 | 117 | let (inner_x, inner_y) = dimensions(elem); 118 | 119 | if inner_x == 0 && inner_y == 0 { 120 | (length.try_into().unwrap(), 0) 121 | } else if inner_y == 0 { 122 | (inner_x, length.try_into().unwrap()) 123 | } else { 124 | // @@@@ TODO ???? 125 | (inner_x, inner_y) 126 | } 127 | } 128 | _ => (0, 0), 129 | } 130 | } 131 | 132 | #[inline] 133 | fn parse_str(attribute: &str, string: &mut String) { 134 | *string = attribute.to_string(); 135 | } 136 | 137 | #[inline] 138 | fn parse_f64(attribute: &str, val: &mut Option) { 139 | *val = Some(attribute.parse::().expect("Failed to parse attribute text as f64")); 140 | } 141 | -------------------------------------------------------------------------------- /xcplib/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | IndentWidth: 4 4 | TabWidth: 4 5 | UseTab: Never 6 | ColumnLimit: 180 7 | 8 | # NOTE: Cpp is used for both C and C++ 9 | Language: Cpp 10 | ... 11 | 12 | -------------------------------------------------------------------------------- /xcplib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | 3 | /C_Demo.a2l 4 | /hello_xcp.a2l 5 | -------------------------------------------------------------------------------- /xcplib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # c_demo 2 | 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | project(xcplib VERSION 6.0 LANGUAGES C) 6 | 7 | 8 | 9 | if(CMAKE_SIZEOF_VOID_P EQUAL 8) 10 | message("Building for 64-bit platform") 11 | set(PLATFORM_64BIT TRUE) 12 | else() 13 | message("Building for 32-bit platform") 14 | set(PLATFORM_32BIT TRUE) 15 | endif() 16 | 17 | 18 | if(WIN32) 19 | message("Building for WINDOWS") 20 | set(WINDOWS FALSE) # Build for Windows 21 | elseif(APPLE) 22 | message("Building for APPLE") 23 | set(MACOS TRUE) # Build for macOS Darwin ARM 24 | elseif(UNIX) 25 | message("Building for UNIX") 26 | set(LINUX FALSE) # Build for Linus x64 27 | endif() 28 | 29 | 30 | 31 | 32 | set(CMAKE_C_COMPILER "gcc") 33 | set(CMAKE_CXX_COMPILER "g++") 34 | set(CMAKE_CXX_STANDARD 11) 35 | set(CMAKE_CXX_STANDARD_REQUIRED True) 36 | 37 | 38 | 39 | # xcplib 40 | if(PLATFORM_64BIT) 41 | set(xcplib_SOURCES ./src/xcpAppl.c ./src/xcpLite.c ./src/xcpEthServer.c ./src/xcpEthTl.c ./src/xcpQueue64.c ./src/a2l.c ./src/platform.c ) 42 | else() 43 | set(xcplib_SOURCES ./src/xcpAppl.c ./src/xcpLite.c ./src/xcpEthServer.c ./src/xcpEthTl.c ./src/xcpQueue32.c ./src/a2l.c ./src/platform.c ) 44 | endif() 45 | 46 | 47 | set_source_files_properties(${xcplib_SOURCES} PROPERTIES LANGUAGE C) 48 | add_library(xcplib ${xcplib_SOURCES}) 49 | target_include_directories(xcplib PUBLIC "${PROJECT_SOURCE_DIR}/src" ) 50 | target_compile_options(xcplib 51 | PRIVATE 52 | -Wall 53 | -Wextra 54 | -Wconversion 55 | -Werror 56 | -pedantic 57 | -Wshadow 58 | ) 59 | 60 | # Example hello_xcp 61 | set(hello_xcp_SOURCES examples/hello_xcp/src/main.c ${xcplib_SOURCES} ) 62 | add_executable(hello_xcp ${hello_xcp_SOURCES}) 63 | target_include_directories(hello_xcp PUBLIC "${PROJECT_SOURCE_DIR}/src" ) 64 | target_link_libraries( hello_xcp PRIVATE xcplib) 65 | 66 | # Example c_demo 67 | set(c_demo_SOURCES examples/c_demo/src/main.c ${xcplib_SOURCES} ) 68 | add_executable(c_demo ${c_demo_SOURCES}) 69 | target_include_directories(c_demo PUBLIC "${PROJECT_SOURCE_DIR}/src" ) 70 | target_link_libraries(c_demo PRIVATE xcplib) 71 | 72 | if (WINDOWS) 73 | 74 | else () 75 | 76 | #set(THREADS_PREFER_PTHREAD_FLAG ON) 77 | #find_package(Threads REQUIRED) 78 | #target_link_libraries(c_demo PRIVATE Threads::Threads) 79 | 80 | #target_link_libraries(c_demo PRIVATE m) 81 | 82 | set_target_properties(c_demo PROPERTIES SUFFIX ".out") 83 | set_target_properties(hello_xcp PROPERTIES SUFFIX ".out") 84 | 85 | endif () 86 | -------------------------------------------------------------------------------- /xcplib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xcplib" 3 | edition = "2024" 4 | version.workspace = true 5 | publish = false # do not allow to publishing to cargo 6 | 7 | # allow generated C code to not use camel case 8 | [lints.rust] 9 | non_camel_case_types = "allow" 10 | 11 | 12 | [build-dependencies] 13 | cc = "1.0" 14 | #build-info-build = "0.0.40" 15 | bindgen = "0.71.1" 16 | -------------------------------------------------------------------------------- /xcplib/README.md: -------------------------------------------------------------------------------- 1 | 2 | # XCPlite V1.x.x 3 | 4 | XCPlite is a lightweight pure C implementation of the ASAM XCP V1.4 standard protocol for measurement and calibration of electronic control units. 5 | It supports XCP on TCP or UDP with jumboframes. 6 | Runs on 64 Bit platforms with POSIX based (LINUX, MACOS) or Windows Operating Systems. 7 | The A2L measurement and calibration object database is generated during runtime and uploaded by the XCP client on connect. 8 | 9 | XCPlite is provided to test and demonstrate calibration tools such as CANape or any other XCP client implementation. 10 | It may serve as a base for individually customized XCP implementations on Microprocessors. 11 | 12 | XCPlite is used as a C library for the implementation of XCP for rust in: 13 | 14 | 15 | New to XCP? 16 | Checkout the Vector XCP Book: 17 | 18 | 19 | Visit the Virtual VectorAcedemy for an E-Learning on XCP: 20 | 21 | 22 | ## Features 23 | 24 | - Supports XCP on TCP or UDP with jumbo frames. 25 | - Thread safe, minimal thread lock and single copy event driven, timestamped high performance consistent data acquisition. 26 | - Runtime A2L database file generation and upload. 27 | - User friendly code instrumentation to create calibration parameter segments, measurement variables and A2L metadata descriptions. 28 | - Measurement of global or local stack variables. 29 | - Thread safe, lock-free and wait-free ECU access to calibration data. 30 | - Calibration page switching and consistent calibration. 31 | - Can be build as a library. 32 | - Used (as FFI library) in the rust xcp-lite version. 33 | 34 | A list of restrictions compared to Vectors free XCPbasic or commercial XCPprof may be found in the source file xcpLite.c. 35 | XCPbasic is an optimized implementation for smaller Microcontrollers and with CAN as Transport-Layer. 36 | XCPprof is a product in Vectors AUTOSAR MICROSAR and CANbedded product portfolio. 37 | 38 | ## Examples 39 | 40 | hello_xcp: 41 | Getting started with a simple demo in C with minimum code and features. 42 | Shows the basics how to integrate XCP in existing applications. 43 | 44 | c_demo: 45 | Shows more complex data objects (structs, arrays), calibration objects (axis, maps and curves). 46 | Consistent calibration changes and measurement. 47 | Calibration page switching and EPK version check. 48 | -------------------------------------------------------------------------------- /xcplib/build.sh: -------------------------------------------------------------------------------- 1 | 2 | cmake -DCMAKE_BUILD_TYPE=Debug -S . -B build 3 | cd build 4 | make 5 | cd .. 6 | 7 | -------------------------------------------------------------------------------- /xcplib/examples/c_demo/CANape/CANape.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectorgrp/xcp-lite/f56fce9a54524a4dc8adf279fbdc6c0165b615f2/xcplib/examples/c_demo/CANape/CANape.ini -------------------------------------------------------------------------------- /xcplib/examples/c_demo/CANape/xcp_demo.cna: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectorgrp/xcp-lite/f56fce9a54524a4dc8adf279fbdc6c0165b615f2/xcplib/examples/c_demo/CANape/xcp_demo.cna -------------------------------------------------------------------------------- /xcplib/examples/c_demo/CANape/xcp_demo_autodetect.a2l: -------------------------------------------------------------------------------- 1 | ASAP2_VERSION 1 71 2 | /begin PROJECT C_Demo "" 3 | 4 | /begin HEADER "" VERSION "1.0" PROJECT_NO VECTOR /end HEADER 5 | 6 | /begin MODULE C_Demo "" 7 | 8 | /include "XCP_104.aml" 9 | 10 | /begin MOD_COMMON "" 11 | BYTE_ORDER MSB_LAST 12 | ALIGNMENT_BYTE 1 13 | ALIGNMENT_WORD 1 14 | ALIGNMENT_LONG 1 15 | ALIGNMENT_FLOAT16_IEEE 1 16 | ALIGNMENT_FLOAT32_IEEE 1 17 | ALIGNMENT_FLOAT64_IEEE 1 18 | ALIGNMENT_INT64 1 19 | /end MOD_COMMON 20 | 21 | /begin RECORD_LAYOUT F64 FNC_VALUES 1 FLOAT64_IEEE ROW_DIR DIRECT /end RECORD_LAYOUT 22 | /begin TYPEDEF_MEASUREMENT M_F64 "" FLOAT64_IEEE NO_COMPU_METHOD 0 0 -1E12 1E12 /end TYPEDEF_MEASUREMENT 23 | /begin TYPEDEF_CHARACTERISTIC C_F64 "" VALUE F64 0 NO_COMPU_METHOD -1E12 1E12 /end TYPEDEF_CHARACTERISTIC 24 | /begin RECORD_LAYOUT F32 FNC_VALUES 1 FLOAT32_IEEE ROW_DIR DIRECT /end RECORD_LAYOUT 25 | /begin TYPEDEF_MEASUREMENT M_F32 "" FLOAT32_IEEE NO_COMPU_METHOD 0 0 -1E12 1E12 /end TYPEDEF_MEASUREMENT 26 | /begin TYPEDEF_CHARACTERISTIC C_F32 "" VALUE F32 0 NO_COMPU_METHOD -1E12 1E12 /end TYPEDEF_CHARACTERISTIC 27 | /begin RECORD_LAYOUT I64 FNC_VALUES 1 A_INT64 ROW_DIR DIRECT /end RECORD_LAYOUT 28 | /begin TYPEDEF_MEASUREMENT M_I64 "" A_INT64 NO_COMPU_METHOD 0 0 -1E12 1E12 /end TYPEDEF_MEASUREMENT 29 | /begin TYPEDEF_CHARACTERISTIC C_I64 "" VALUE I64 0 NO_COMPU_METHOD -1E12 1E12 /end TYPEDEF_CHARACTERISTIC 30 | /begin RECORD_LAYOUT I32 FNC_VALUES 1 SLONG ROW_DIR DIRECT /end RECORD_LAYOUT 31 | /begin TYPEDEF_MEASUREMENT M_I32 "" SLONG NO_COMPU_METHOD 0 0 -2147483648 2147483647 /end TYPEDEF_MEASUREMENT 32 | /begin TYPEDEF_CHARACTERISTIC C_I32 "" VALUE I32 0 NO_COMPU_METHOD -2147483648 2147483647 /end TYPEDEF_CHARACTERISTIC 33 | /begin RECORD_LAYOUT I16 FNC_VALUES 1 SWORD ROW_DIR DIRECT /end RECORD_LAYOUT 34 | /begin TYPEDEF_MEASUREMENT M_I16 "" SWORD NO_COMPU_METHOD 0 0 -32768 32767 /end TYPEDEF_MEASUREMENT 35 | /begin TYPEDEF_CHARACTERISTIC C_I16 "" VALUE I16 0 NO_COMPU_METHOD -32768 32767 /end TYPEDEF_CHARACTERISTIC 36 | /begin RECORD_LAYOUT I8 FNC_VALUES 1 SBYTE ROW_DIR DIRECT /end RECORD_LAYOUT 37 | /begin TYPEDEF_MEASUREMENT M_I8 "" SBYTE NO_COMPU_METHOD 0 0 -128 127 /end TYPEDEF_MEASUREMENT 38 | /begin TYPEDEF_CHARACTERISTIC C_I8 "" VALUE I8 0 NO_COMPU_METHOD -128 127 /end TYPEDEF_CHARACTERISTIC 39 | /begin RECORD_LAYOUT U8 FNC_VALUES 1 UBYTE ROW_DIR DIRECT /end RECORD_LAYOUT 40 | /begin TYPEDEF_MEASUREMENT M_U8 "" UBYTE NO_COMPU_METHOD 0 0 0 255 /end TYPEDEF_MEASUREMENT 41 | /begin TYPEDEF_CHARACTERISTIC C_U8 "" VALUE U8 0 NO_COMPU_METHOD 0 255 /end TYPEDEF_CHARACTERISTIC 42 | /begin RECORD_LAYOUT U16 FNC_VALUES 1 UWORD ROW_DIR DIRECT /end RECORD_LAYOUT 43 | /begin TYPEDEF_MEASUREMENT M_U16 "" UWORD NO_COMPU_METHOD 0 0 0 65535 /end TYPEDEF_MEASUREMENT 44 | /begin TYPEDEF_CHARACTERISTIC C_U16 "" VALUE U16 0 NO_COMPU_METHOD 0 65535 /end TYPEDEF_CHARACTERISTIC 45 | /begin RECORD_LAYOUT U32 FNC_VALUES 1 ULONG ROW_DIR DIRECT /end RECORD_LAYOUT 46 | /begin TYPEDEF_MEASUREMENT M_U32 "" ULONG NO_COMPU_METHOD 0 0 0 4294967295 /end TYPEDEF_MEASUREMENT 47 | /begin TYPEDEF_CHARACTERISTIC C_U32 "" VALUE U32 0 NO_COMPU_METHOD 0 4294967295 /end TYPEDEF_CHARACTERISTIC 48 | /begin RECORD_LAYOUT U64 FNC_VALUES 1 A_UINT64 ROW_DIR DIRECT /end RECORD_LAYOUT 49 | /begin TYPEDEF_MEASUREMENT M_U64 "" A_UINT64 NO_COMPU_METHOD 0 0 0 1E12 /end TYPEDEF_MEASUREMENT 50 | /begin TYPEDEF_CHARACTERISTIC C_U64 "" VALUE U64 0 NO_COMPU_METHOD 0 1E12 /end TYPEDEF_CHARACTERISTIC 51 | 52 | /begin CHARACTERISTIC params.counter_max "maximum counter value" VALUE 0x80010000 U16 0 NO_COMPU_METHOD 0 2000 /end CHARACTERISTIC 53 | /begin CHARACTERISTIC params.delay_us "mainloop delay time in ue" VALUE 0x80010004 U32 0 NO_COMPU_METHOD 0 1e+06 PHYS_UNIT "us" /end CHARACTERISTIC 54 | /begin CHARACTERISTIC params.test_byte1 "" VALUE 0x80010008 I8 0 NO_COMPU_METHOD -128 127 /end CHARACTERISTIC 55 | /begin CHARACTERISTIC params.test_byte2 "" VALUE 0x80010009 I8 0 NO_COMPU_METHOD -128 127 /end CHARACTERISTIC 56 | /begin COMPU_METHOD counter_global.Conversion "" LINEAR "%6.3" "counts" COEFFS_LINEAR 1 0 /end COMPU_METHOD 57 | /begin MEASUREMENT counter_global "Measurement variable" UWORD counter_global.Conversion 0 0 0.000000 65535.000000 ECU_ADDRESS 0x1C042 ECU_ADDRESS_EXTENSION 1 PHYS_UNIT "counts" READ_WRITE /end MEASUREMENT 58 | /begin COMPU_METHOD counter.Conversion "" LINEAR "%6.3" "counts" COEFFS_LINEAR 1 0 /end COMPU_METHOD 59 | /begin MEASUREMENT counter "Measurement variable" UWORD counter.Conversion 0 0 0.000000 65535.000000 ECU_ADDRESS 0x2 ECU_ADDRESS_EXTENSION 3 PHYS_UNIT "counts" READ_WRITE /begin IF_DATA XCP /begin DAQ_EVENT FIXED_EVENT_LIST EVENT 0x1 /end DAQ_EVENT /end IF_DATA /end MEASUREMENT 60 | /begin TYPEDEF_STRUCTURE params_t "The calibration parameter struct as measurement typedef" 0xC 61 | /begin STRUCTURE_COMPONENT test_byte1 M_I8 0x8 /end STRUCTURE_COMPONENT 62 | /begin STRUCTURE_COMPONENT test_byte2 M_I8 0x9 /end STRUCTURE_COMPONENT 63 | /begin STRUCTURE_COMPONENT counter_max M_U16 0x0 /end STRUCTURE_COMPONENT 64 | /begin STRUCTURE_COMPONENT delay_us M_U32 0x4 /end STRUCTURE_COMPONENT 65 | /end TYPEDEF_STRUCTURE 66 | /begin INSTANCE params_copy "A copy of the current calibration parameters" params_t 0xFFFFFFE8 ECU_ADDRESS_EXTENSION 3 /begin IF_DATA XCP /begin DAQ_EVENT FIXED_EVENT_LIST EVENT 0x1 /end DAQ_EVENT /end IF_DATA /end INSTANCE 67 | 68 | /begin MOD_PAR "" 69 | EPK "EPK_22:38:21" ADDR_EPK 0x80000000 70 | /begin MEMORY_SEGMENT epk "" DATA FLASH INTERN 0x80000000 12 -1 -1 -1 -1 -1 /end MEMORY_SEGMENT 71 | /begin MEMORY_SEGMENT 72 | params "" DATA FLASH INTERN 0x80010000 0x0000000C -1 -1 -1 -1 -1 73 | /begin IF_DATA XCP 74 | /begin SEGMENT 0x01 0x02 0x00 0x00 0x00 75 | /begin CHECKSUM XCP_ADD_44 MAX_BLOCK_SIZE 0xFFFF EXTERNAL_FUNCTION "" /end CHECKSUM 76 | /begin PAGE 0x01 ECU_ACCESS_DONT_CARE XCP_READ_ACCESS_DONT_CARE XCP_WRITE_ACCESS_NOT_ALLOWED /end PAGE 77 | /begin PAGE 0x00 ECU_ACCESS_DONT_CARE XCP_READ_ACCESS_DONT_CARE XCP_WRITE_ACCESS_DONT_CARE /end PAGE 78 | /end SEGMENT 79 | /end IF_DATA 80 | /end MEMORY_SEGMENT 81 | /end MOD_PAR 82 | 83 | 84 | /begin IF_DATA XCP 85 | /begin PROTOCOL_LAYER 86 | 0x0104 1000 2000 0 0 0 0 0 248 5964 BYTE_ORDER_MSB_LAST ADDRESS_GRANULARITY_BYTE 87 | OPTIONAL_CMD GET_COMM_MODE_INFO 88 | OPTIONAL_CMD GET_ID 89 | OPTIONAL_CMD SET_REQUEST 90 | OPTIONAL_CMD SET_MTA 91 | OPTIONAL_CMD UPLOAD 92 | OPTIONAL_CMD SHORT_UPLOAD 93 | OPTIONAL_CMD DOWNLOAD 94 | OPTIONAL_CMD SHORT_DOWNLOAD 95 | OPTIONAL_CMD GET_CAL_PAGE 96 | OPTIONAL_CMD SET_CAL_PAGE 97 | OPTIONAL_CMD COPY_CAL_PAGE 98 | OPTIONAL_CMD BUILD_CHECKSUM 99 | OPTIONAL_CMD USER_CMD 100 | OPTIONAL_CMD GET_DAQ_RESOLUTION_INFO 101 | OPTIONAL_CMD GET_DAQ_PROCESSOR_INFO 102 | OPTIONAL_CMD FREE_DAQ 103 | OPTIONAL_CMD ALLOC_DAQ 104 | OPTIONAL_CMD ALLOC_ODT 105 | OPTIONAL_CMD ALLOC_ODT_ENTRY 106 | OPTIONAL_CMD SET_DAQ_PTR 107 | OPTIONAL_CMD WRITE_DAQ 108 | OPTIONAL_CMD GET_DAQ_LIST_MODE 109 | OPTIONAL_CMD SET_DAQ_LIST_MODE 110 | OPTIONAL_CMD START_STOP_SYNCH 111 | OPTIONAL_CMD START_STOP_DAQ_LIST 112 | OPTIONAL_CMD GET_DAQ_CLOCK 113 | OPTIONAL_CMD WRITE_DAQ_MULTIPLE 114 | OPTIONAL_CMD TIME_CORRELATION_PROPERTIES 115 | OPTIONAL_LEVEL1_CMD GET_VERSION 116 | /end PROTOCOL_LAYER 117 | /begin DAQ 118 | DYNAMIC 0 2 0 OPTIMISATION_TYPE_DEFAULT ADDRESS_EXTENSION_FREE IDENTIFICATION_FIELD_TYPE_RELATIVE_BYTE GRANULARITY_ODT_ENTRY_SIZE_DAQ_BYTE 0xF8 OVERLOAD_INDICATION_PID 119 | /begin TIMESTAMP_SUPPORTED 120 | 0x01 SIZE_DWORD UNIT_1NS TIMESTAMP_FIXED 121 | /end TIMESTAMP_SUPPORTED 122 | /begin EVENT "mainloop_global" "mainloop_global" 0x0 DAQ 0xFF 0 0 0 CONSISTENCY EVENT /end EVENT 123 | /begin EVENT "mainloop_local" "mainloop_local" 0x1 DAQ 0xFF 0 0 0 CONSISTENCY EVENT /end EVENT 124 | /end DAQ 125 | /begin XCP_ON_UDP_IP 126 | 0x0104 5555 ADDRESS "192.168.8.110" 127 | /end XCP_ON_UDP_IP 128 | /end IF_DATA 129 | 130 | /end MODULE 131 | /end PROJECT 132 | -------------------------------------------------------------------------------- /xcplib/examples/c_demo/src/main.c: -------------------------------------------------------------------------------- 1 | // c_demo xcplib example 2 | 3 | #include // for assert 4 | #include // for bool 5 | #include // for uintxx_t 6 | #include // for printf 7 | #include // for sprintf 8 | 9 | #include "a2l.h" // for A2l generation 10 | #include "platform.h" // for sleepMs, clockGet 11 | #include "xcpEthServer.h" // for XcpEthServerInit, XcpEthServerShutdown, XcpEthServerStatus 12 | #include "xcpLite.h" // for XcpInit, XcpEventExt, XcpCreateEvent, XcpCreateCalSeg, ... 13 | 14 | //----------------------------------------------------------------------------------------------------- 15 | 16 | // XCP parameters 17 | #define OPTION_A2L_PROJECT_NAME "C_Demo" // A2L project name 18 | #define OPTION_A2L_FILE_NAME "C_Demo.a2l" // A2L file name 19 | #define OPTION_USE_TCP false // TCP or UDP 20 | #define OPTION_SERVER_PORT 5555 // Port 21 | // #define OPTION_SERVER_ADDR {0, 0, 0, 0} // Bind addr, 0.0.0.0 = ANY 22 | #define OPTION_SERVER_ADDR {192, 168, 8, 110} // Bind addr, 0.0.0.0 = ANY 23 | #define OPTION_QUEUE_SIZE 1024 * 16 // Size of the measurement queue in bytes, must be a multiple of 8 24 | 25 | //----------------------------------------------------------------------------------------------------- 26 | 27 | // Demo calibration parameters 28 | typedef struct params { 29 | uint16_t counter_max; // Maximum value for the counters 30 | uint32_t delay_us; // Delay in microseconds for the main loop 31 | int8_t test_byte1; 32 | int8_t test_byte2; 33 | } params_t; 34 | 35 | const params_t params = {.counter_max = 1000, .delay_us = 1000, .test_byte1 = -1, .test_byte2 = 1}; 36 | 37 | //----------------------------------------------------------------------------------------------------- 38 | 39 | // Global demo measurement variable 40 | static uint16_t counter_global = 0; 41 | 42 | //----------------------------------------------------------------------------------------------------- 43 | 44 | // Demo main 45 | int main(void) { 46 | 47 | printf("\nXCP on Ethernet C xcplib demo\n"); 48 | 49 | // Set log level (1-error, 2-warning, 3-info, 4-show XCP commands) 50 | XcpSetLogLevel(3); 51 | 52 | // Initialize the XCP singleton, must be called before starting the server 53 | XcpInit(); 54 | 55 | // Initialize the XCP Server 56 | uint8_t addr[4] = OPTION_SERVER_ADDR; 57 | if (!XcpEthServerInit(addr, OPTION_SERVER_PORT, OPTION_USE_TCP, NULL, OPTION_QUEUE_SIZE)) { 58 | return 1; 59 | } 60 | 61 | // Prepare the A2L file 62 | if (!A2lInit(OPTION_A2L_FILE_NAME, OPTION_A2L_PROJECT_NAME, addr, OPTION_SERVER_PORT, OPTION_USE_TCP, true)) { 63 | return 1; 64 | } 65 | 66 | // Create a calibration segment for the calibration parameter struct 67 | // This segment has a working page (RAM) and a reference page (FLASH), it creates a MEMORY_SEGMENT in the A2L file 68 | // It provides safe (thread safe against XCP modifications), lock-free and consistent access to the calibration parameters 69 | // It supports XCP/ECU independant page switching, checksum calculation and reinitialization (copy reference page to working page) 70 | // Note that it can be used in only one ECU thread (in Rust terminology, it is Send, but not Sync) 71 | uint16_t calseg = XcpCreateCalSeg("params", (const uint8_t *)¶ms, sizeof(params)); 72 | 73 | // Register individual calibration parameters in the calibration segment 74 | A2lSetSegAddrMode(calseg, (uint8_t *)¶ms); 75 | A2lCreateParameterWithLimits(params.counter_max, A2L_TYPE_UINT16, "maximum counter value", "", 0, 2000); 76 | A2lCreateParameterWithLimits(params.delay_us, A2L_TYPE_UINT32, "mainloop delay time in ue", "us", 0, 1000000); 77 | A2lCreateParameter(params.test_byte1, A2L_TYPE_INT8, "", ""); 78 | A2lCreateParameter(params.test_byte2, A2L_TYPE_INT8, "", ""); 79 | 80 | // Create a measurement event for global variables 81 | uint16_t event_global = XcpCreateEvent("mainloop_global", 0, 0); 82 | 83 | // Register global measurement variables 84 | A2lSetAbsAddrMode(); // Set absolute addressing 85 | A2lCreatePhysMeasurement(counter_global, A2L_TYPE_UINT16, "Measurement variable", 1.0, 0.0, "counts"); 86 | 87 | // Variables on stack 88 | uint16_t counter = 0; 89 | 90 | // Create a measurement event for local variables 91 | uint16_t event = XcpCreateEvent("mainloop_local", 0, 0); 92 | 93 | // Register measurement variables located on stack 94 | A2lSetRelAddrMode(&event); // Set event relative addressing 95 | A2lCreatePhysMeasurement(counter, A2L_TYPE_UINT16, "Measurement variable", 1.0, 0.0, "counts"); 96 | 97 | // Create a typedef for the calibration parameter struct 98 | A2lTypedefBegin(params_t, "The calibration parameter struct as measurement typedef"); 99 | A2lTypedefComponent(test_byte1, A2L_TYPE_INT8, params); 100 | A2lTypedefComponent(test_byte2, A2L_TYPE_INT8, params); 101 | A2lTypedefComponent(counter_max, A2L_TYPE_UINT16, params); 102 | A2lTypedefComponent(delay_us, A2L_TYPE_UINT32, params); 103 | A2lTypedefEnd(); 104 | 105 | for (;;) { 106 | // Lock the calibration parameter segment for consistent and safe access 107 | // Calibration segment locking is completely lock-free and wait-free (no mutexes, system calls or CAS operations ) 108 | // It returns a pointer to the active page (working or reference) of the calibration segment 109 | params_t *params = (params_t *)XcpLockCalSeg(calseg); 110 | 111 | // Sleep for the specified delay parameter in microseconds 112 | sleepNs(params->delay_us * 1000); 113 | 114 | // Local variable for measurement 115 | counter++; 116 | if (counter > params->counter_max) { 117 | counter = 0; 118 | } 119 | 120 | // Demonstrate calibration consistency 121 | // Insert test_byte1 and test_byte2 into a CANape calibration window, enable indirect calibration, use the update button for the calibration window for consistent 122 | // modification 123 | params_t params_copy = *params; // Test: for measure of the calibration parameters, copy the current calibration parameters to a local variable 124 | A2lCreateTypedefInstance(params_copy, params_t, "A copy of the current calibration parameters"); 125 | if (params->test_byte1 != -params->test_byte2) { 126 | char buffer[64]; 127 | snprintf(buffer, sizeof(buffer), "Inconsistent %u: %d - %d", counter, params->test_byte1, params->test_byte2); 128 | XcpPrint(buffer); 129 | printf("%s\n", buffer); 130 | } 131 | // printf("Counter: %u, Delay: %u us, Test Bytes: %d, %d\n", counter, params->delay_us, params->test_byte1, params->test_byte2); 132 | 133 | // Unlock the calibration segment 134 | XcpUnlockCalSeg(calseg); 135 | 136 | // Global variable 137 | counter_global = counter; 138 | 139 | // Trigger measurement events 140 | XcpEventExt(event, (void *)&event); // For local variables 141 | XcpEvent(event_global); // For global variables 142 | 143 | // Check server status 144 | if (!XcpEthServerStatus()) { 145 | printf("\nXCP Server failed\n"); 146 | break; 147 | } 148 | } // for(;;) 149 | 150 | // Force disconnect the XCP client 151 | XcpDisconnect(); 152 | 153 | // Stop the XCP server 154 | XcpEthServerShutdown(); 155 | 156 | return 0; 157 | } 158 | -------------------------------------------------------------------------------- /xcplib/examples/hello_xcp/CANape/CANape.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectorgrp/xcp-lite/f56fce9a54524a4dc8adf279fbdc6c0165b615f2/xcplib/examples/hello_xcp/CANape/CANape.ini -------------------------------------------------------------------------------- /xcplib/examples/hello_xcp/CANape/xcp_demo.cna: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectorgrp/xcp-lite/f56fce9a54524a4dc8adf279fbdc6c0165b615f2/xcplib/examples/hello_xcp/CANape/xcp_demo.cna -------------------------------------------------------------------------------- /xcplib/examples/hello_xcp/CANape/xcp_demo_autodetect.a2l: -------------------------------------------------------------------------------- 1 | ASAP2_VERSION 1 71 2 | /begin PROJECT hello_xcp "" 3 | 4 | /begin HEADER "" VERSION "1.0" PROJECT_NO VECTOR /end HEADER 5 | 6 | /begin MODULE hello_xcp "" 7 | 8 | /include "XCP_104.aml" 9 | 10 | /begin MOD_COMMON "" 11 | BYTE_ORDER MSB_LAST 12 | ALIGNMENT_BYTE 1 13 | ALIGNMENT_WORD 1 14 | ALIGNMENT_LONG 1 15 | ALIGNMENT_FLOAT16_IEEE 1 16 | ALIGNMENT_FLOAT32_IEEE 1 17 | ALIGNMENT_FLOAT64_IEEE 1 18 | ALIGNMENT_INT64 1 19 | /end MOD_COMMON 20 | 21 | /begin RECORD_LAYOUT F64 FNC_VALUES 1 FLOAT64_IEEE ROW_DIR DIRECT /end RECORD_LAYOUT 22 | /begin TYPEDEF_MEASUREMENT M_F64 "" FLOAT64_IEEE NO_COMPU_METHOD 0 0 -1E12 1E12 /end TYPEDEF_MEASUREMENT 23 | /begin TYPEDEF_CHARACTERISTIC C_F64 "" VALUE F64 0 NO_COMPU_METHOD -1E12 1E12 /end TYPEDEF_CHARACTERISTIC 24 | /begin RECORD_LAYOUT F32 FNC_VALUES 1 FLOAT32_IEEE ROW_DIR DIRECT /end RECORD_LAYOUT 25 | /begin TYPEDEF_MEASUREMENT M_F32 "" FLOAT32_IEEE NO_COMPU_METHOD 0 0 -1E12 1E12 /end TYPEDEF_MEASUREMENT 26 | /begin TYPEDEF_CHARACTERISTIC C_F32 "" VALUE F32 0 NO_COMPU_METHOD -1E12 1E12 /end TYPEDEF_CHARACTERISTIC 27 | /begin RECORD_LAYOUT S64 FNC_VALUES 1 A_INT64 ROW_DIR DIRECT /end RECORD_LAYOUT 28 | /begin TYPEDEF_MEASUREMENT M_S64 "" A_INT64 NO_COMPU_METHOD 0 0 -1E12 1E12 /end TYPEDEF_MEASUREMENT 29 | /begin TYPEDEF_CHARACTERISTIC C_S64 "" VALUE S64 0 NO_COMPU_METHOD -1E12 1E12 /end TYPEDEF_CHARACTERISTIC 30 | /begin RECORD_LAYOUT S32 FNC_VALUES 1 SLONG ROW_DIR DIRECT /end RECORD_LAYOUT 31 | /begin TYPEDEF_MEASUREMENT M_S32 "" SLONG NO_COMPU_METHOD 0 0 -2147483648 2147483647 /end TYPEDEF_MEASUREMENT 32 | /begin TYPEDEF_CHARACTERISTIC C_S32 "" VALUE S32 0 NO_COMPU_METHOD -2147483648 2147483647 /end TYPEDEF_CHARACTERISTIC 33 | /begin RECORD_LAYOUT S16 FNC_VALUES 1 SWORD ROW_DIR DIRECT /end RECORD_LAYOUT 34 | /begin TYPEDEF_MEASUREMENT M_S16 "" SWORD NO_COMPU_METHOD 0 0 -32768 32767 /end TYPEDEF_MEASUREMENT 35 | /begin TYPEDEF_CHARACTERISTIC C_S16 "" VALUE S16 0 NO_COMPU_METHOD -32768 32767 /end TYPEDEF_CHARACTERISTIC 36 | /begin RECORD_LAYOUT S8 FNC_VALUES 1 SBYTE ROW_DIR DIRECT /end RECORD_LAYOUT 37 | /begin TYPEDEF_MEASUREMENT M_S8 "" SBYTE NO_COMPU_METHOD 0 0 -128 127 /end TYPEDEF_MEASUREMENT 38 | /begin TYPEDEF_CHARACTERISTIC C_S8 "" VALUE S8 0 NO_COMPU_METHOD -128 127 /end TYPEDEF_CHARACTERISTIC 39 | /begin RECORD_LAYOUT U8 FNC_VALUES 1 UBYTE ROW_DIR DIRECT /end RECORD_LAYOUT 40 | /begin TYPEDEF_MEASUREMENT M_U8 "" UBYTE NO_COMPU_METHOD 0 0 0 255 /end TYPEDEF_MEASUREMENT 41 | /begin TYPEDEF_CHARACTERISTIC C_U8 "" VALUE U8 0 NO_COMPU_METHOD 0 255 /end TYPEDEF_CHARACTERISTIC 42 | /begin RECORD_LAYOUT U16 FNC_VALUES 1 UWORD ROW_DIR DIRECT /end RECORD_LAYOUT 43 | /begin TYPEDEF_MEASUREMENT M_U16 "" UWORD NO_COMPU_METHOD 0 0 0 65535 /end TYPEDEF_MEASUREMENT 44 | /begin TYPEDEF_CHARACTERISTIC C_U16 "" VALUE U16 0 NO_COMPU_METHOD 0 65535 /end TYPEDEF_CHARACTERISTIC 45 | /begin RECORD_LAYOUT U32 FNC_VALUES 1 ULONG ROW_DIR DIRECT /end RECORD_LAYOUT 46 | /begin TYPEDEF_MEASUREMENT M_U32 "" ULONG NO_COMPU_METHOD 0 0 0 4294967295 /end TYPEDEF_MEASUREMENT 47 | /begin TYPEDEF_CHARACTERISTIC C_U32 "" VALUE U32 0 NO_COMPU_METHOD 0 4294967295 /end TYPEDEF_CHARACTERISTIC 48 | /begin RECORD_LAYOUT U64 FNC_VALUES 1 A_UINT64 ROW_DIR DIRECT /end RECORD_LAYOUT 49 | /begin TYPEDEF_MEASUREMENT M_U64 "" A_UINT64 NO_COMPU_METHOD 0 0 0 1E12 /end TYPEDEF_MEASUREMENT 50 | /begin TYPEDEF_CHARACTERISTIC C_U64 "" VALUE U64 0 NO_COMPU_METHOD 0 1E12 /end TYPEDEF_CHARACTERISTIC 51 | 52 | /begin CHARACTERISTIC params.counter_max "maximum counter value" VALUE 0x80010000 U16 0 NO_COMPU_METHOD 0 2000 /end CHARACTERISTIC 53 | /begin CHARACTERISTIC params.delay_us "mainloop delay time in ue" VALUE 0x80010004 U32 0 NO_COMPU_METHOD 0 1e+06 PHYS_UNIT "us" /end CHARACTERISTIC 54 | /begin COMPU_METHOD counter.Conversion "" LINEAR "%6.3" "counts" COEFFS_LINEAR 1 0 /end COMPU_METHOD 55 | /begin MEASUREMENT counter "Measurement variable" UWORD counter.Conversion 0 0 0.000000 65535.000000 ECU_ADDRESS 0x1C052 ECU_ADDRESS_EXTENSION 1 PHYS_UNIT "counts" READ_WRITE /end MEASUREMENT 56 | 57 | /begin MOD_PAR "" 58 | EPK "V1.0" ADDR_EPK 0x80000000 59 | /begin MEMORY_SEGMENT epk "" DATA FLASH INTERN 0x80000000 8 -1 -1 -1 -1 -1 /end MEMORY_SEGMENT 60 | /begin MEMORY_SEGMENT 61 | params "" DATA FLASH INTERN 0x80010000 0x0000000C -1 -1 -1 -1 -1 62 | /begin IF_DATA XCP 63 | /begin SEGMENT 0x01 0x02 0x00 0x00 0x00 64 | /begin CHECKSUM XCP_ADD_44 MAX_BLOCK_SIZE 0xFFFF EXTERNAL_FUNCTION "" /end CHECKSUM 65 | /begin PAGE 0x01 ECU_ACCESS_DONT_CARE XCP_READ_ACCESS_DONT_CARE XCP_WRITE_ACCESS_NOT_ALLOWED /end PAGE 66 | /begin PAGE 0x00 ECU_ACCESS_DONT_CARE XCP_READ_ACCESS_DONT_CARE XCP_WRITE_ACCESS_DONT_CARE /end PAGE 67 | /end SEGMENT 68 | /end IF_DATA 69 | /end MEMORY_SEGMENT 70 | /end MOD_PAR 71 | 72 | 73 | /begin IF_DATA XCP 74 | /begin PROTOCOL_LAYER 75 | 0x0104 1000 2000 0 0 0 0 0 248 5964 BYTE_ORDER_MSB_LAST ADDRESS_GRANULARITY_BYTE 76 | OPTIONAL_CMD GET_COMM_MODE_INFO 77 | OPTIONAL_CMD GET_ID 78 | OPTIONAL_CMD SET_REQUEST 79 | OPTIONAL_CMD SET_MTA 80 | OPTIONAL_CMD UPLOAD 81 | OPTIONAL_CMD SHORT_UPLOAD 82 | OPTIONAL_CMD DOWNLOAD 83 | OPTIONAL_CMD SHORT_DOWNLOAD 84 | OPTIONAL_CMD GET_CAL_PAGE 85 | OPTIONAL_CMD SET_CAL_PAGE 86 | OPTIONAL_CMD COPY_CAL_PAGE 87 | OPTIONAL_CMD BUILD_CHECKSUM 88 | OPTIONAL_CMD USER_CMD 89 | OPTIONAL_CMD GET_DAQ_RESOLUTION_INFO 90 | OPTIONAL_CMD GET_DAQ_PROCESSOR_INFO 91 | OPTIONAL_CMD FREE_DAQ 92 | OPTIONAL_CMD ALLOC_DAQ 93 | OPTIONAL_CMD ALLOC_ODT 94 | OPTIONAL_CMD ALLOC_ODT_ENTRY 95 | OPTIONAL_CMD SET_DAQ_PTR 96 | OPTIONAL_CMD WRITE_DAQ 97 | OPTIONAL_CMD GET_DAQ_LIST_MODE 98 | OPTIONAL_CMD SET_DAQ_LIST_MODE 99 | OPTIONAL_CMD START_STOP_SYNCH 100 | OPTIONAL_CMD START_STOP_DAQ_LIST 101 | OPTIONAL_CMD GET_DAQ_CLOCK 102 | OPTIONAL_CMD WRITE_DAQ_MULTIPLE 103 | OPTIONAL_CMD TIME_CORRELATION_PROPERTIES 104 | OPTIONAL_LEVEL1_CMD GET_VERSION 105 | /end PROTOCOL_LAYER 106 | /begin DAQ 107 | DYNAMIC 0 1 0 OPTIMISATION_TYPE_DEFAULT ADDRESS_EXTENSION_FREE IDENTIFICATION_FIELD_TYPE_RELATIVE_BYTE GRANULARITY_ODT_ENTRY_SIZE_DAQ_BYTE 0xF8 OVERLOAD_INDICATION_PID 108 | /begin TIMESTAMP_SUPPORTED 109 | 0x01 SIZE_DWORD UNIT_1NS TIMESTAMP_FIXED 110 | /end TIMESTAMP_SUPPORTED 111 | /begin EVENT "mainloop" "mainloop" 0x0 DAQ 0xFF 0 0 0 CONSISTENCY EVENT /end EVENT 112 | /end DAQ 113 | /begin XCP_ON_UDP_IP 114 | 0x0104 5555 ADDRESS "192.168.8.110" 115 | /end XCP_ON_UDP_IP 116 | /end IF_DATA 117 | 118 | /end MODULE 119 | /end PROJECT 120 | -------------------------------------------------------------------------------- /xcplib/examples/hello_xcp/src/main.c: -------------------------------------------------------------------------------- 1 | // hello_xcp xcplib example 2 | 3 | #include // for assert 4 | #include // for bool 5 | #include // for uintxx_t 6 | #include // for printf 7 | #include // for sprintf 8 | 9 | #include "a2l.h" // for A2l generation 10 | #include "platform.h" // for sleepMs, clockGet 11 | #include "xcpEthServer.h" // for XcpEthServerInit, XcpEthServerShutdown, XcpEthServerStatus 12 | #include "xcpLite.h" // for XcpInit, XcpEventExt, XcpCreateEvent, XcpCreateCalSeg, ... 13 | 14 | //----------------------------------------------------------------------------------------------------- 15 | 16 | // XCP parameters 17 | #define OPTION_A2L_PROJECT_NAME "hello_xcp" // A2L project name 18 | #define OPTION_A2L_FILE_NAME "hello_xcp.a2l" // A2L filename 19 | #define OPTION_USE_TCP false // TCP or UDP 20 | #define OPTION_SERVER_PORT 5555 // Port 21 | // #define OPTION_SERVER_ADDR {0, 0, 0, 0} // Bind addr, 0.0.0.0 = ANY 22 | #define OPTION_SERVER_ADDR {192, 168, 8, 110} // Bind addr, 0.0.0.0 = ANY 23 | #define OPTION_QUEUE_SIZE 1024 * 16 // Size of the measurement queue in bytes, must be a multiple of 8 24 | 25 | //----------------------------------------------------------------------------------------------------- 26 | 27 | // Demo calibration parameters 28 | typedef struct params { 29 | uint16_t counter_max; // Maximum value for the counters 30 | uint32_t delay_us; // Delay in microseconds for the main loop 31 | int8_t test_byte1; 32 | int8_t test_byte2; 33 | } params_t; 34 | 35 | // Default values 36 | const params_t params = {.counter_max = 1000, .delay_us = 1000, .test_byte1 = -1, .test_byte2 = 1}; 37 | 38 | //----------------------------------------------------------------------------------------------------- 39 | 40 | // Global demo measurement variable 41 | static uint16_t counter = 0; 42 | 43 | //----------------------------------------------------------------------------------------------------- 44 | 45 | // Demo main 46 | int main(void) { 47 | 48 | printf("\nXCP on Ethernet hello_xcp C xcplib demo\n"); 49 | 50 | // Set log level (1-error, 2-warning, 3-info, 4-show XCP commands) 51 | XcpSetLogLevel(3); 52 | 53 | // Initialize the XCP singleton, must be called before starting the server 54 | XcpInit(); 55 | 56 | // Initialize the XCP Server 57 | uint8_t addr[4] = OPTION_SERVER_ADDR; 58 | if (!XcpEthServerInit(addr, OPTION_SERVER_PORT, OPTION_USE_TCP, NULL, OPTION_QUEUE_SIZE)) { 59 | return 1; 60 | } 61 | 62 | // Prepare the A2L file 63 | if (!A2lInit(OPTION_A2L_FILE_NAME, OPTION_A2L_PROJECT_NAME, addr, OPTION_SERVER_PORT, OPTION_USE_TCP, true)) { 64 | return 1; 65 | } 66 | 67 | // Create a calibration segment for the calibration parameter struct 68 | // This segment has a working page (RAM) and a reference page (FLASH), it creates a MEMORY_SEGMENT in the A2L file 69 | // It provides safe (thread safe against XCP modifications), lock-free and consistent access to the calibration parameters 70 | // It supports XCP/ECU independant page switching, checksum calculation and reinitialization (copy reference page to working page) 71 | // Note that it can be used in only one ECU thread (in Rust terminology, it is Send, but not Sync) 72 | uint16_t calseg = XcpCreateCalSeg("params", (const uint8_t *)¶ms, sizeof(params)); 73 | 74 | // Register individual calibration parameters in the calibration segment 75 | A2lSetSegAddrMode(calseg, (uint8_t *)¶ms); 76 | A2lCreateParameterWithLimits(params.counter_max, A2L_TYPE_UINT16, "maximum counter value", "", 0, 2000); 77 | A2lCreateParameterWithLimits(params.delay_us, A2L_TYPE_UINT32, "mainloop delay time in ue", "us", 0, 1000000); 78 | 79 | // Create a measurement event 80 | uint16_t event = XcpCreateEvent("mainloop", 0, 0); 81 | 82 | // Register a global measurement variable 83 | A2lSetAbsAddrMode(); // Set absolute addressing 84 | A2lCreatePhysMeasurement(counter, A2L_TYPE_UINT16, "Measurement variable", 1.0, 0.0, "counts"); 85 | 86 | for (;;) { 87 | // Lock the calibration parameter segment for consistent and safe access 88 | // Calibration segment locking is completely lock-free and wait-free (no mutexes, system calls or CAS operations ) 89 | // It returns a pointer to the active page (working or reference) of the calibration segment 90 | params_t *params = (params_t *)XcpLockCalSeg(calseg); 91 | 92 | // Sleep for the specified delay parameter in microseconds 93 | sleepNs(params->delay_us * 1000); 94 | 95 | // Local variable for measurement 96 | counter++; 97 | if (counter > params->counter_max) { 98 | counter = 0; 99 | } 100 | 101 | // Unlock the calibration segment 102 | XcpUnlockCalSeg(calseg); 103 | 104 | // Trigger measurement events 105 | XcpEvent(event); 106 | 107 | } // for(;;) 108 | 109 | // Force disconnect the XCP client 110 | XcpDisconnect(); 111 | 112 | // Stop the XCP server 113 | XcpEthServerShutdown(); 114 | 115 | return 0; 116 | } 117 | -------------------------------------------------------------------------------- /xcplib/src/a2l.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* A2L.h */ 3 | /* Copyright(c) Vector Informatik GmbH.All rights reserved. 4 | Licensed under the MIT license.See LICENSE file in the project root for details. */ 5 | 6 | #include // for bool 7 | #include // for uintxx_t 8 | 9 | #include "platform.h" // for atomic_bool 10 | 11 | // Basic A2L types 12 | #define A2L_TYPE_UINT8 1 13 | #define A2L_TYPE_UINT16 2 14 | #define A2L_TYPE_UINT32 4 15 | #define A2L_TYPE_UINT64 8 16 | #define A2L_TYPE_INT8 -1 17 | #define A2L_TYPE_INT16 -2 18 | #define A2L_TYPE_INT32 -4 19 | #define A2L_TYPE_INT64 -8 20 | #define A2L_TYPE_FLOAT -9 21 | #define A2L_TYPE_DOUBLE -10 22 | 23 | // Set mode for all following A2lCreateXxxx macros and functions 24 | void A2lSetAbsAddrMode(void); 25 | void A2lSetSegAddrMode(uint16_t calseg_index, const uint8_t *calseg); 26 | void A2lSetRelAddrMode(const uint16_t *event); 27 | void A2lSetFixedEvent(uint16_t event); 28 | void A2lRstFixedEvent(void); 29 | void A2lSetDefaultEvent(uint16_t event); 30 | void A2lRstDefaultEvent(void); 31 | 32 | // Create parameters in a calibration segment or in global memory 33 | 34 | #define A2lCreateParameter(name, type, comment, unit) A2lCreateParameter_(#name, type, A2lGetAddrExt(), A2lGetAddr((uint8_t *)&name), comment, unit) 35 | 36 | #define A2lCreateParameterWithLimits(name, type, comment, unit, min, max) \ 37 | A2lCreateParameterWithLimits_(#name, type, A2lGetAddrExt(), A2lGetAddr((uint8_t *)&name), comment, unit, min, max) 38 | 39 | #define A2lCreateCurve(name, type, xdim, comment, unit) A2lCreateCurve_(#name, type, A2lGetAddrExt(), A2lGetAddr((uint8_t *)&name[0]), xdim, comment, unit) 40 | 41 | #define A2lCreateMap(name, type, xdim, ydim, comment, unit) A2lCreateMap_(#name, type, A2lGetAddrExt(), A2lGetAddr((uint8_t *)&name[0][0]), xdim, ydim, comment, unit) 42 | 43 | // Create measurements on stack or in global memory 44 | // Measurements are registered once, it is allowed to use the following macros in local scope which is run multiple times 45 | 46 | #define A2lCreateMeasurement(name, type, comment) \ 47 | { \ 48 | static atomic_bool __once = false; \ 49 | if (A2lOnce(&__once)) \ 50 | A2lCreateMeasurement_(NULL, #name, type, A2lGetAddrExt(), A2lGetAddr((uint8_t *)&(name)), 1.0, 0.0, NULL, comment); \ 51 | } 52 | 53 | #define A2lCreatePhysMeasurement(name, type, comment, factor, offset, unit) \ 54 | { \ 55 | static atomic_bool __once = false; \ 56 | if (A2lOnce(&__once)) \ 57 | A2lCreateMeasurement_(NULL, #name, type, A2lGetAddrExt(), A2lGetAddr((uint8_t *)&name), factor, offset, unit, comment); \ 58 | } 59 | 60 | #define A2lCreateMeasurementArray(name, type) \ 61 | { \ 62 | static atomic_bool __once = false; \ 63 | if (A2lOnce(&__once)) \ 64 | A2lCreateMeasurementArray_(NULL, #name, type, sizeof(name) / sizeof(name[0]), A2lGetAddrExt(), A2lGetAddr(&name[0])); \ 65 | } 66 | 67 | #define A2lCreateTypedefInstance(instanceName, typeName, comment) \ 68 | { \ 69 | static atomic_bool __once = false; \ 70 | if (A2lOnce(&__once)) \ 71 | A2lCreateTypedefInstance_(#instanceName, #typeName, A2lGetAddrExt(), A2lGetAddr((uint8_t *)&instanceName), comment); \ 72 | } 73 | 74 | // Create typedefs 75 | #define A2lTypedefBegin(name, comment) A2lTypedefBegin_(#name, (uint32_t)sizeof(name), comment) 76 | #define A2lTypedefComponent(fieldName, type, instanceName) A2lTypedefMeasurementComponent_(#fieldName, type, ((uint8_t *)&(instanceName.fieldName) - (uint8_t *)&instanceName)) 77 | #define A2lTypedefEnd() A2lTypedefEnd_() 78 | 79 | // Create groups 80 | void A2lParameterGroup(const char *name, int count, ...); 81 | void A2lParameterGroupFromList(const char *name, const char *pNames[], int count); 82 | void A2lMeasurementGroup(const char *name, int count, ...); 83 | void A2lMeasurementGroupFromList(const char *name, char *names[], uint32_t count); 84 | 85 | // Init A2L generation 86 | bool A2lInit(const char *a2l_filename, const char *a2l_projectname, const uint8_t *addr, uint16_t port, bool useTCP, bool finalize_on_connect); 87 | 88 | // Finish A2L generation 89 | bool A2lFinalize(void); 90 | 91 | // -------------------------------------------------------------------------------------------- 92 | // Functions used in the for A2L generation macros 93 | 94 | uint32_t A2lGetAddr(const uint8_t *addr); 95 | uint8_t A2lGetAddrExt(void); 96 | bool A2lOnce(atomic_bool *once); 97 | 98 | // Create measurements 99 | void A2lCreateMeasurement_(const char *instanceName, const char *name, int32_t type, uint8_t ext, uint32_t addr, double factor, double offset, const char *unit, 100 | const char *comment); 101 | void A2lCreateMeasurementArray_(const char *instanceName, const char *name, int32_t type, int dim, uint8_t ext, uint32_t addr); 102 | 103 | // Create typedefs 104 | void A2lTypedefBegin_(const char *name, uint32_t size, const char *comment); 105 | void A2lTypedefMeasurementComponent_(const char *name, int32_t type, uint32_t offset); 106 | void A2lTypedefParameterComponent_(const char *name, int32_t type, uint32_t offset); 107 | void A2lTypedefEnd_(void); 108 | void A2lCreateTypedefInstance_(const char *instanceName, const char *typeName, uint8_t ext, uint32_t addr, const char *comment); 109 | 110 | // Create parameters 111 | void A2lCreateParameter_(const char *name, int32_t type, uint8_t ext, uint32_t addr, const char *comment, const char *unit); 112 | void A2lCreateParameterWithLimits_(const char *name, int32_t type, uint8_t ext, uint32_t addr, const char *comment, const char *unit, double min, double max); 113 | void A2lCreateMap_(const char *name, int32_t type, uint8_t ext, uint32_t addr, uint32_t xdim, uint32_t ydim, const char *comment, const char *unit); 114 | void A2lCreateCurve_(const char *name, int32_t type, uint8_t ext, uint32_t addr, uint32_t xdim, const char *comment, const char *unit); 115 | -------------------------------------------------------------------------------- /xcplib/src/dbg_print.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define __DBG_PRINT_H__ 3 | 4 | /* 5 | | Code released into public domain, no attribution required 6 | */ 7 | 8 | #include "main_cfg.h" // for OPTION_xxx 9 | 10 | //------------------------------------------------------------------------------- 11 | // Debug print 12 | 13 | #if defined(OPTION_ENABLE_DBG_PRINTS) && !defined(OPTION_DEFAULT_DBG_LEVEL) 14 | #error "Please define OPTION_DEFAULT_DBG_LEVEL" 15 | #endif 16 | 17 | #ifdef OPTION_ENABLE_DBG_PRINTS 18 | 19 | /* 20 | 1 - Error 21 | 2 - Warn 22 | 3 - Info 23 | 4 - Trace 24 | 5 - Debug 25 | */ 26 | 27 | extern uint8_t gDebugLevel; 28 | #define DBG_LEVEL gDebugLevel 29 | 30 | #define DBG_PRINTF(format, ...) printf(format, __VA_ARGS__) 31 | #define DBG_PRINTF_ERROR(format, ...) \ 32 | if (DBG_LEVEL >= 1) \ 33 | printf("[XCP ] ERROR: " format, __VA_ARGS__) 34 | #define DBG_PRINTF_WARNING(format, ...) \ 35 | if (DBG_LEVEL >= 2) \ 36 | printf("[XCP ] WARNING: " format, __VA_ARGS__) 37 | #define DBG_PRINTF3(format, ...) \ 38 | if (DBG_LEVEL >= 3) \ 39 | printf("[XCP ] " format, __VA_ARGS__) 40 | #define DBG_PRINTF4(format, ...) \ 41 | if (DBG_LEVEL >= 4) \ 42 | printf("[XCP ] " format, __VA_ARGS__) 43 | #define DBG_PRINTF5(format, ...) \ 44 | if (DBG_LEVEL >= 5) \ 45 | printf("[XCP ] " format, __VA_ARGS__) 46 | 47 | #define DBG_PRINT(format) printf(format) 48 | #define DBG_PRINT_ERROR(format) \ 49 | if (DBG_LEVEL >= 1) \ 50 | printf("[XCP ] ERROR: " format) 51 | #define DBG_PRINT_WARNING(format) \ 52 | if (DBG_LEVEL >= 2) \ 53 | printf("[XCP ] WARNING: " format) 54 | #define DBG_PRINT3(format) \ 55 | if (DBG_LEVEL >= 3) \ 56 | printf("[XCP ] " format) 57 | #define DBG_PRINT4(format) \ 58 | if (DBG_LEVEL >= 4) \ 59 | printf("[XCP ] " format) 60 | #define DBG_PRINT5(format) \ 61 | if (DBG_LEVEL >= 5) \ 62 | printf("[XCP ] " format) 63 | 64 | #else 65 | 66 | #undef DBG_LEVEL 67 | 68 | #define DBG_PRINTF(s, ...) 69 | #define DBG_PRINTF_ERROR(s, ...) // printf(s,__VA_ARGS__) 70 | #define DBG_PRINTF_WARNING(s, ...) 71 | #define DBG_PRINTF3(s, ...) 72 | #define DBG_PRINTF4(s, ...) 73 | #define DBG_PRINTF5(s, ...) 74 | 75 | #define DBG_PRINT(s, ...) 76 | #define DBG_PRINT_ERROR(s, ...) // printf(s,__VA_ARGS__) 77 | #define DBG_PRINT_WARNING(s, ...) 78 | #define DBG_PRINT3(s, ...) 79 | #define DBG_PRINT4(s, ...) 80 | #define DBG_PRINT5(s, ...) 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /xcplib/src/main_cfg.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define __MAIN_CFG_H__ 3 | 4 | /* 5 | | Build options for XCP or xcplib 6 | | 7 | | Code released into public domain, no attribution required 8 | */ 9 | 10 | /* 11 | XCP library build options: 12 | 13 | // Logging 14 | #define OPTION_ENABLE_DBG_PRINTS Enable debug prints 15 | #define OPTION_DEFAULT_DBG_LEVEL Default log level: 1 - Error, 2 - Warn, 3 - Info, 4 - Trace, 5 - Debug 16 | 17 | // Clock 18 | #define OPTION_CLOCK_EPOCH_ARB Arbitrary epoch or since 1.1.1970 19 | #define OPTION_CLOCK_EPOCH_PTP 20 | #define OPTION_CLOCK_TICKS_1NS Resolution 1ns or 1us, granularity depends on platform 21 | #define OPTION_CLOCK_TICKS_1US 22 | 23 | // XCP 24 | #define OPTION_ENABLE_TCP 25 | #define OPTION_ENABLE_UDP 26 | #define OPTION_MTU UDP MTU 27 | #define OPTION_DAQ_MEM_SIZE Size of memory for DAQ setup in bytes (5 byts per signal needed) 28 | #define OPTION_ENABLE_A2L_UPLOAD Enable A2L upload through XCP 29 | #define OPTION_ENABLE_GET_LOCAL_ADDR Determine an existing IP address for A2L file, if bound to ANY 30 | #define OPTION_SERVER_FORCEFULL_TERMINATION Terminate the server threads instead of waiting for the tasks to finish 31 | */ 32 | 33 | // Application configuration: 34 | // (More specific XCP configuration is in xcp_cfg.h (Protocol Layer) and xcptl_cfg.h (Transport Layer)) 35 | 36 | // XCP options 37 | #define OPTION_ENABLE_TCP 38 | #define OPTION_ENABLE_UDP 39 | #define OPTION_MTU 6000 // UDP MTU size - Jumbo frames supported 40 | #define OPTION_DAQ_MEM_SIZE (32 * 1024 * 5) // Memory bytes used for XCP DAQ tables - max 5 bytes per signal needed 41 | #define OPTION_ENABLE_A2L_UPLOAD 42 | #define OPTION_SERVER_FORCEFULL_TERMINATION 43 | 44 | // Platform options 45 | 46 | // Clock 47 | // #define OPTION_CLOCK_EPOCH_ARB // -> use CLOCK_MONOTONIC_RAW 48 | #define OPTION_CLOCK_EPOCH_PTP // -> use CLOCK_REALTIME 49 | #define OPTION_CLOCK_TICKS_1NS 50 | 51 | // Enable socketGetLocalAddr and XcpEthTlGetInfo 52 | // Used for convenience to get a correct ip addr in A2L, when bound to ANY 0.0.0.0 53 | #define OPTION_ENABLE_GET_LOCAL_ADDR 54 | 55 | // Logging 56 | #define OPTION_ENABLE_DBG_PRINTS 57 | #define OPTION_DEFAULT_DBG_LEVEL 2 58 | -------------------------------------------------------------------------------- /xcplib/src/xcpAppl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define __XCP_APPL_H__ 3 | 4 | // Additional functions for users of the XCP library xcplib 5 | 6 | #include // for bool 7 | #include // for uintxx_t 8 | 9 | #include "xcpLite.h" // for tXcpDaqLists 10 | 11 | void ApplXcpSetLogLevel(uint8_t level); 12 | 13 | void ApplXcpRegisterCallbacks(bool (*cb_connect)(void), uint8_t (*cb_prepare_daq)(void), uint8_t (*cb_start_daq)(void), void (*cb_stop_daq)(void), 14 | uint8_t (*cb_freeze_daq)(uint8_t clear, uint16_t config_id), uint8_t (*cb_get_cal_page)(uint8_t segment, uint8_t mode), 15 | uint8_t (*cb_set_cal_page)(uint8_t segment, uint8_t page, uint8_t mode), uint8_t (*cb_freeze_cal)(void), 16 | uint8_t (*cb_init_cal)(uint8_t src_page, uint8_t dst_page), uint8_t (*cb_read)(uint32_t src, uint8_t size, uint8_t *dst), 17 | uint8_t (*cb_write)(uint32_t dst, uint8_t size, const uint8_t *src, uint8_t delay), uint8_t (*cb_flush)(void)); 18 | 19 | void ApplXcpRegisterConnectCallback(bool (*cb_connect)(void)); 20 | 21 | void ApplXcpSetA2lName(const char *name); 22 | -------------------------------------------------------------------------------- /xcplib/src/xcpEthServer.c: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------- 2 | | File: 3 | | xcpEthServer.c 4 | | 5 | | Description: 6 | | XCP on UDP Server 7 | | SHows how to integrate the XCP driver in an application 8 | | Creates threads for cmd handling and data transmission 9 | | 10 | | Copyright (c) Vector Informatik GmbH. All rights reserved. 11 | | Licensed under the MIT license. See LICENSE file in the project root for details. 12 | | 13 | -----------------------------------------------------------------------------*/ 14 | 15 | #include "xcpEthServer.h" 16 | 17 | #include // for assert 18 | #include // for PRIu64 19 | #include // for bool 20 | #include // for uintxx_t 21 | #include // for printf 22 | 23 | #include "dbg_print.h" // for DBG_LEVEL, DBG_PRINT3, DBG_PRINTF4, DBG... 24 | #include "main_cfg.h" // for OPTION_xxx 25 | #include "platform.h" // for platform defines (WIN_, LINUX_, MACOS_) and specific implementation of sockets, clock, thread, mutex 26 | #include "xcp.h" // for CRC_XXX 27 | #include "xcpEthTl.h" // for tXcpCtoMessage, tXcpDtoMessage, xcpTlXxxx, xcpEthTlxxx 28 | #include "xcpLite.h" // for tXcpDaqLists, XcpXxx, ApplXcpXxx, ... 29 | #include "xcpQueue.h" 30 | #include "xcptl_cfg.h" // for XCPTL_xxx 31 | 32 | #if !defined(_WIN) && !defined(_LINUX) && !defined(_MACOS) 33 | #error "Please define platform _WIN, _MACOS or _LINUX" 34 | #endif 35 | 36 | #if defined(_WIN) // Windows 37 | static DWORD WINAPI XcpServerReceiveThread(LPVOID lpParameter); 38 | #elif defined(_LINUX) // Linux 39 | static void *XcpServerReceiveThread(void *par); 40 | #endif 41 | #if defined(_WIN) // Windows 42 | static DWORD WINAPI XcpServerTransmitThread(LPVOID lpParameter); 43 | #elif defined(_LINUX) // Linux 44 | static void *XcpServerTransmitThread(void *par); 45 | #endif 46 | 47 | static struct { 48 | 49 | bool isInit; 50 | 51 | // Threads 52 | THREAD TransmitThreadHandle; 53 | volatile bool TransmitThreadRunning; 54 | THREAD ReceiveThreadHandle; 55 | volatile bool ReceiveThreadRunning; 56 | 57 | // Queue 58 | tQueueHandle TransmitQueue; 59 | MUTEX TransmitQueueMutex; 60 | 61 | } gXcpServer; 62 | 63 | // Check XCP server status 64 | bool XcpEthServerStatus(void) { return gXcpServer.isInit && gXcpServer.TransmitThreadRunning && gXcpServer.ReceiveThreadRunning; } 65 | 66 | // XCP server init 67 | bool XcpEthServerInit(const uint8_t *addr, uint16_t port, bool useTCP, void *queue, uint32_t queueSize) { 68 | 69 | // Check that the XCP singleton has been explicitly initialized 70 | if (!XcpIsInitialized()) { 71 | DBG_PRINT_ERROR("XCP not initialized!\n"); 72 | return false; 73 | } 74 | 75 | // Check if already initialized and running 76 | if (gXcpServer.isInit) { 77 | DBG_PRINT_WARNING("XCP server already running!\n"); 78 | return false; 79 | } 80 | 81 | DBG_PRINT3("Start XCP server\n"); 82 | DBG_PRINTF3(" Queue size = %u\n", queueSize); 83 | 84 | gXcpServer.TransmitThreadRunning = false; 85 | gXcpServer.ReceiveThreadRunning = false; 86 | 87 | // Init network sockets 88 | if (!socketStartup()) 89 | return false; 90 | 91 | // Create queue 92 | // @@@@@ External queue not supported yet 93 | assert(queueSize > 0); 94 | assert(queue == NULL); 95 | gXcpServer.TransmitQueue = QueueInit(queueSize); 96 | if (gXcpServer.TransmitQueue == NULL) 97 | return false; 98 | 99 | // Initialize XCP transport layer 100 | if (!XcpEthTlInit(addr, port, useTCP, true /*blocking rx*/, gXcpServer.TransmitQueue)) 101 | return false; 102 | 103 | // Start XCP protocol layer 104 | XcpStart(gXcpServer.TransmitQueue, false); 105 | 106 | // Create threads 107 | mutexInit(&gXcpServer.TransmitQueueMutex, false, 0); 108 | create_thread(&gXcpServer.TransmitThreadHandle, XcpServerTransmitThread); 109 | create_thread(&gXcpServer.ReceiveThreadHandle, XcpServerReceiveThread); 110 | 111 | gXcpServer.isInit = true; 112 | return true; 113 | } 114 | 115 | bool XcpEthServerShutdown(void) { 116 | 117 | #ifdef OPTION_SERVER_FORCEFULL_TERMINATION 118 | // Forcefull termination 119 | if (gXcpServer.isInit) { 120 | DBG_PRINT3("Disconnect, cancel threads and shutdown XCP!\n"); 121 | XcpDisconnect(); 122 | cancel_thread(gXcpServer.ReceiveThreadHandle); 123 | cancel_thread(gXcpServer.TransmitThreadHandle); 124 | XcpEthTlShutdown(); 125 | mutexDestroy(&gXcpServer.TransmitQueueMutex); 126 | gXcpServer.isInit = false; 127 | socketCleanup(); 128 | XcpReset(); 129 | } 130 | #else 131 | // Gracefull termination 132 | if (gXcpServer.isInit) { 133 | XcpDisconnect(); 134 | gXcpServer.ReceiveThreadRunning = false; 135 | gXcpServer.TransmitThreadRunning = false; 136 | XcpEthTlShutdown(); 137 | join_thread(gXcpServer.ReceiveThreadHandle); 138 | join_thread(gXcpServer.TransmitThreadHandle); 139 | mutexDestroy(&gXcpServer.TransmitQueueMutex); 140 | gXcpServer.isInit = false; 141 | socketCleanup(); 142 | XcpReset(); 143 | } 144 | #endif 145 | 146 | QueueDeinit(gXcpServer.TransmitQueue); 147 | 148 | return true; 149 | } 150 | 151 | // XCP server unicast command receive thread 152 | #if defined(_WIN) // Windows 153 | DWORD WINAPI XcpServerReceiveThread(LPVOID par) 154 | #elif defined(_LINUX) // Linux 155 | extern void *XcpServerReceiveThread(void *par) 156 | #endif 157 | { 158 | (void)par; 159 | DBG_PRINT3("Start XCP CMD thread\n"); 160 | 161 | // Receive XCP unicast commands loop 162 | gXcpServer.ReceiveThreadRunning = true; 163 | while (gXcpServer.ReceiveThreadRunning) { 164 | if (!XcpEthTlHandleCommands(XCPTL_TIMEOUT_INFINITE)) { // Timeout Blocking 165 | DBG_PRINT_ERROR("XcpEthTlHandleCommands failed!\n"); 166 | break; // error -> terminate thread 167 | } else { 168 | // Handle transmit queue after each command, to keep the command latency short 169 | mutexLock(&gXcpServer.TransmitQueueMutex); 170 | int32_t n = XcpTlHandleTransmitQueue(); 171 | mutexUnlock(&gXcpServer.TransmitQueueMutex); 172 | if (n < 0) { 173 | DBG_PRINT_ERROR("XcpTlHandleTransmitQueue failed!\n"); 174 | break; // error - terminate thread 175 | } 176 | 177 | XcpBackgroundTasks(); // Handle background tasks, e.g. pending calibration updates 178 | } 179 | } 180 | gXcpServer.ReceiveThreadRunning = false; 181 | 182 | DBG_PRINT3("XCP receive thread terminated!\n"); 183 | return 0; 184 | } 185 | 186 | // XCP server transmit thread 187 | #if defined(_WIN) // Windows 188 | DWORD WINAPI XcpServerTransmitThread(LPVOID par) 189 | #elif defined(_LINUX) // Linux 190 | extern void *XcpServerTransmitThread(void *par) 191 | #endif 192 | { 193 | (void)par; 194 | int32_t n; 195 | 196 | DBG_PRINT3("Start XCP DAQ thread\n"); 197 | 198 | // Transmit loop 199 | gXcpServer.TransmitThreadRunning = true; 200 | while (gXcpServer.TransmitThreadRunning) { 201 | 202 | // Wait for transmit data available, time out at least for required flush cycle 203 | if (!XcpTlWaitForTransmitData(XCPTL_QUEUE_FLUSH_CYCLE_MS)) 204 | XcpTlFlushTransmitQueue(); // Flush after timeout to keep data visualization going 205 | 206 | // Transmit all completed messages from the transmit queue 207 | mutexLock(&gXcpServer.TransmitQueueMutex); 208 | n = XcpTlHandleTransmitQueue(); 209 | mutexUnlock(&gXcpServer.TransmitQueueMutex); 210 | if (n < 0) { 211 | DBG_PRINT_ERROR("XcpTlHandleTransmitQueue failed!\n"); 212 | break; // error - terminate thread 213 | } 214 | 215 | } // for (;;) 216 | gXcpServer.TransmitThreadRunning = false; 217 | 218 | DBG_PRINT3("XCP transmit thread terminated!\n"); 219 | return 0; 220 | } 221 | -------------------------------------------------------------------------------- /xcplib/src/xcpEthServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define __XCP_ETH_SERVER_H__ 3 | 4 | /* Copyright(c) Vector Informatik GmbH.All rights reserved. 5 | Licensed under the MIT license.See LICENSE file in the project root for details. */ 6 | 7 | #include 8 | #include 9 | 10 | /// Initialize the XCP on Ethernet server instance. 11 | /// @pre User has called XcpInit. 12 | /// @param address Address to bind to. 13 | /// @param port Port to bind to. 14 | /// @param use_tcp Use TCP if true, otherwise UDP. 15 | /// @param measurement_queue Optional external memory to place the measurement queue. 16 | /// Pass NULL if server should allocate it. 17 | /// @param measurement_queue_size Measurement queue size in bytes. Includes the bytes occupied by the queue header. 18 | /// @return True on success, otherwise false. 19 | bool XcpEthServerInit(uint8_t const *address, uint16_t port, bool use_tcp, void *measurement_queue, uint32_t measurement_queue_size); 20 | 21 | /// Shutdown the XCP on Ethernet server instance. 22 | bool XcpEthServerShutdown(void); 23 | 24 | /// Get the XCP on Ethernet server instance status. 25 | /// @return True if the server is running, otherwise false. 26 | bool XcpEthServerStatus(void); 27 | 28 | /// Get information about the XCP on Ethernet server instance address. 29 | /// @pre The server instance is running. 30 | /// @param out_is_tcp Optional out parameter to query if TCP or UDP is used. 31 | /// True if TCP, otherwise UDP. 32 | /// Pass NULL if not required. 33 | /// @param out_mac Optional out parameter to query the MAC address of the interface used in the server instance. 34 | /// Pass NULL if not required. 35 | /// @param out_address Optional out parameter to query the IP address used in the server instance. 36 | /// Pass NULL if not required. 37 | /// @param out_port Optional out parameter to query the port address used in the server instance. 38 | /// Pass NULL if not required. 39 | void XcpEthServerGetInfo(bool *out_is_tcp, uint8_t *out_mac, uint8_t *out_address, uint16_t *out_port); 40 | -------------------------------------------------------------------------------- /xcplib/src/xcpEthTl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define __XCP_ETHTL_H__ 3 | 4 | /* Copyright(c) Vector Informatik GmbH.All rights reserved. 5 | Licensed under the MIT license.See LICENSE file in the project root for details. */ 6 | 7 | #include 8 | #include 9 | 10 | #include "platform.h" // for platform defines (WIN_, LINUX_, MACOS_) and specific implementation of sockets, clock, thread, mutex 11 | #include "xcptl_cfg.h" // for XCPTL_xxx 12 | #include "xcpQueue.h" // for QueueXxxx, tQueueHandle 13 | 14 | // Parameter checks 15 | #if XCPTL_TRANSPORT_LAYER_HEADER_SIZE != 4 16 | #error "Transportlayer supports only 4 byte headers!" 17 | #endif 18 | #if ((XCPTL_MAX_CTO_SIZE & 0x07) != 0) 19 | #error "XCPTL_MAX_CTO_SIZE should be aligned to 8!" 20 | #endif 21 | #if ((XCPTL_MAX_DTO_SIZE & 0x03) != 0) 22 | #error "XCPTL_MAX_DTO_SIZE should be aligned to 4!" 23 | #endif 24 | 25 | #pragma pack(push, 1) 26 | typedef struct { 27 | uint16_t dlc; // XCP TL header lenght 28 | uint16_t ctr; // XCP TL Header message counter 29 | uint8_t data[]; 30 | } tXcpDtoMessage; 31 | #pragma pack(pop) 32 | 33 | #pragma pack(push, 1) 34 | typedef struct { 35 | uint16_t dlc; 36 | uint16_t ctr; 37 | uint8_t packet[XCPTL_MAX_CTO_SIZE]; 38 | } tXcpCtoMessage; 39 | #pragma pack(pop) 40 | 41 | #define XCPTL_TIMEOUT_INFINITE 0xFFFFFFFF // Infinite timeout (blocking mode) for XcpTlHandleCommands, XcpTlWaitForTransmitData 42 | 43 | // Transport Layer functions called by protocol layer in xcpLite.c 44 | bool XcpTlWaitForTransmitQueueEmpty(uint16_t timeout_ms); // Wait (sleep) until transmit queue is empty, timeout after 1s return false 45 | bool XcpTlNotifyTransmitQueueHandler(tQueueHandle queueHandle); // provider -> consumer event notificatoni 46 | 47 | // Transport layer functions called by XCP server in xcpEthServer.c 48 | uint8_t XcpTlCommand(uint16_t msgLen, const uint8_t *msgBuf); // Handle XCP message 49 | bool XcpTlWaitForTransmitData(uint32_t timeout_ms); // Wait for at least timeout_ms, until packets are pending in the transmit queue 50 | int32_t XcpTlHandleTransmitQueue(void); // Send pending packets in the transmit queue 51 | void XcpTlFlushTransmitQueue(void); 52 | 53 | /* ETH transport Layer functions called by server */ 54 | 55 | bool XcpEthTlInit(const uint8_t *addr, uint16_t port, bool useTCP, bool blockingRx, tQueueHandle queue_handle); // Start transport layer 56 | void XcpEthTlShutdown(void); 57 | void XcpEthTlGetInfo(bool *isTCP, uint8_t *mac, uint8_t *addr, uint16_t *port); 58 | 59 | /* Transmit a segment (contains multiple XCP DTO or CRO messages */ 60 | int XcpEthTlSend(const uint8_t *data, uint16_t size, const uint8_t *addr, uint16_t port); 61 | 62 | /* ETH transport Layer functions called by server */ 63 | bool XcpEthTlHandleCommands(uint32_t timeout_ms); // Handle all incoming XCP commands, (wait for at least timeout_ms) 64 | 65 | /* ETH transport Layer functions called by protocol layer */ 66 | #ifdef XCPTL_ENABLE_MULTICAST 67 | void XcpEthTlSendMulticastCrm(const uint8_t *data, uint16_t n, const uint8_t *addr, uint16_t port); // Send multicast command response 68 | void XcpEthTlSetClusterId(uint16_t clusterId); // Set cluster id for GET_DAQ_CLOCK_MULTICAST reception 69 | #endif 70 | -------------------------------------------------------------------------------- /xcplib/src/xcpQueue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define __XCP_QUEUE_h__ 3 | 4 | /* Copyright(c) Vector Informatik GmbH.All rights reserved. 5 | Licensed under the MIT license.See LICENSE file in the project root for details. */ 6 | 7 | #include 8 | #include 9 | 10 | // Handle for queue 11 | typedef struct tQueueHandleType *tQueueHandle; 12 | #define UNDEFINED_QUEUE_HANDLE NULL 13 | 14 | // Buffer acquired from the queue with `QueueAcquire` (producer) or obtained with `QueuePop`/`QueuePeek` (consumer) 15 | typedef struct { 16 | uint8_t *buffer; 17 | uint16_t size; 18 | } tQueueBuffer; 19 | 20 | // Create new heap allocated queue. Free using `QueueDeinit` 21 | tQueueHandle QueueInit(uint32_t buffer_size); 22 | 23 | // Creates a queue inside the user provided buffer. 24 | // This can be used to place the queue inside shared memory to be used by multiple applications 25 | tQueueHandle QueueInitFromMemory(void *queue_buffer, uint32_t queue_buffer_size, bool clear_queue); 26 | 27 | // Deinitialize queue. Does **not** free user allocated memory provided by `QueueInitFromMemory` 28 | void QueueDeinit(tQueueHandle queueHandle); 29 | 30 | // Acquire a queue buffer of size bytes 31 | tQueueBuffer QueueAcquire(tQueueHandle queueHandle, uint16_t size); 32 | 33 | // Push an aquired buffer to the queue 34 | void QueuePush(tQueueHandle queueHandle, tQueueBuffer *const handle, bool flush); 35 | 36 | // Single consumer: Get next buffer from the queue 37 | /// Buffers must be released in the order they were acquired !!! 38 | tQueueBuffer QueuePeek(tQueueHandle queueHandle); 39 | 40 | // Multi consumer: Not implemented 41 | // tQueueBuffer QueuePop(tQueueHandle handle); 42 | 43 | // Release buffer from `QueuePeek` or `QueuePop` 44 | // This is required to notify the queue that it can reuse a memory region. 45 | void QueueRelease(tQueueHandle queueHandle, tQueueBuffer *const queueBuffer); 46 | 47 | // Get amount of bytes in the queue, 0 if empty 48 | uint32_t QueueLevel(tQueueHandle queueHandle); 49 | 50 | // Clear queue content 51 | void QueueClear(tQueueHandle queueHandle); 52 | 53 | // Flush queue content 54 | void QueueFlush(tQueueHandle queueHandle); 55 | -------------------------------------------------------------------------------- /xcplib/src/xcpQueue32.c: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------- 2 | | File: 3 | | xcpQueue32.c 4 | | 5 | | Description: 6 | | XCP transport layer queue 7 | | Multi producer single consumer queue (producer side thread safe, not consumer side) 8 | | XCP transport layer specific: 9 | | Queue entries include XCP message header of WORD CTR and LEN type, CTR incremented on push, overflow indication via CTR 10 | | 11 | | Copyright (c) Vector Informatik GmbH. All rights reserved. 12 | | See LICENSE file in the project root for details. 13 | | 14 | ----------------------------------------------------------------------------*/ 15 | 16 | #include "xcpQueue.h" 17 | 18 | #include // for assert 19 | #include // for PRIu64 20 | #include // for bool 21 | #include // for uintxx_t 22 | #include // for NULL, snprintf 23 | #include // for free, malloc 24 | #include // for memcpy, strcmp 25 | 26 | #include "dbg_print.h" // for DBG_LEVEL, DBG_PRINT3, DBG_PRINTF4, DBG... 27 | #include "platform.h" // for platform defines (WIN_, LINUX_, MACOS_) and specific implementation of atomics, sockets, clock, thread, mutex 28 | #include "xcpEthTl.h" // for tXcpDtoMessage 29 | 30 | #error "Not implemented yet" -------------------------------------------------------------------------------- /xcplib/src/xcp_cfg.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define __XCP_CFG_H__ 3 | 4 | /*---------------------------------------------------------------------------- 5 | | File: 6 | | xcp_cfg.h 7 | | 8 | | Description: 9 | | Parameter configuration for XCP protocol layer parameters 10 | | 11 | | Code released into public domain, no attribution required 12 | ----------------------------------------------------------------------------*/ 13 | 14 | #include "main_cfg.h" // for OPTION_xxx 15 | 16 | // Clock resolution defined in platform.h 17 | #include "platform.h" // for platform defines (WIN_, LINUX_, MACOS_) and specific implementation of sockets, clock, thread, mutex 18 | 19 | /*----------------------------------------------------------------------------*/ 20 | /* Version */ 21 | 22 | // Driver version (GET_COMM_MODE_INFO) 23 | #define XCP_DRIVER_VERSION 0x01 24 | 25 | // Enable Ethernet specific protocol layer commands 26 | #define XCP_ENABLE_PROTOCOL_LAYER_ETH 27 | 28 | // Protocol layer version 29 | // #define XCP_PROTOCOL_LAYER_VERSION 0x0101 30 | // #define XCP_PROTOCOL_LAYER_VERSION 0x0103 // GET_DAQ_CLOCK_MULTICAST, GET_TIME_CORRELATION_PROPERTIES 31 | #define XCP_PROTOCOL_LAYER_VERSION 0x0104 // PACKED_MODE, CC_START_STOP_SYNCH prepare 32 | 33 | /*----------------------------------------------------------------------------*/ 34 | /* Address, address extension coding */ 35 | 36 | // Use addr_ext XCP_ADDR_EXT_REL to indicate relative addr format offset as uint64 37 | #define XCP_ENABLE_REL_ADDRESSING 38 | #define XCP_ADDR_EXT_REL 0x03 // Event relative address format 39 | 40 | // Use addr_ext XCP_ADDR_EXT_DYN to indicate relative addr format (event as uint16_t <<16)| offset as int16_t 41 | #define XCP_ENABLE_DYN_ADDRESSING 42 | #define XCP_ADDR_EXT_DYN 0x02 // Relative address format 43 | 44 | // Use addr_ext XCP_ADDR_EXT_ABS to indicate absulute addr format (ApplXcpGetBaseAddr()+ addr as uint64) 45 | #define XCP_ENABLE_ABS_ADDRESSING 46 | #define XCP_ADDR_EXT_ABS 0x01 // Absolute address format 47 | 48 | // Use addr_ext XCP_ADDR_EXT_SEG to indicate application specific addr format 49 | // Use ApplXcpReadMemory and ApplXcpWriteMemory or the XCP lite calibration segments (#ifdef XCP_ENABLE_CALSEG_LIST) to access memory 50 | #define XCP_ENABLE_APP_ADDRESSING // Segment relative memory access handled by application 51 | #define XCP_ADDR_EXT_SEG 0x00 // Segment relative address format 52 | 53 | // Internally used address extensions 54 | // Use addr_ext XCP_ADDR_EXT_EPK to indicate EPK upload memory space 55 | #define XCP_ADDR_EXT_EPK 0x00 56 | #define XCP_ADDR_EPK 0x80000000 57 | // Use addr_ext XCP_ADDR_EXT_A2L to indicate A2L upload memory space 58 | #define XCP_ADDR_EXT_A2L 0xFD 59 | #define XCP_ADDR_A2l 0x00000000 60 | // Use addr_ext XCP_ADDR_EXT_PTR to indicate gXcp.MtaPtr is valid 61 | #define XCP_ADDR_EXT_PTR 0xFE 62 | 63 | // Undefined address extension 64 | #define XCP_UNDEFINED_ADDR_EXT 0xFF // Undefined address extension 65 | 66 | // Make XcpEvent thread safe for same CAL event coming from different threads 67 | // Needed for xcp-lite, because CalSeg cal sync events may come from different threads 68 | #ifdef XCP_ENABLE_DYN_ADDRESSING 69 | #define XCP_ENABLE_MULTITHREAD_CAL_EVENTS 70 | #endif 71 | 72 | /*----------------------------------------------------------------------------*/ 73 | /* Protocol features */ 74 | 75 | #define XCP_ENABLE_CAL_PAGE // Enable calibration page switching commands 76 | #ifdef XCP_ENABLE_CAL_PAGE 77 | #define XCP_ENABLE_COPY_CAL_PAGE // // Enable calibration page initialization (FLASH->RAM copy) 78 | #define XCP_ENABLE_FREEZE_CAL_PAGE // Enable calibration freeze command 79 | #endif 80 | 81 | #define XCP_ENABLE_CHECKSUM // Enable checksum calculation command 82 | 83 | // #define XCP_ENABLE_SEED_KEY // Enable seed/key command 84 | 85 | #define XCP_ENABLE_SERV_TEXT // Enable SERV_TEXT events 86 | 87 | /*----------------------------------------------------------------------------*/ 88 | /* GET_ID command */ 89 | 90 | #ifdef OPTION_ENABLE_A2L_UPLOAD 91 | #define XCP_ENABLE_IDT_A2L_UPLOAD // Enable upload A2L via XCP 92 | #endif 93 | 94 | /*----------------------------------------------------------------------------*/ 95 | /* User defined command */ 96 | 97 | // Used for begin and end atomic calibration operation 98 | #define XCP_ENABLE_USER_COMMAND 99 | 100 | /*----------------------------------------------------------------------------*/ 101 | /* DAQ features and parameters */ 102 | 103 | // Maximum number of DAQ events 104 | // If XCP_MAX_EVENT_COUNT is defined, DAQ list to event association lookup will be optimized 105 | // Requires XCP_MAX_EVENT_COUNT * 2 bytes of memory 106 | // XCP_MAX_EVENT_COUNT must be even 107 | #define XCP_MAX_EVENT_COUNT 256 // For available event numbers from 0 to 255 108 | 109 | // Maximum number of DAQ lists 110 | // Must be <= 0xFFFE 111 | // Numbers smaller than 256 will switch to 2 byte transport layer header DAQ_HDR_ODT_DAQB 112 | #define XCP_MAX_DAQ_COUNT 1024 113 | 114 | // Static allocated memory for DAQ tables 115 | // Amount of memory for DAQ tables, each ODT entry (e.g. measurement variable) needs 5 bytes, each DAQ list 12 bytes and 116 | // each ODT 8 bytes 117 | #ifdef OPTION_DAQ_MEM_SIZE 118 | #define XCP_DAQ_MEM_SIZE OPTION_DAQ_MEM_SIZE 119 | #else 120 | #define XCP_DAQ_MEM_SIZE (1024 * 5) // Amount of memory for DAQ tables, each ODT entry (e.g. measurement variable or memory block) needs 5 bytes 121 | #endif 122 | 123 | // Enable DAQ resume mode 124 | #define XCP_ENABLE_DAQ_RESUME 125 | 126 | // Enable event list 127 | // Not needed for Rust xcp-lite, has its own event management 128 | #define XCP_ENABLE_DAQ_EVENT_LIST 129 | #ifdef XCP_ENABLE_DAQ_EVENT_LIST 130 | 131 | // Enable XCP_GET_EVENT_INFO, if this is enabled, A2L file event information will be ignored 132 | // #define XCP_ENABLE_DAQ_EVENT_INFO 133 | 134 | #define XCP_MAX_EVENT_NAME 15 135 | 136 | #endif 137 | 138 | // Enable calibration segment list 139 | // Not needed for Rust xcp-lite, has its own calibration segment management 140 | #define XCP_ENABLE_CALSEG_LIST 141 | #ifdef XCP_ENABLE_CALSEG_LIST 142 | 143 | #define XCP_MAX_CALSEG_COUNT 4 144 | #define XCP_MAX_CALSEG_NAME 15 145 | 146 | // Enable lazy write mode for calibration segments 147 | // RCU updates of calibration segments are done in a cyclic manner in the background 148 | // Calibration write speed is then independent from the lock rate, but single calibration latencies are higher 149 | // Without this, calibration updates are always delayed by one lock cycle and only one single direct or one atomic calibration change is possible per lock 150 | // If the latency of a single, sporadic calibration change is extremely important, this can be disabled 151 | #define XCP_ENABLE_CALSEG_LAZY_WRITE 152 | 153 | // Timeout for acquiring a free calibration segment page 154 | #define XCP_CALSEG_AQUIRE_FREE_PAGE_TIMEOUT 500 // 500 ms timeout 155 | 156 | #endif 157 | 158 | // Overrun indication via PID 159 | // Not needed for Ethernet, client detects data loss via transport layer counters 160 | // #define XCP_ENABLE_OVERRUN_INDICATION_PID 161 | 162 | // Clock resolution 163 | // #define XCP_DAQ_CLOCK_32BIT // Use 32 Bit time stamps 164 | #define XCP_DAQ_CLOCK_64BIT // Use 64 Bit time stamps 165 | #if CLOCK_TICKS_PER_S == 1000000 // us 166 | #define XCP_TIMESTAMP_UNIT DAQ_TIMESTAMP_UNIT_1US // unit 167 | #define XCP_TIMESTAMP_TICKS 1 // ticks per unit 168 | #elif CLOCK_TICKS_PER_S == 1000000000 // ns 169 | #define XCP_TIMESTAMP_UNIT DAQ_TIMESTAMP_UNIT_1NS // unit 170 | #define XCP_TIMESTAMP_TICKS 1 // ticks per unit 171 | #else 172 | #ifndef __WRAPPER_H__ // Rust bindgen 173 | #error "Please define clock resolution" 174 | #endif 175 | #endif 176 | 177 | // Grandmaster clock (optional, use XcpSetGrandmasterClockInfo, implement ApplXcpGetClockInfoGrandmaster) 178 | #define XCP_ENABLE_PTP 179 | #define XCP_DAQ_CLOCK_UIID {0xdc, 0xa6, 0x32, 0xFF, 0xFE, 0x7e, 0x66, 0xdc} 180 | 181 | // Enable GET_DAQ_CLOCK_MULTICAST 182 | // Not recommended 183 | // #define XCP_ENABLE_DAQ_CLOCK_MULTICAST 184 | #ifdef XCP_ENABLE_DAQ_CLOCK_MULTICAST 185 | // XCP default cluster id (multicast addr 239,255,0,1, group 127,0,1 (mac 01-00-5E-7F-00-01) 186 | #define XCP_MULTICAST_CLUSTER_ID 1 187 | #endif 188 | 189 | //------------------------------------------------------------------------------- 190 | // Debug 191 | 192 | // Enable extended error checks, performance penalty !!! 193 | #define XCP_ENABLE_TEST_CHECKS 194 | -------------------------------------------------------------------------------- /xcplib/src/xcptl_cfg.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define __XCPTL_CFG_H__ 3 | 4 | /*---------------------------------------------------------------------------- 5 | | File: 6 | | xcptl_cfg.h 7 | | 8 | | Description: 9 | | Parameter configuration for XCP transport layer 10 | | 11 | | Code released into public domain, no attribution required 12 | ----------------------------------------------------------------------------*/ 13 | 14 | #include "main_cfg.h" // for OPTION_xxx 15 | 16 | #if defined(OPTION_ENABLE_UDP) 17 | #define XCPTL_ENABLE_UDP 18 | #endif 19 | #if defined(OPTION_ENABLE_TCP) 20 | #define XCPTL_ENABLE_TCP 21 | #endif 22 | 23 | // Transport layer version 24 | #define XCP_TRANSPORT_LAYER_VERSION 0x0104 25 | 26 | // CTO size 27 | // Maximum size of a XCP command packet (CRO,CRM) 28 | #define XCPTL_MAX_CTO_SIZE (248) // Prefer %8=0 over the maximum value of 255 for better allignment and granularities 29 | 30 | // DTO size 31 | // Maximum size of a XCP data packet (DAQ,STIM) 32 | #define XCPTL_MAX_DTO_SIZE (XCPTL_MAX_SEGMENT_SIZE - 8) // Segment size - XCP transport layer header size, size must be mod 8 33 | // #define XCPTL_MAX_DTO_SIZE (248) // Segment size - XCP transport layer header size, size must be mod 8 34 | 35 | // Segment size is the maximum data buffer size given to sockets send/sendTo, for UDP it is the UDP MTU 36 | // Jumbo frames are supported, but it might be more efficient to use a smaller segment sizes 37 | #ifdef OPTION_MTU 38 | #define XCPTL_MAX_SEGMENT_SIZE (OPTION_MTU - 20 - 8) // UDP MTU (MTU - IP-header - UDP-header) 39 | #else 40 | #error "Please define XCPTL_MAX_SEGMENT_SIZE" 41 | #define XCPTL_MAX_SEGMENT_SIZE (1500 - 20 - 8) 42 | #endif 43 | 44 | // Alignment for packet concatenation 45 | #define XCPTL_PACKET_ALIGNMENT 4 // Packet alignment for multiple XCP transport layer packets in a XCP transport layer message 46 | 47 | // Maximum queue (producer->consumer) event rate (Windows only, Linux uses polling on the consumer side) 48 | #define XCPTL_QUEUE_TRANSMIT_CYCLE_TIME (1 * CLOCK_TICKS_PER_MS) 49 | 50 | // Flush cycle 51 | #define XCPTL_QUEUE_FLUSH_CYCLE_MS 100 // Send a DTO packet at least every x ms, XCPTL_TIMEOUT_INFINITE to turn off 52 | 53 | // Transport layer message header size 54 | // This is fixed, no other options supported 55 | #define XCPTL_TRANSPORT_LAYER_HEADER_SIZE 4 56 | 57 | // Multicast (GET_DAQ_CLOCK_MULTICAST) 58 | // #define XCPTL_ENABLE_MULTICAST 59 | /* 60 | Use multicast time synchronisation to improve synchronisation of multiple XCP slaves 61 | This option is available since XCP V1.3, but using it, needs to create an additional thread and socket for multicast reception 62 | There is no benefit if PTP time synchronized is used or if there is only one XCP device 63 | Older CANape versions expect this option is on by default -> turn it off in device/protocol/event/TIME_CORRELATION_GETDAQCLOCK by changing from "multicast" to "extendedresponse" 64 | */ 65 | #if defined(XCPTL_ENABLE_UDP) || defined(XCPTL_ENABLE_TCP) 66 | #ifdef XCPTL_ENABLE_MULTICAST 67 | // #define XCLTL_RESTRICT_MULTICAST 68 | #define XCPTL_MULTICAST_PORT 5557 69 | #endif 70 | #endif 71 | -------------------------------------------------------------------------------- /xcplib/wrapper.h: -------------------------------------------------------------------------------- 1 | // Source file for bindgen to generate Rust bindings for the XCPlite library xcplib 2 | 3 | #define __WRAPPER_H__ 4 | 5 | #include "xcpAppl.h" 6 | #include "xcpEthServer.h" 7 | #include "xcpEthTl.h" 8 | #include "xcpLite.h" 9 | 10 | // @@@@ temporary for testing 11 | void c_demo(void); 12 | --------------------------------------------------------------------------------