Skip to content

Commit b697c88

Browse files
Merge pull request #118 from FrameworkComputer/usi-stylus
Get information from USI Stylus
2 parents 9670265 + 7205d8f commit b697c88

File tree

6 files changed

+195
-12
lines changed

6 files changed

+195
-12
lines changed

EXAMPLES.md

+20-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ Touchscreen
3333
MPP Protocol: true
3434
```
3535

36+
### Stylus (Framework 12)
37+
38+
```
39+
> sudo framework_tool --versions
40+
[...]
41+
Stylus
42+
Serial Number: 28C1A00-12E71DAE
43+
Vendor ID: 32AC (Framework Computer)
44+
Product ID: 002B (Framework Stylus)
45+
Firmware Version: FF.FF
46+
[...]
47+
```
48+
3649
### Touchpad (Framework 12, Framework 13, Framework 16)
3750

3851
```
@@ -163,7 +176,6 @@ ALS: 76 Lux
163176
Fan Speed: 0 RPM
164177
```
165178

166-
167179
## Check expansion bay (Framework 16)
168180

169181
```
@@ -282,3 +294,10 @@ Battery Status
282294
> sudo framework_tool --charge-rate-limit 80 0.8
283295
> sudo framework_tool --charge-current-limit 80 2000
284296
```
297+
298+
## Stylus (Framework 12)
299+
300+
```
301+
> sudo framework_tool --stylus-battery
302+
Stylus Battery Strength: 77%
303+
```

framework_lib/src/commandline/clap_std.rs

+6
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,11 @@ struct ClapCli {
198198
#[arg(long)]
199199
touchscreen_enable: Option<bool>,
200200

201+
/// Check stylus battery level (USI 2.0 stylus only)
202+
#[clap(value_enum)]
203+
#[arg(long)]
204+
stylus_battery: bool,
205+
201206
/// Get EC console, choose whether recent or to follow the output
202207
#[clap(value_enum)]
203208
#[arg(long)]
@@ -390,6 +395,7 @@ pub fn parse(args: &[String]) -> Cli {
390395
rgbkbd: args.rgbkbd,
391396
tablet_mode: args.tablet_mode,
392397
touchscreen_enable: args.touchscreen_enable,
398+
stylus_battery: args.stylus_battery,
393399
console: args.console,
394400
reboot_ec: args.reboot_ec,
395401
hash: args.hash.map(|x| x.into_os_string().into_string().unwrap()),

framework_lib/src/commandline/mod.rs

+18
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ pub struct Cli {
184184
pub rgbkbd: Vec<u64>,
185185
pub tablet_mode: Option<TabletModeArg>,
186186
pub touchscreen_enable: Option<bool>,
187+
pub stylus_battery: bool,
187188
pub console: Option<ConsoleArg>,
188189
pub reboot_ec: Option<RebootEcArg>,
189190
pub hash: Option<String>,
@@ -336,6 +337,18 @@ fn active_mode(mode: &FwMode, reference: FwMode) -> &'static str {
336337
}
337338
}
338339

340+
#[cfg(feature = "hidapi")]
341+
fn print_stylus_battery_level() {
342+
loop {
343+
if let Some(level) = touchscreen::get_battery_level() {
344+
println!("Stylus Battery Strength: {}%", level);
345+
return;
346+
} else {
347+
debug!("Stylus Battery Strength: Unknown");
348+
}
349+
}
350+
}
351+
339352
fn print_versions(ec: &CrosEc) {
340353
println!("UEFI BIOS");
341354
if let Some(smbios) = get_smbios() {
@@ -816,6 +829,11 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 {
816829
if touchscreen::enable_touch(*_enable).is_none() {
817830
error!("Failed to enable/disable touch");
818831
}
832+
} else if args.stylus_battery {
833+
#[cfg(feature = "hidapi")]
834+
print_stylus_battery_level();
835+
#[cfg(not(feature = "hidapi"))]
836+
error!("Not build with hidapi feature");
819837
} else if let Some(console_arg) = &args.console {
820838
match console_arg {
821839
ConsoleArg::Follow => {

framework_lib/src/commandline/uefi.rs

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ pub fn parse(args: &[String]) -> Cli {
9797
rgbkbd: vec![],
9898
tablet_mode: None,
9999
touchscreen_enable: None,
100+
stylus_battery: false,
100101
console: None,
101102
reboot_ec: None,
102103
hash: None,

framework_lib/src/touchscreen.rs

+112-8
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,24 @@ use crate::touchscreen_win;
55

66
pub const ILI_VID: u16 = 0x222A;
77
pub const ILI_PID: u16 = 0x5539;
8+
const VENDOR_USAGE_PAGE: u16 = 0xFF00;
89
pub const USI_BITMAP: u8 = 1 << 1;
910
pub const MPP_BITMAP: u8 = 1 << 2;
1011

12+
const REPORT_ID_FIRMWARE: u8 = 0x27;
13+
const REPORT_ID_USI_VER: u8 = 0x28;
14+
1115
struct HidapiTouchScreen {
1216
device: HidDevice,
1317
}
1418

1519
impl TouchScreen for HidapiTouchScreen {
16-
fn open_device() -> Option<HidapiTouchScreen> {
17-
debug!("Looking for touchscreen HID device");
20+
fn open_device(target_up: u16, skip: u8) -> Option<HidapiTouchScreen> {
21+
debug!(
22+
"Looking for touchscreen HID device {:X} {}",
23+
target_up, skip
24+
);
25+
let mut skip = skip;
1826
match HidApi::new() {
1927
Ok(api) => {
2028
for dev_info in api.device_list() {
@@ -29,7 +37,7 @@ impl TouchScreen for HidapiTouchScreen {
2937
" Found {:04X}:{:04X} (Usage Page {:04X})",
3038
vid, pid, usage_page
3139
);
32-
if usage_page != 0xFF00 {
40+
if usage_page != target_up {
3341
debug!(" Skipping usage page. Expected {:04X}", 0xFF00);
3442
continue;
3543
}
@@ -40,6 +48,10 @@ impl TouchScreen for HidapiTouchScreen {
4048
debug!(" Found matching touchscreen HID device");
4149
debug!(" Path: {:?}", dev_info.path());
4250
debug!(" IC Type: {:04X}", pid);
51+
if skip > 0 {
52+
skip -= 1;
53+
continue;
54+
}
4355

4456
// Unwrapping because if we can enumerate it, we should be able to open it
4557
let device = dev_info.open_device(&api).unwrap();
@@ -97,16 +109,86 @@ impl TouchScreen for HidapiTouchScreen {
97109
debug!(" Read buf: {:X?}", buf);
98110
Some(buf[msg_len..msg_len + read_len].to_vec())
99111
}
112+
113+
fn get_battery_status(&self) -> Option<u8> {
114+
let mut msg = [0u8; 0x40];
115+
msg[0] = 0x0D;
116+
self.device.read(&mut msg).ok()?;
117+
// println!(" Tip Switch {}%", msg[12]);
118+
// println!(" Barrell Switch: {}%", msg[12]);
119+
// println!(" Eraser: {}%", msg[12]);
120+
// println!(" Invert: {}%", msg[12]);
121+
// println!(" In Range: {}%", msg[12]);
122+
// println!(" 2nd Barrel Switch:{}%", msg[12]);
123+
// println!(" X {}%", msg[12]);
124+
// println!(" Y {}%", msg[12]);
125+
// println!(" Tip Pressure: {}%", msg[12]);
126+
// println!(" X Tilt: {}%", msg[12]);
127+
// println!(" Y Tilt: {}%", msg[12]);
128+
debug!(" Battery Strength: {}%", msg[12]);
129+
debug!(
130+
" Barrel Pressure: {}",
131+
u16::from_le_bytes([msg[13], msg[14]])
132+
);
133+
debug!(" Transducer Index: {}", msg[15]);
134+
135+
if msg[12] == 0 {
136+
None
137+
} else {
138+
Some(msg[12])
139+
}
140+
}
141+
142+
fn get_stylus_fw(&self) -> Option<()> {
143+
let mut msg = [0u8; 0x40];
144+
msg[0] = REPORT_ID_USI_VER;
145+
self.device.get_feature_report(&mut msg).ok()?;
146+
let usi_major = msg[2];
147+
let usi_minor = msg[3];
148+
debug!("USI version (Major.Minor): {}.{}", usi_major, usi_minor);
149+
150+
if usi_major != 2 || usi_minor != 0 {
151+
// Probably not USI mode
152+
return None;
153+
}
154+
155+
let mut msg = [0u8; 0x40];
156+
msg[0] = REPORT_ID_FIRMWARE;
157+
self.device.get_feature_report(&mut msg).ok()?;
158+
let sn_low = u32::from_le_bytes([msg[2], msg[3], msg[4], msg[5]]);
159+
let sn_high = u32::from_le_bytes([msg[6], msg[7], msg[8], msg[9]]);
160+
let vid = u16::from_le_bytes([msg[14], msg[15]]);
161+
let vendor = if vid == 0x32AC {
162+
" (Framework Computer)"
163+
} else {
164+
""
165+
};
166+
let pid = u16::from_le_bytes([msg[16], msg[17]]);
167+
let product = if pid == 0x002B {
168+
" (Framework Stylus)"
169+
} else {
170+
""
171+
};
172+
println!("Stylus");
173+
println!(" Serial Number: {:X}-{:X}", sn_high, sn_low);
174+
debug!(" Redundant SN {:X?}", &msg[10..14]);
175+
println!(" Vendor ID: {:04X}{}", vid, vendor);
176+
println!(" Product ID: {:04X}{}", pid, product);
177+
println!(" Firmware Version: {:02X}.{:02X}", &msg[18], msg[19]);
178+
179+
Some(())
180+
}
100181
}
101182

102183
pub trait TouchScreen {
103-
fn open_device() -> Option<Self>
184+
fn open_device(usage_page: u16, skip: u8) -> Option<Self>
104185
where
105186
Self: std::marker::Sized;
106187
fn send_message(&self, message_id: u8, read_len: usize, data: Vec<u8>) -> Option<Vec<u8>>;
107188

108189
fn check_fw_version(&self) -> Option<()> {
109190
println!("Touchscreen");
191+
110192
let res = self.send_message(0x42, 3, vec![0])?;
111193
let ver = res
112194
.iter()
@@ -135,22 +217,44 @@ pub trait TouchScreen {
135217
self.send_message(0x38, 0, vec![!enable as u8, 0x00])?;
136218
Some(())
137219
}
220+
221+
fn get_stylus_fw(&self) -> Option<()>;
222+
fn get_battery_status(&self) -> Option<u8>;
223+
}
224+
225+
pub fn get_battery_level() -> Option<u8> {
226+
for skip in 0..5 {
227+
if let Some(device) = HidapiTouchScreen::open_device(0x000D, skip) {
228+
if let Some(level) = device.get_battery_status() {
229+
return Some(level);
230+
}
231+
}
232+
}
233+
None
138234
}
139235

140236
pub fn print_fw_ver() -> Option<()> {
237+
for skip in 0..5 {
238+
if let Some(device) = HidapiTouchScreen::open_device(0x000D, skip) {
239+
if device.get_stylus_fw().is_some() {
240+
break;
241+
}
242+
}
243+
}
244+
141245
#[cfg(target_os = "windows")]
142-
let device = touchscreen_win::NativeWinTouchScreen::open_device()?;
246+
let device = touchscreen_win::NativeWinTouchScreen::open_device(VENDOR_USAGE_PAGE, 0)?;
143247
#[cfg(not(target_os = "windows"))]
144-
let device = HidapiTouchScreen::open_device()?;
248+
let device = HidapiTouchScreen::open_device(VENDOR_USAGE_PAGE, 0)?;
145249

146250
device.check_fw_version()
147251
}
148252

149253
pub fn enable_touch(enable: bool) -> Option<()> {
150254
#[cfg(target_os = "windows")]
151-
let device = touchscreen_win::NativeWinTouchScreen::open_device()?;
255+
let device = touchscreen_win::NativeWinTouchScreen::open_device(VENDOR_USAGE_PAGE, 0)?;
152256
#[cfg(not(target_os = "windows"))]
153-
let device = HidapiTouchScreen::open_device()?;
257+
let device = HidapiTouchScreen::open_device(VENDOR_USAGE_PAGE, 0)?;
154258

155259
device.enable_touch(enable)
156260
}

framework_lib/src/touchscreen_win.rs

+38-3
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,20 @@ use windows::{
1616
},
1717
};
1818

19+
const REPORT_ID_FIRMWARE: u8 = 0x27;
20+
const REPORT_ID_USI_VER: u8 = 0x28;
21+
1922
pub struct NativeWinTouchScreen {
2023
handle: HANDLE,
2124
}
2225

2326
impl TouchScreen for NativeWinTouchScreen {
24-
fn open_device() -> Option<Self> {
25-
debug!("Looking for touchscreen HID device");
27+
fn open_device(target_up: u16, skip: u8) -> Option<Self> {
28+
debug!(
29+
"Looking for touchscreen HID device {:X} {}",
30+
target_up, skip
31+
);
32+
let mut skip = skip;
2633
match HidApi::new() {
2734
Ok(api) => {
2835
for dev_info in api.device_list() {
@@ -37,7 +44,7 @@ impl TouchScreen for NativeWinTouchScreen {
3744
" Found {:04X}:{:04X} (Usage Page {:04X})",
3845
vid, pid, usage_page
3946
);
40-
if usage_page != 0xFF00 {
47+
if usage_page != target_up {
4148
debug!(" Skipping usage page. Expected {:04X}", 0xFF00);
4249
continue;
4350
}
@@ -48,6 +55,10 @@ impl TouchScreen for NativeWinTouchScreen {
4855
debug!(" Found matching touchscreen HID device");
4956
debug!(" Path: {:?}", dev_info.path());
5057
debug!(" IC Type: {:04X}", pid);
58+
if skip > 0 {
59+
skip -= 1;
60+
continue;
61+
}
5162

5263
// TODO: Enumerate with windows
5364
// Should enumerate and find the right one
@@ -135,4 +146,28 @@ impl TouchScreen for NativeWinTouchScreen {
135146

136147
Some(buf[msg_len..msg_len + read_len].to_vec())
137148
}
149+
150+
fn get_stylus_fw(&self) -> Option<()> {
151+
let mut msg = [0u8; 0x40];
152+
msg[0] = REPORT_ID_FIRMWARE;
153+
unsafe {
154+
let success = HidD_GetFeature(self.handle, msg.as_mut_ptr() as _, msg.len() as u32);
155+
debug!(" Success: {}", success);
156+
}
157+
println!("Stylus firmware: {:X?}", msg);
158+
159+
let mut msg = [0u8; 0x40];
160+
msg[0] = REPORT_ID_USI_VER;
161+
unsafe {
162+
let success = HidD_GetFeature(self.handle, msg.as_mut_ptr() as _, msg.len() as u32);
163+
debug!(" Success: {}", success);
164+
}
165+
println!("USI Version: {:X?}", msg);
166+
167+
None
168+
}
169+
fn get_battery_status(&self) -> Option<u8> {
170+
error!("Get stylus battery status not supported on Windows");
171+
None
172+
}
138173
}

0 commit comments

Comments
 (0)