Skip to content

Commit 4d8efe8

Browse files
committed
Add axserialport wrapper and example
1 parent fd005ce commit 4d8efe8

File tree

14 files changed

+756
-0
lines changed

14 files changed

+756
-0
lines changed

Cargo.lock

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ axevent = { path = "crates/axevent" }
5252
axevent-sys = { path = "crates/axevent-sys" }
5353
axparameter = { path = "crates/axparameter" }
5454
axparameter-sys = { path = "crates/axparameter-sys" }
55+
axserialport = { path = "crates/axserialport" }
56+
axserialport-sys = { path = "crates/axserialport-sys" }
5557
axstorage = { path = "crates/axstorage" }
5658
axstorage-sys = { path = "crates/axstorage-sys" }
5759
bbox = { path = "crates/bbox" }

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,11 @@ check_tests:
241241

242242
## Attempt to fix formatting automatically
243243
fix_format:
244+
find apps/axserialport_example crates/axserialport -type f -name '*.rs' \
245+
| xargs rustfmt \
246+
--config imports_granularity=Crate \
247+
--config group_imports=StdExternalCrate \
248+
--edition 2021
244249
cargo fmt
245250
.PHONY: fix_format
246251

apps/axserialport_example/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "axserialport_example"
3+
version = "0.0.0"
4+
edition.workspace = true
5+
publish = false
6+
7+
[dependencies]
8+
anyhow = { workspace = true }
9+
glib = { workspace = true }
10+
log = { workspace = true }
11+
glib-sys = { workspace = true }
12+
13+
acap-logging = { workspace = true }
14+
axserialport = { workspace = true }
15+
libc = "0.2.158"
16+
17+
[features]
18+
default = ["acap-logging/default"]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"schemaVersion": "1.7.0",
3+
"acapPackageConf": {
4+
"setup": {
5+
"appName": "axserialport_example",
6+
"vendor": "Axis Communications",
7+
"runMode": "never",
8+
"version": "0.0.0"
9+
}
10+
},
11+
"resources": {
12+
"linux": {
13+
"user": {
14+
"groups": [
15+
"admin"
16+
]
17+
}
18+
}
19+
}
20+
}

apps/axserialport_example/src/main.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#![forbid(unsafe_code)]
2+
3+
use std::time::Instant;
4+
5+
use anyhow::Context;
6+
use axserialport::{
7+
gio::{Condition, IOChannel},
8+
BaudRate, Config, DataBits, Enable, Parity, PortMode, StopBits,
9+
};
10+
use glib::MainLoop;
11+
use libc::{SIGINT, SIGTERM};
12+
use log::{error, info};
13+
14+
fn incoming_data(channel: &mut IOChannel, _condition: Condition) -> glib::ControlFlow {
15+
match channel.read_chars(2) {
16+
Ok(timestamp) => {
17+
let min = timestamp[0];
18+
let sec = timestamp[1];
19+
info!("incoming_data() timestamp: {min:02}:{sec:02}");
20+
}
21+
Err(e) => {
22+
error!("{}", e.message());
23+
}
24+
}
25+
glib::ControlFlow::Continue
26+
}
27+
28+
fn send_timer_data(channel: &mut IOChannel, timer: Instant) -> glib::ControlFlow {
29+
let elapsed = timer.elapsed().as_secs();
30+
let minutes = elapsed / 60;
31+
let seconds = elapsed % 60;
32+
33+
match channel.write_chars(&[minutes as u8, seconds as u8]) {
34+
Ok(bytes_written) => {
35+
let status = channel.flush().unwrap();
36+
info!("send_timer_data() wrote {bytes_written} bytes, status: {status:?}");
37+
}
38+
Err(e) => {
39+
error!("{}", e.message());
40+
}
41+
}
42+
glib::ControlFlow::Continue
43+
}
44+
45+
fn main() -> anyhow::Result<()> {
46+
acap_logging::init_logger();
47+
48+
let main_loop = MainLoop::new(None, false);
49+
glib::unix_signal_add_once(SIGTERM, {
50+
let main_loop = main_loop.clone();
51+
move || main_loop.quit()
52+
});
53+
glib::unix_signal_add_once(SIGINT, {
54+
let main_loop = main_loop.clone();
55+
move || main_loop.quit()
56+
});
57+
58+
info!("Starting AxSerialPort application");
59+
60+
// Config example (product dependent) see product datasheet.
61+
let mut config = Config::try_new(0)?;
62+
config
63+
.port_enable(Enable::Enable)?
64+
.baudrate(BaudRate::B19200)?
65+
.bias(Enable::Disable)?
66+
.databits(DataBits::Eight)?
67+
.parity(Parity::None)?
68+
.portmode(PortMode::Rs485Four)?
69+
.stopbits(StopBits::One)?
70+
.termination(Enable::Disable)?
71+
.sync()?;
72+
73+
let fd = config.get_fd()?;
74+
75+
let mut iochannel = IOChannel::from_borrowed_fd(fd).context("Failed to get channel")?;
76+
iochannel.set_encoding(None)?;
77+
78+
let timer = Instant::now();
79+
80+
// Add a watch that waits for incoming data, then calls 'incoming_data()'
81+
// when the conditions are met.
82+
iochannel.watch_local(Condition::In, incoming_data);
83+
84+
// Periodically call 'send_timer_data()' every 10 seconds
85+
glib::timeout_add_seconds_local(10, move || send_timer_data(&mut iochannel, timer));
86+
87+
main_loop.run();
88+
89+
info!("Finish AXSerialPort application");
90+
Ok(())
91+
}

