Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
This commit is contained in:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1,67 @@
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test")
load("@everest_crate_index//:defs.bzl", "all_crate_deps")
load("@rules_rust//cargo:defs.bzl", "cargo_build_script")
cargo_build_script(
name = "build_script",
srcs = ["build.rs"],
edition="2021",
build_script_env = {
"EVEREST_CORE_ROOT": "../../../..",
},
data = [
"manifest.yaml",
"@everest-core//errors",
"@everest-core//interfaces",
"@everest-core//types",
],
deps = all_crate_deps(build = True) + [
"//lib/everest/framework/everestrs/everestrs-build",
],
)
rust_binary(
name = "RsIskraMeterBinary",
srcs = glob(["src/*.rs"]),
edition="2021",
proc_macro_deps = all_crate_deps(proc_macro = True),
visibility = ["//visibility:public"],
deps = all_crate_deps(normal = True) + [
":build_script",
"//lib/everest/framework/everestrs/everestrs",
"//lib/everest/framework/everestrs/everestrs:everestrs_sys",
"//lib/everest/framework/everestrs/everestrs:everestrs_bridge",
],
)
rust_test(
name = "RsIskraMeterTest",
edition="2021",
srcs = [],
crate_features = ["mockall", "mockall_double"],
crate = ":RsIskraMeterBinary",
)
binary = ":RsIskraMeterBinary"
manifest = ":manifest.yaml"
name = "RsIskraMeter"
genrule(
name = "copy_to_subdir",
srcs = [binary, manifest],
outs = [
"{}/manifest.yaml".format(name),
"{}/{}".format(name, name),
],
cmd = "mkdir -p $(RULEDIR)/{} && ".format(name) +
"cp $(location {}) $(RULEDIR)/{}/{} && ".format(binary, name, name) +
"cp $(location {}) $(RULEDIR)/{}/".format(manifest, name),
)
filegroup(
name = name,
srcs = [
":copy_to_subdir",
],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,20 @@
[package]
name = "RsIskraMeter"
version = "0.1.0"
edition = "2021"
[build-dependencies]
everestrs-build = { workspace=true }
[dependencies]
anyhow = "1.0.75"
backon = "1.2.0"
chrono = "0.4.31"
everestrs = { workspace=true }
log = "0.4.20"
rand = "0.8.5"
mockall = { version = "0.12.1", optional = true }
mockall_double = { version = "0.3.1", optional = true}
[features]
default = ["mockall", "mockall_double"]

View File

@@ -0,0 +1,65 @@
description: Iskra meter
config:
powermeter_device_id:
description: The address of the device on the modbus
default: 33
type: integer
ocmf_format_version:
description: Version of the data format in the representation.
default: "1.0"
type: string
ocmf_gateway_identification:
description: >-
Identifier of the manufacturer for the system which has generated the
present data.
default: ""
type: string
ocmf_gateway_serial:
description: Serial number of the above mentioned system.
default: ""
type: string
ocmf_gateway_version:
description: >-
Version designation of the manufacturer for the software.
default: ""
type: string
ocmf_charge_point_identification_type:
description: >-
Type of the specification for the identification of the charge point.
default: "EVSEID"
type: string
ocmf_charge_point_identification:
description: >-
Identification information for the charge point. If you set it to `EVSEID`
it will be overwritten by the evse-id provided by `TransactionReq`
default: ""
type: string
communication_errors_threshold:
description: The maximum number of consecutive errors allowed before a persistent error is reported
default: 10
type: integer
lcd_main_text:
description: Text to display when there is no transaction. At most 8 chars.
type: string
default: ""
lcd_label_text:
description: Label to display when there is no transaction. At most 4 chars.
type: string
default: ""
read_meter_values_interval_ms:
description: How often to read the meter values in milliseconds. The refresh rate of WM3M4C is ~1 second.
type: integer
default: 5000
minimum: 1000
provides:
meter:
description: Implementation of the driver functionality
interface: powermeter
requires:
modbus:
interface: serial_communication_hub
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- embedded@qwello.eu
enable_external_mqtt: false

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,350 @@
use anyhow::Result;
use rand::Rng;
pub fn to_8_string(input: &[u16]) -> Result<String> {
let u8_slice = input.iter().flat_map(|&x| u16::to_be_bytes(x)).collect();
Ok(String::from_utf8(u8_slice)?.trim().to_string())
}
pub fn counter(regs: [u16; 2], exp: u16) -> f64 {
let measurement = (regs[0] as i32) << 16 | ((regs[1] as i32) & 0xFFFF);
measurement as f64 * 1.0e1_f64.powi(exp as i32)
}
/// Unsigned Measurement (32 bit)
/// Decade Exponent (Signed 8 bit)
/// Binary Signed value (24 bit)
pub fn from_t5_format(regs: [u16; 2]) -> f64 {
let exp = ((regs[0] >> 8) & 0xFF) as i8;
let value = (regs[0] & 0xFF) as u8;
let measurement = ((value as u32) << 16) | ((regs[1] as u32) & 0xFFFF);
measurement as f64 * 1.0e1_f64.powi(exp as i32)
}
/// Signed Measurement (32 bit)
/// Decade Exponent (Signed 8 bit)
/// Binary Signed value (24 bit)
pub fn from_t6_format(regs: [u16; 2]) -> f64 {
let exp = ((regs[0] >> 8) & 0xFF) as i8;
let value = (regs[0] & 0xFF) as i8;
let measurement = ((value as i32) << 16) | ((regs[1] as i32) & 0xFFFF);
measurement as f64 * 1.0e1_f64.powi(exp as i32)
}
pub fn string_to_vec(input: &str) -> Vec<u16> {
input
.as_bytes()
.chunks(2)
.map(|chunk| {
let mut value = (chunk[0] as u16) << 8;
if chunk.len() > 1 {
value |= chunk[1] as u16 & 0xFF;
}
value
})
.collect()
}
pub fn create_random_meter_session_id() -> String {
let start = format!("{:06X}", rand::thread_rng().gen_range(0..=0xFFFFFF));
let hex_time = format!(
"{:X}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
);
let end = format!("{:06X}", rand::thread_rng().gen_range(0..=0xFFFFFF));
start + &hex_time + &end
}
pub fn to_hex_string(input: Vec<u16>) -> String {
input
.into_iter()
.flat_map(|value| value.to_be_bytes())
.map(|value| format!("{value:02X}"))
.collect::<Vec<_>>()
.concat()
}
/// The Iskra firmware has a bug where we must remove leading zeros from the r
/// and s segments if the first non-zero byte is smaller than 0x80.
///
/// If we remove any zeros we have to correct the lengths of the entire signature
/// and the affected r/s components.
///
/// The signature looks like
/// 0x30, 0x44, 0x02, 0x20 ... 0x02, 0x20, ...
/// | | | | | | length of the r/s component
/// | | | | | start of the r/s component
/// | | | | length of the r/s component.
/// | | | start of r/s component.
/// | | signature length
/// | start
pub fn to_signature(input: Vec<u16>) -> String {
// The words contain two bytes - split them into bytes.
let input: Vec<u8> = input
.iter()
.flat_map(|w| w.to_be_bytes().to_vec())
.map(|c| c as u8)
.collect();
fn to_hex_string_from_bytes(bytes: &[u8]) -> String {
bytes
.iter()
.map(|value| format!("{value:02X}"))
.collect::<Vec<_>>()
.concat()
}
// Check if we have the first two bytes.
if input.len() < 2 {
log::warn!("Signature too short, aborting");
return to_hex_string_from_bytes(&input);
}
// Check if the first element is 0x30
if input[0] != 0x30 {
log::warn!(
"Signature starts with {} instead of 0x30, aborting",
input[0]
);
return to_hex_string_from_bytes(&input);
}
// Check the integrity of the signature
if input.len() != input[1] as usize + 2 {
log::warn!(
"Signature length mismatch: Expected {} received {}, aborting",
input.len(),
input[1] as usize + 2
);
return to_hex_string_from_bytes(&input);
}
let mut output = Vec::new();
output.reserve(input.len());
output.extend_from_slice(&input[0..2]);
let mut current_pos = 2;
// Process sub-components
while current_pos < input.len() {
if input[current_pos] != 2 {
log::warn!(
"Sub-component starts with {} instead of 2, aborting",
input[current_pos]
);
return to_hex_string_from_bytes(&input);
}
output.push(2);
current_pos += 1;
if current_pos >= input.len() {
log::warn!("No length element, aborting");
return to_hex_string_from_bytes(&input);
}
// Get the length of the current sub-component
let sub_component_length = input[current_pos] as usize;
if sub_component_length == 0 {
log::warn!("Sub-component length is 0");
return to_hex_string_from_bytes(&input);
}
current_pos += 1;
// Check if we have enough u16 values for the sub-component
if current_pos + sub_component_length > input.len() {
log::warn!("Sub-component length exceeds available u16 values, aborting");
return to_hex_string_from_bytes(&input);
}
// Extract the sub-component
let mut sub_component = &input[current_pos..current_pos + sub_component_length];
// Process sub-component. Find the first non-zero value.
let non_zero_idx = sub_component
.iter()
.position(|&value| value != 0)
.unwrap_or(sub_component.len() - 1);
let non_zero_value = sub_component[non_zero_idx];
// If there is a non-zero value, check if it is less than 0x80.
if non_zero_value < 0x80 {
log::info!("Removed {} leading zeros from sub-component", non_zero_idx);
sub_component = &sub_component[non_zero_idx..];
}
output.push(sub_component.len() as u8);
output.extend_from_slice(&sub_component);
current_pos += sub_component_length;
}
// Update the signature length (2nd u8) if any zeros were removed
output[1] = (output.len() - 2) as u8;
to_hex_string_from_bytes(&output)
}
pub fn create_ocmf(signed_meter_values: String, signature: String) -> String {
format!("OCMF|{}|{{\"SD\":\"{}\"}}", signed_meter_values, signature)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
/// Tests for the `to_8_string` conversion.
fn test__to_8_string() {
let parameter = [
(vec![], ""),
(
vec![
u16::from_be_bytes([b' ', b'\r']),
u16::from_be_bytes([b'h', b'e']),
u16::from_be_bytes([b'l', b'l']),
u16::from_be_bytes([b'o', b' ']),
],
"hello",
),
(vec![u16::from_be_bytes([b' ', b'a']); 5], "a a a a a"),
(vec![u16::from_be_bytes([b' ', b' ']); 5], ""),
];
for (input, expected) in parameter {
let output = to_8_string(&input).unwrap();
assert_eq!(&output, expected);
}
}
#[test]
/// Tests the `counter` conversion.
fn test__counter() {
let parameters = [
([0, 0], 1, 0.0),
([0, 1234], 0, 1234.0),
([0, 1234], 1, 12340.0),
([16, 0], 0, (16 << 16) as f64),
];
for (input_reg, input_exp, expected) in parameters {
assert_eq!(counter(input_reg, input_exp), expected);
}
}
#[test]
/// Tests the `from_t5_format` and `from_t6_format` conversionss.
fn test__from_tx_format() {
let parameters = [
([0, 0], 0.0),
([0, 1234], 1234.0),
([u16::from_be_bytes([3, 2]), 0], (2 << 16) as f64 * 1000.0),
];
for (input, expected) in parameters {
assert_eq!(from_t5_format(input), expected);
assert_eq!(from_t6_format(input), expected);
}
}
#[test]
fn test__string_to_vec() {
let parameters = [
("", vec![]),
(
"hello",
vec![
u16::from_be_bytes([b'h', b'e']),
u16::from_be_bytes([b'l', b'l']),
(b'o' as u16) << 8,
],
),
(
"test",
vec![
u16::from_be_bytes([b't', b'e']),
u16::from_be_bytes([b's', b't']),
],
),
];
for (input, expected) in parameters {
assert_eq!(string_to_vec(input), expected);
}
}
#[test]
fn test__to_hex_string() {
let parameters = [
(vec![], ""),
(vec![0xdead, 0xbeef], "DEADBEEF"),
(vec![0xdead, 0xbe], "DEAD00BE"),
];
for (input, expected) in parameters {
assert_eq!(to_hex_string(input), expected);
}
}
#[test]
fn test__correct_signature() {
fn hex_string_to_vec(hex: &str) -> Vec<u16> {
hex.as_bytes()
.chunks(4)
.map(|chunk| {
let hex_str = std::str::from_utf8(chunk).unwrap();
u16::from_str_radix(hex_str, 16).unwrap()
})
.collect()
}
// Test case with specific hex strings
for (input_hex, expected_hex) in [
// No zeros.
("30440220015d226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce402206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49",
"30440220015d226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce402206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49"),
// In the first segment 1 byte with zero.
("30440220005d226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce402206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49",
"3043021f5d226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce402206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49"),
// In the first segment 2 bytes with zero.
("304402200000226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce402206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49",
"3042021e226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce402206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49"),
// In the first segment 1 bytes with zero but second element is valid
("304402200080226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce402206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49",
"304402200080226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce402206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49"),
// In the second segment 3 bytes with zero.
("304402200080226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce4022000000016da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49",
"304102200080226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce4021d16da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49"),
// In the first segment all zeros
("30440220000000000000000000000000000000000000000000000000000000000000000002206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49",
"302502010002206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49"),
// In both are zeros.
("30440220005d226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce40220007b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49",
"3042021f5d226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce4021f7b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49"),
// No second sub-component
("30220220005d226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce4",
"3021021f5d226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce4"),
// The reference case used for the negative tests below. This makes
// sure that we will modify the input if it would not contain errors.
("300402020001", "3003020101"),
] {
// Convert hex strings to Vec<u16>
let input = hex_string_to_vec(input_hex);
// let expected = hex_string_to_vec(expected_hex);
assert_eq!(to_signature(input).to_uppercase(), expected_hex.to_uppercase(), "{}", input_hex);
}
// Test cases with invalid input
for input in [
"300302020001", // Not the right length overall. 3 is Wrong.
"310402020001", // Wrong start.
"300403020001", // Sub-component not started by 2. 3 is Wrong.
"300402030001", // Sub-component wrong length (too short) - should be 2.
"300402000001", // Sub-component wrong length (zeros) - should be 2.
"300402050001", // Sub-component wrong length (too long) - should be 2.
] {
let expected = hex_string_to_vec(input);
assert_eq!(to_signature(expected.clone()), input);
}
}
}