Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "FreePPS"
version = "1.6.0"
version = "1.7.0"
edition = "2024"
authors = ["Seyud"]

Expand All @@ -23,4 +23,3 @@ opt-level = 1
debug = true
lto = false
codegen-units = 256

7 changes: 5 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,19 @@

FreePPS 是一个专为米系设备设计的模块,能够**解锁并启用公版 PPS(Programmable Power Supply)快充协议支持**,让你的设备享受更好的兼容性!

> ⚠️ **重要注意事项**:锁定PPS支持模式,部分机型将**自动关闭MIPPS(小米私有PPS协议)支持**,两种协议存在冲突,无法同时启用。请根据您的充电设备选择适合的协议。
> ⚠️ **重要注意事项**:自动识别目前仅在高通平台验证。联发科平台选择自动模式时仍按“锁定公版 PPS”处理。

> 💡 **特别感谢**:酷安@低线阻狂魔、酷安@花橋桥 提供的节点

## 🚀 主要功能

- ✅ **PPS协议解锁** - 启用公版PPS快充支持
- 🔄 **双模切换** - 通过模块操作按钮进行开/关切换
- 🔄 **自动协议识别(高通)** - 插入时优先尝试小米协议;若只检测到公版 PD PPS,则自动执行一次软件重连并启用公版 PPS
- 🎛️ **三种模式** - 模块操作按钮依次切换“小米协议优先 → 锁定公版 PPS → 自动识别”
- 🔛 **临时控制** - 使用模块开关进行兼容性切换PPS支持状态

自动模式仅在每次插入后的短暂协商阶段使用定时器,空闲时由内核事件唤醒,不进行周期轮询。为保证下一次插入能优先完成小米认证,请保持自动模式并在切换充电器前正常拔出当前充电器。


## 🙏 致谢

Expand Down
7 changes: 5 additions & 2 deletions docs/en/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@

FreePPS is a module specifically designed for Xiaomi/Mi devices that can **unlock and enable public PPS (Programmable Power Supply) fast charging protocol support**, giving your device better compatibility!

> ⚠️ **Important Note**: After enabling public PPS support, some devices will **automatically disable MIPPS (Xiaomi proprietary PPS protocol) support**, the two protocols conflict and cannot be enabled simultaneously. Please choose the appropriate protocol based on your charging device.
> ⚠️ **Important Note**: Automatic detection is currently verified only on Qualcomm devices. On MediaTek, Automatic mode continues to behave like Force Public PPS.

> 💡 **Special Thanks**: Node provided by Coolapk @低线阻狂魔 and Coolapk @花橋桥

## 🚀 Key Features

- ✅ **PPS Protocol Unlock** - Enable public PPS fast charging support
- 🔄 **Two-state Toggle** - On/Off switching through module operation buttons
- 🔄 **Automatic protocol detection (Qualcomm)** - Prefer Xiaomi charging on attachment, then perform one software reconnect for public-PPS-only chargers
- 🎛️ **Three modes** - The module action button cycles through Xiaomi Priority, Force Public PPS, and Automatic Detection
- 🔛 **Temporary Control** - Use module switches for compatibility switching of PPS support status

Automatic mode uses deadlines only during the brief negotiation after attachment. While idle, it sleeps until a kernel event instead of polling. Leave the module in Automatic mode and disconnect normally before changing chargers so the next attachment starts with Xiaomi authentication enabled.

## 🙏 Acknowledgments

- **Coolapk @低线阻狂魔**, **Coolapk @花橋桥** - Node
Expand Down
8 changes: 7 additions & 1 deletion module/action.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