crates/axserialport-sys/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
build = "build.rs"
3+
name = "axserialport-sys"
4+
version = "0.0.0"
5+
edition.workspace = true
6+
license = "MIT"
7+
8+
[build-dependencies]
9+
bindgen = { workspace = true }
10+
pkg-config = { workspace = true }
11+
12+
[dependencies]
13+
glib-sys = { workspace = true }

crates/axserialport-sys/build.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use bindgen::callbacks::{EnumVariantValue, ParseCallbacks};
2+
use std::{env, path};
3+
4+
fn to_title_case(name: &str) -> String {
5+
name.split('_')
6+
.map(|word| {
7+
let mut chars = word.chars();
8+
match chars.next() {
9+
Some(first) => {
10+
first.to_uppercase().collect::<String>()
11+
+ &chars.collect::<String>().to_lowercase()
12+
}
13+
None => String::new(),
14+
}
15+
})
16+
.collect::<Vec<_>>()
17+
.join("")
18+
}
19+
20+
#[derive(Debug)]
21+
struct MyParseCallbacks;
22+
23+
impl ParseCallbacks for MyParseCallbacks {
24+
fn enum_variant_name(
25+
&self,
26+
enum_name: Option<&str>,
27+
original_variant_name: &str,
28+
variant_value: EnumVariantValue,
29+
) -> Option<String> {
30+
if let Some(enum_name) = enum_name {
31+
let new_variant_name = match enum_name {
32+
"AXSerialParity" => original_variant_name.trim_start_matches("AX_SERIAL_PARITY_"),
33+
"AXSerialPortmode" => match original_variant_name {
34+
"AX_SERIAL_RS485_4" => "RS485_FOUR",
35+
_ => original_variant_name.trim_start_matches("AX_SERIAL_"),
36+
},
37+
"AXSerialDatabits" => match variant_value {
38+
EnumVariantValue::Boolean(_) => panic!(),
39+
EnumVariantValue::Signed(_) => panic!(),
40+
EnumVariantValue::Unsigned(n) => match n {
41+
7 => "Seven",
42+
8 => "Eight",
43+
_ => panic!(),
44+
},
45+
},
46+
"AXSerialStopbits" => match variant_value {
47+
EnumVariantValue::Boolean(_) => panic!(),
48+
EnumVariantValue::Signed(_) => panic!(),
49+
EnumVariantValue::Unsigned(n) => match n {
50+
1 => "One",
51+
2 => "Two",
52+
_ => panic!(),
53+
},
54+
},
55+
_ => original_variant_name.trim_start_matches("AX_SERIAL_"),
56+
};
57+
Some(to_title_case(new_variant_name))
58+
} else {
59+
None
60+
}
61+
}
62+
}
63+
fn populated_bindings(dst: &path::PathBuf) {
64+
let library = pkg_config::Config::new().probe("axserialport").unwrap();
65+
let mut bindings = bindgen::Builder::default()
66+
.header("wrapper.h")
67+
.rustified_enum(".*")
68+
.generate_comments(false)
69+
.allowlist_recursively(false)
70+
.allowlist_function("^(ax_.*)$")
71+
.allowlist_type("^(_?AX.*)$")
72+
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
73+
.parse_callbacks(Box::new(MyParseCallbacks))
74+
.layout_tests(false);
75+
for path in library.include_paths {
76+
bindings = bindings.clang_args(&["-I", path.to_str().unwrap()]);
77+
}
78+
bindings.generate().unwrap().write_to_file(dst).unwrap();
79+
}
80+
81+
fn main() {
82+
let dst = path::PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs");
83+
if env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default() != "x86_64" {
84+
populated_bindings(&dst);
85+
}
86+
}

0 commit comments

Comments
 (0)