MODDIR=${0%/*}
FREE_FILE="$MODDIR/free"
AUTO_FILE="$MODDIR/auto"

if [ -f "$FREE_FILE" ]; then
FREE_VALUE=$(cat "$FREE_FILE" | tr -d '[:space:]')
Expand All @@ -10,11 +11,16 @@ else
fi

if [ "$FREE_VALUE" = "0" ]; then
rm -f "$AUTO_FILE"
printf "1" > "$FREE_FILE"
echo "✅锁定PPS支持⚡"
elif [ ! -f "$AUTO_FILE" ]; then
touch "$AUTO_FILE"
echo "🔄协议自动识别💡"
else
printf "0" > "$FREE_FILE"
echo "⏸️PPS已暂停💤"
rm -f "$AUTO_FILE"
echo "⏸️小米协议优先💤"
fi

sleep 0.3
Expand Down
1 change: 1 addition & 0 deletions module/auto
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Binary file modified module/bin/FreePPS
Binary file not shown.
5 changes: 2 additions & 3 deletions module/customize.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ MODDIR=${0%/*}
# Function to detect key press (Volume Up or Volume Down) or timeout
detect_key_press() {
timeout_seconds=6
line=$(timeout "$timeout_seconds" getevent -ql | awk '/KEY_VOLUME/ {print; exit}')

read -r -t $timeout_seconds line < <(getevent -ql | awk '/KEY_VOLUME/ {print; exit}')

if [ $? -eq 142 ]; then
if [ -z "$line" ]; then
echo "[!] No key pressed within $timeout_seconds seconds. Skipping installation..."
return 1
fi
Expand Down
4 changes: 2 additions & 2 deletions module/module.prop
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
id=FreePPS
name=FreePPS
version=v1.6.0
versionCode=20260128
version=v1.7.0
versionCode=20260816
author=酷安@瓦力喀/GitHub@Seyud
description=解锁并启用米系设备的公版 PPS 快充协议支持。(感谢“酷安@低线阻狂魔”、“酷安@花橋桥”提供节点)
updateJson=https://gitee.com/Seyud/FreePPS/raw/master/Update.json
4 changes: 4 additions & 0 deletions src/common/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ pub const IN_DELETE: u32 = 0x00000200;
#[cfg(unix)]
pub const MODULE_BASE_PATH: &str = "/data/adb/modules/FreePPS";
pub const FREE_FILE: &str = "/data/adb/modules/FreePPS/free";
pub const AUTO_FILE: &str = "/data/adb/modules/FreePPS/auto";
pub const DISABLE_FILE: &str = "/data/adb/modules/FreePPS/disable";
#[cfg(unix)]
pub const MODULE_PROP: &str = "/data/adb/modules/FreePPS/module.prop";
pub const PD_VERIFIED_PATH: &str = "/sys/class/qcom-battery/pd_verifed";
pub const PD_ADAPTER_VERIFIED_PATH: &str = "/sys/class/Charging_Adapter/pd_adapter/usbpd_verifed";
pub const BATTERY_STATUS_PATH: &str = "/sys/class/power_supply/battery/status";
pub const INPUT_SUSPEND_PATH: &str = "/sys/class/qcom-battery/input_suspend";
pub const TYPEC_MODE_PATH: &str = "/sys/class/qcom-battery/typec_mode";
pub const USB_REAL_TYPE_PATH: &str = "/sys/class/qcom-battery/usb_real_type";

// 金标动画广播伪造相关 sysfs 节点
#[cfg(unix)]
Expand Down
60 changes: 46 additions & 14 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@ mod pd;
mod platform;

use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::{AtomicBool, AtomicU8};
use std::thread;
#[cfg(not(unix))]
use std::time::Duration;

use common::constants::{FREE_FILE, PD_ADAPTER_VERIFIED_PATH, PD_VERIFIED_PATH};
use common::constants::{PD_ADAPTER_VERIFIED_PATH, PD_VERIFIED_PATH};
use common::utils;
use log::{error, info};
use monitoring::{
ModuleManager, spawn_disable_file_monitor, spawn_free_file_monitor,
spawn_pd_adapter_verified_monitor, spawn_pd_verified_monitor,
};
use pd::{PdAdapterVerifier, PdVerifier};
use platform::install_signal_handlers;
use platform::{EventFd, SignalWaiter, install_signal_handlers};

fn main() {
// 初始化 Android Logger
Expand All @@ -33,44 +34,58 @@ fn main() {
let module_manager = Arc::new(ModuleManager::new().expect("创建模块管理器失败"));

// 初始化阶段:确保基础文件存在并设置初始状态
if let Err(e) = module_manager.initialize_module() {
let initial_mode = module_manager.initialize_module().unwrap_or_else(|e| {
error!("模块初始化失败: {}", e);
}
monitoring::ChargingMode::Native
});

// 创建运行标志
let running = Arc::new(AtomicBool::new(true));
install_signal_handlers(&running);
let signal_waiter = SignalWaiter::new().expect("创建信号事件失败");
install_signal_handlers(&running, &signal_waiter);

let pd_verifier = Arc::new(PdVerifier::new().expect("创建PD验证器失败"));
let pd_adapter_verifier = Arc::new(PdAdapterVerifier::new().expect("创建PD适配器验证器失败"));

let free_enabled = Arc::new(AtomicBool::new(
monitoring::FileMonitor::read_file_content(FREE_FILE).unwrap_or_else(|_| "0".to_string())
== "1",
));
let charging_mode = Arc::new(AtomicU8::new(initial_mode as u8));
let qcom_config_event = Arc::new(EventFd::new().expect("创建qcom配置事件失败"));
let mtk_config_event = Arc::new(EventFd::new().expect("创建mtk配置事件失败"));

let mut thread_handles: Vec<thread::JoinHandle<()>> = Vec::new();
let mut stop_events: Vec<Arc<EventFd>> = Vec::new();

// 创建free文件监控线程
let free_stop_event = Arc::new(EventFd::new().expect("创建free线程停止事件失败"));
stop_events.push(Arc::clone(&free_stop_event));
thread_handles.push(spawn_free_file_monitor(
Arc::clone(&running),
Arc::clone(&module_manager),
Arc::clone(&free_enabled),
Arc::clone(&charging_mode),
Arc::clone(&qcom_config_event),
Arc::clone(&mtk_config_event),
free_stop_event,
));

// 创建disable文件监控线程
let disable_stop_event = Arc::new(EventFd::new().expect("创建disable线程停止事件失败"));
stop_events.push(Arc::clone(&disable_stop_event));
thread_handles.push(spawn_disable_file_monitor(
Arc::clone(&running),
Arc::clone(&module_manager),
disable_stop_event,
));

// 初始化时按节点存在性一次性创建 qcom/mtk 线程(不做后续轮询判断/重启)
if std::path::Path::new(PD_VERIFIED_PATH).exists() {
info!("检测到qcom节点存在,启动qcom线程: {}", PD_VERIFIED_PATH);
let qcom_stop_event = Arc::new(EventFd::new().expect("创建qcom线程停止事件失败"));
stop_events.push(Arc::clone(&qcom_stop_event));
thread_handles.push(spawn_pd_verified_monitor(
Arc::clone(&running),
Arc::clone(&pd_verifier),
Arc::clone(&free_enabled),
Arc::clone(&charging_mode),
Arc::clone(&qcom_config_event),
qcom_stop_event,
));
} else {
info!("qcom节点不存在,跳过qcom线程启动: {}", PD_VERIFIED_PATH);
Expand All @@ -81,10 +96,14 @@ fn main() {
"检测到mtk节点存在,启动mtk线程: {}",
PD_ADAPTER_VERIFIED_PATH
);
let mtk_stop_event = Arc::new(EventFd::new().expect("创建mtk线程停止事件失败"));
stop_events.push(Arc::clone(&mtk_stop_event));
thread_handles.push(spawn_pd_adapter_verified_monitor(
Arc::clone(&running),
Arc::clone(&pd_adapter_verifier),
Arc::clone(&free_enabled),
Arc::clone(&charging_mode),
Arc::clone(&mtk_config_event),
mtk_stop_event,
));
} else {
info!(
Expand All @@ -94,16 +113,29 @@ fn main() {
}

info!(
"[{}] 监控线程已按需启动(仅初始化判断一次),主线程park等待...",
"[{}] 监控线程已按需启动(仅初始化判断一次),主线程等待退出信号...",
main_thread_name
);

#[cfg(unix)]
if let Err(error) = signal_waiter.wait() {
error!("等待退出信号失败: {}", error);
}

#[cfg(not(unix))]
while running.load(std::sync::atomic::Ordering::Relaxed) {
thread::park_timeout(Duration::from_secs(1));
}

info!("检测到退出信号,开始停止所有监控线程...");
running.store(false, std::sync::atomic::Ordering::Relaxed);

for stop_event in &stop_events {
if let Err(error) = stop_event.notify() {
error!("通知监控线程停止失败: {}", error);
}
}

for handle in thread_handles {
if let Err(e) = handle.join() {
error!("线程join失败: {:?}", e);
Expand Down
2 changes: 2 additions & 0 deletions src/monitoring.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
pub mod file_monitor;
pub mod mode;
pub mod module_manager;
pub mod threads;

pub use file_monitor::FileMonitor;
pub use mode::ChargingMode;
pub use module_manager::ModuleManager;
pub use threads::{
spawn_disable_file_monitor, spawn_free_file_monitor, spawn_pd_adapter_verified_monitor,
Expand Down
42 changes: 42 additions & 0 deletions src/monitoring/mode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use anyhow::Result;
use std::path::Path;

use crate::common::constants::{AUTO_FILE, FREE_FILE};
use crate::monitoring::FileMonitor;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum ChargingMode {
Native = 0,
ForcePublicPps = 1,
Automatic = 2,
}

impl ChargingMode {
pub fn from_files() -> Result<Self> {
let free = FileMonitor::read_file_content(FREE_FILE)?;
Ok(if free != "1" {
Self::Native
} else if Path::new(AUTO_FILE).exists() {
Self::Automatic
} else {
Self::ForcePublicPps
})
}

pub fn from_raw(value: u8) -> Self {
match value {
1 => Self::ForcePublicPps,
2 => Self::Automatic,
_ => Self::Native,
}
}

pub fn description_prefix(self) -> &'static str {
match self {
Self::Native => "[⏸️小米协议优先💤] ",
Self::ForcePublicPps => "[✅锁定PPS支持⚡] ",
Self::Automatic => "[🔄协议自动识别💡] ",
}
}
}
Loading