Merge pull request #342 from wlmh110/noai

PiSugar Plugin update
This commit is contained in:
Jayofelony
2025-02-17 14:14:43 +01:00
committed by GitHub
2 changed files with 237 additions and 57 deletions

View File

@ -67,6 +67,9 @@ main.plugins.pwndroid.display_altitude = false # show altitude on display
main.plugins.pisugarx.enabled = false main.plugins.pisugarx.enabled = false
main.plugins.pisugarx.rotation = false main.plugins.pisugarx.rotation = false
main.plugins.pisugarx.default_display = "percentage" main.plugins.pisugarx.default_display = "percentage"
main.plugins.pisugarx.lowpower_shutdown = true
main.plugins.pisugarx.lowpower_shutdown_level = 10 # battery percent at which the device will turn off
main.plugins.pisugarx.max_charge_voltage_protection = false #It will limit the battery voltage to about 80% to extend battery life
main.plugins.session-stats.enabled = false main.plugins.session-stats.enabled = false
main.plugins.session-stats.save_directory = "/var/tmp/pwnagotchi/sessions/" main.plugins.session-stats.save_directory = "/var/tmp/pwnagotchi/sessions/"

View File

@ -29,7 +29,7 @@ curve1200 = [
(3.49, 3.2), (3.49, 3.2),
(3.1, 0.0), (3.1, 0.0),
] ]
curve1200_3= [ curve1200_3 = [
(4.2, 100.0), # 高电量阶段 (100%) (4.2, 100.0), # 高电量阶段 (100%)
(4.0, 80.0), # 中电量阶段 (80%) (4.0, 80.0), # 中电量阶段 (80%)
(3.7, 60.0), # 中电量阶段 (60%) (3.7, 60.0), # 中电量阶段 (60%)
@ -50,26 +50,42 @@ curve5000 = [
] ]
class PiSugarServer: class PiSugarServer:
def __init__(self): def __init__(self):
""" """
PiSugar initialization, if unable to connect to any version of PiSugar, return false PiSugar initialization, if unable to connect to any version of PiSugar, return false
""" """
self._bus = smbus.SMBus(1) self._bus = smbus.SMBus(1)
self.ready = False
self.modle = None self.modle = None
self.i2creg = [] self.i2creg = []
self.address = 0 self.address = 0
self.battery_voltage = 0 self.battery_voltage = 0.00
self.voltage_history = deque(maxlen=10) self.voltage_history = deque(maxlen=10)
self.battery_level = 0 self.battery_level = 0
self.battery_charging = 0 self.battery_charging = 0
self.temperature = 0 self.temperature = 0
self.power_plugged = False self.power_plugged = False
self.allow_charging = True self.allow_charging = True
self.lowpower_shutdown = False
self.lowpower_shutdown_level = 10
self.max_charge_voltage_protection = False
self.max_protection_level=80
# Start the device connection in a background thread
self.connection_thread = threading.Thread(
target=self._connect_device, daemon=True)
self.connection_thread.start()
def _connect_device(self):
"""
Attempt to connect to the PiSugar device in a background thread.
"""
while self.modle is None: while self.modle is None:
if self.check_device(PiSugar_addresses["PiSugar2"]) is not None: if self.check_device(PiSugar_addresses["PiSugar2"]) is not None:
self.address = PiSugar_addresses["PiSugar2"] self.address = PiSugar_addresses["PiSugar2"]
if self.check_device(PiSugar_addresses["PiSugar2"], 0Xc2) != 0: if self.check_device(PiSugar_addresses["PiSugar2"], 0xC2) != 0:
self.modle = "PiSugar2Plus" self.modle = "PiSugar2Plus"
else: else:
self.modle = "PiSugar2" self.modle = "PiSugar2"
@ -77,16 +93,20 @@ class PiSugarServer:
elif self.check_device(PiSugar_addresses["PiSugar3"]) is not None: elif self.check_device(PiSugar_addresses["PiSugar3"]) is not None:
self.modle = 'PiSugar3' self.modle = 'PiSugar3'
self.address = PiSugar_addresses["PiSugar3"] self.address = PiSugar_addresses["PiSugar3"]
self.device_init()
else: else:
self.modle = None self.modle = None
logging.error( logging.info(
"No PiSugar device was found. Please check if the PiSugar device is powered on.") "No PiSugar device was found. Please check if the PiSugar device is powered on."
)
time.sleep(5) time.sleep(5)
logging.info(f"{self.modle} is connected")
# self.update_value() # Once connected, start the timer
self.start_timer() self.start_timer()
while len(self.i2creg) < 256: while len(self.i2creg) < 256:
time.sleep(1) time.sleep(1)
self.ready = True
logging.info(f"{self.modle} is ready")
def start_timer(self): def start_timer(self):
@ -99,6 +119,9 @@ class PiSugarServer:
"""每三秒更新pisugar状态包括触发自动关机""" """每三秒更新pisugar状态包括触发自动关机"""
while True: while True:
try: try:
if( self.modle == 'PiSugar2') | (self.modle == 'PiSugar2Plus'):
self.set_battery_notallow_charging() #短暂关闭充电以获取准确电池电压
time.sleep(0.05)
self.i2creg = [] self.i2creg = []
for i in range(0, 256, 32): for i in range(0, 256, 32):
# 计算当前读取的起始寄存器地址 # 计算当前读取的起始寄存器地址
@ -121,6 +144,20 @@ class PiSugarServer:
ctr1 = self.i2creg[0x02] # 读取控制寄存器 1 ctr1 = self.i2creg[0x02] # 读取控制寄存器 1
self.power_plugged = (ctr1 & (1 << 7)) != 0 # 检查电源是否插入 self.power_plugged = (ctr1 & (1 << 7)) != 0 # 检查电源是否插入
self.allow_charging = (ctr1 & (1 << 6)) != 0 # 检查是否允许充电 self.allow_charging = (ctr1 & (1 << 6)) != 0 # 检查是否允许充电
if self.max_charge_voltage_protection:
self._bus.write_byte_data(
self.address, 0x0B, 0x29) # 关闭写保护
self._bus.write_byte_data(self.address, 0x20, self._bus.read_byte_data(
self.address, 0x20) | 0b10000000)
self._bus.write_byte_data(
self.address, 0x0B, 0x00) # 开启写保护
else:
self._bus.write_byte_data(
self.address, 0x0B, 0x29) # 关闭写保护
self._bus.write_byte_data(self.address, 0x20, self._bus.read_byte_data(
self.address, 0x20) & 0b01111111)
self._bus.write_byte_data(
self.address, 0x0B, 0x00) # 开启写保护
elif self.modle == 'PiSugar2': elif self.modle == 'PiSugar2':
high = self.i2creg[0xa3] high = self.i2creg[0xa3]
low = self.i2creg[0xa2] low = self.i2creg[0xa2]
@ -129,20 +166,60 @@ class PiSugarServer:
2600.0 + (((high & 0x1f) << 8) + low) * 0.26855) / 1000.0 2600.0 + (((high & 0x1f) << 8) + low) * 0.26855) / 1000.0
self.power_plugged = (self.i2creg[0x55] & 0b00010000) != 0 self.power_plugged = (self.i2creg[0x55] & 0b00010000) != 0
if self.max_charge_voltage_protection:
self.voltage_history.append(self.battery_voltage)
self.battery_level = self.convert_battery_voltage_to_level()
if (self.battery_level) > self.max_protection_level:
self.set_battery_notallow_charging()
else:
self.set_battery_allow_charging()
else:
self.set_battery_allow_charging()
elif self.modle == 'PiSugar2Plus': elif self.modle == 'PiSugar2Plus':
low = self.i2creg[0xd0] low = self.i2creg[0xd0]
high = self.i2creg[0xd1] high = self.i2creg[0xd1]
self.battery_voltage = ( self.battery_voltage = (
(((high & 0b00111111) << 8) + low) * 0.26855 + 2600.0)/1000 (((high & 0b00111111) << 8) + low) * 0.26855 + 2600.0)/1000
self.power_plugged = self.i2creg[0xdd] == 0x1f self.power_plugged = self.i2creg[0xdd] == 0x1f
if self.max_charge_voltage_protection:
self.voltage_history.append(self.battery_voltage)
self.battery_level = self.convert_battery_voltage_to_level()
if (self.battery_level) > self.max_protection_level:
self.set_battery_notallow_charging()
else:
self.set_battery_allow_charging()
else:
self.set_battery_allow_charging()
self.voltage_history.append(self.battery_voltage) self.voltage_history.append(self.battery_voltage)
self.battery_level=self.convert_battery_voltage_to_level() self.battery_level = self.convert_battery_voltage_to_level()
if self.lowpower_shutdown:
if self.battery_level < self.lowpower_shutdown_level:
logging.info("[PiSugarX] low power shutdown now.")
self.shutdown()
pwnagotchi.shutdown()
time.sleep(3) time.sleep(3)
except: except Exception as e:
logging.error(f"read error") logging.error(f"read error{e}")
time.sleep(3) time.sleep(3)
def shutdown(self):
# logging.info("[PiSugarX] PiSugar set shutdown .")
if self.modle == 'PiSugar3':
# 10秒后关闭电源
self._bus.write_byte_data(self.address, 0x0B, 0x29) # 关闭写保护
self._bus.write_byte_data(self.address, 0x09, 10)
self._bus.write_byte_data(self.address, 0x02, self._bus.read_byte_data(
self.address, 0x02) & 0b11011111)
self._bus.write_byte_data(self.address, 0x0B, 0x00) # 开启写保护
logging.info("[PiSugarX] PiSugar shutdown in 10s.")
elif self.modle == 'PiSugar2':
pass
elif self.modle == 'PiSugar2Plus':
pass
def check_device(self, address, reg=0): def check_device(self, address, reg=0):
"""Check if a device is present at the specified address""" """Check if a device is present at the specified address"""
try: try:
@ -278,6 +355,58 @@ class PiSugarServer:
""" """
return self.allow_charging return self.allow_charging
def set_battery_allow_charging(self):
if self.modle == 'PiSugar3':
pass
elif self.modle == 'PiSugar2':
# 禁止 gpio2 输出
self._bus.write_byte_data(self.address, 0x54, self._bus.read_byte_data(
self.address, 0x54) & 0b11111011)
# 开启充电
self._bus.write_byte_data(self.address, 0x55, self._bus.read_byte_data(
self.address, 0x55) & 0b11111011)
# 开启 gpio2 输出
self._bus.write_byte_data(self.address, 0x54, self._bus.read_byte_data(
self.address, 0x54) | 0b00000100)
elif self.modle == 'PiSugar2Plus':
# 禁止 gpio2 输出
self._bus.write_byte_data(self.address, 0x56, self._bus.read_byte_data(
self.address, 0x56) & 0b11111011)
# 开启充电
self._bus.write_byte_data(self.address, 0x58, self._bus.read_byte_data(
self.address, 0x58) & 0b11111011)
# 开启 gpio2 输出
self._bus.write_byte_data(self.address, 0x56, self._bus.read_byte_data(
self.address, 0x56) | 0b00000100)
return
def set_battery_notallow_charging(self):
if self.modle == 'PiSugar3':
pass
elif self.modle == 'PiSugar2':
# 禁止 gpio2 输出
print(self._bus.write_byte_data(self.address, 0x54, self._bus.read_byte_data(
self.address, 0x54) & 0b11111011))
# 关闭充电
self._bus.write_byte_data(self.address, 0x55, self._bus.read_byte_data(
self.address, 0x55) | 0b00000100)
# 开启 gpio2 输出
self._bus.write_byte_data(self.address, 0x54, self._bus.read_byte_data(
self.address, 0x54) | 0b00000100)
elif self.modle == 'PiSugar2Plus':
# 禁止 gpio2 输出
self._bus.write_byte_data(self.address, 0x56, self._bus.read_byte_data(
self.address, 0x56) & 0b11111011)
# 关闭充电
self._bus.write_byte_data(self.address, 0x58, self._bus.read_byte_data(
self.address, 0x58) | 0b00000100)
# 开启 gpio2 输出
self._bus.write_byte_data(self.address, 0x56, self._bus.read_byte_data(
self.address, 0x56) | 0b00000100)
return
def get_battery_charging_range(self): def get_battery_charging_range(self):
""" """
Get the battery charging range. Get the battery charging range.
@ -406,6 +535,8 @@ class PiSugarServer:
""" """
pass pass
class PiSugar(plugins.Plugin): class PiSugar(plugins.Plugin):
__author__ = "jayofelony" __author__ = "jayofelony"
__version__ = "1.2" __version__ = "1.2"
@ -418,14 +549,25 @@ class PiSugar(plugins.Plugin):
def __init__(self): def __init__(self):
self._agent = None self._agent = None
self.is_new_model = False
self.options = dict() self.options = dict()
"""
self.options = {
'enabled': True,
'rotation': False,
'default_display': 'percentage',
'lowpower_shutdown': True,
'lowpower_shutdown_level': 10,
'max_charge_voltage_protection': True
}
"""
self.ps = None self.ps = None
# logging.debug(f"[PiSugarX] {self.options}")
try: try:
self.ps = PiSugarServer() self.ps = PiSugarServer()
except Exception as e: except Exception as e:
# Log at debug to avoid clutter since it might be a false positive # Log at debug to avoid clutter since it might be a false positive
logging.debug("[PiSugarX] Unable to establish connection: %s", repr(e)) logging.debug(
"[PiSugarX] Unable to establish connection: %s", repr(e))
self.ready = False self.ready = False
self.lasttemp = 69 self.lasttemp = 69
@ -444,7 +586,8 @@ class PiSugar(plugins.Plugin):
try: try:
return func() return func()
except Exception as e: except Exception as e:
logging.debug("[PiSugarX] Failed to get data using %s: %s", func.__name__, e) logging.debug(
"[PiSugarX] Failed to get data using %s: %s", func.__name__, e)
return default return default
def on_loaded(self): def on_loaded(self):
@ -455,15 +598,25 @@ class PiSugar(plugins.Plugin):
valid_displays = ['voltage', 'percentage', 'temp'] valid_displays = ['voltage', 'percentage', 'temp']
if self.default_display not in valid_displays: if self.default_display not in valid_displays:
logging.warning(f"[PiSugarX] Invalid default_display '{self.default_display}'. Using 'voltage'.") logging.warning(
f"[PiSugarX] Invalid default_display '{self.default_display}'. Using 'voltage'.")
self.default_display = 'voltage' self.default_display = 'voltage'
logging.info(f"[PiSugarX] Rotation is {'enabled' if self.rotation_enabled else 'disabled'}.") logging.info(
logging.info(f"[PiSugarX] Default display (when rotation disabled): {self.default_display}") f"[PiSugarX] Rotation is {'enabled' if self.rotation_enabled else 'disabled'}.")
logging.info(
f"[PiSugarX] Default display (when rotation disabled): {self.default_display}")
self.ps.lowpower_shutdown = self.options['lowpower_shutdown']
self.ps.lowpower_shutdown_level = self.options['lowpower_shutdown_level']
self.ps.max_charge_voltage_protection = self.options['max_charge_voltage_protection']
def on_ready(self, agent): def on_ready(self, agent):
self.ready = True try:
self._agent = agent self.ready = self.ps.ready
except Exception as e:
# Log at debug to avoid clutter since it might be a false positive
logging.warning(f"[PiSugarX] {e}")
def on_internet_available(self, agent): def on_internet_available(self, agent):
self._agent = agent self._agent = agent
@ -477,30 +630,52 @@ class PiSugar(plugins.Plugin):
try: try:
if request.method == "GET": if request.method == "GET":
if path == "/" or not path: if path == "/" or not path:
version = self.safe_get(self.ps.get_version, default='Unknown') version = self.safe_get(
self.ps.get_version, default='Unknown')
model = self.safe_get(self.ps.get_model, default='Unknown') model = self.safe_get(self.ps.get_model, default='Unknown')
battery_level = self.safe_get(self.ps.get_battery_level, default='N/A') battery_level = self.safe_get(
battery_voltage = self.safe_get(self.ps.get_battery_voltage, default='N/A') self.ps.get_battery_level, default='N/A')
battery_current = self.safe_get(self.ps.get_battery_current, default='N/A') battery_voltage = self.safe_get(
battery_allow_charging = self.safe_get(self.ps.get_battery_allow_charging, default=False) self.ps.get_battery_voltage, default='N/A')
battery_charging_range = self.safe_get(self.ps.get_battery_charging_range, default='N/A') if self.is_new_model or model == 'Pisugar 3' else 'Not supported' battery_current = self.safe_get(
battery_full_charge_duration = getattr(self.ps, 'get_battery_full_charge_duration', lambda: 'N/A')() self.ps.get_battery_current, default='N/A')
safe_shutdown_level = self.safe_get(self.ps.get_battery_safe_shutdown_level, default=None) battery_allow_charging = self.safe_get(
self.ps.get_battery_allow_charging, default=False)
battery_charging_range = self.safe_get(
self.ps.get_battery_charging_range, default='N/A')
battery_full_charge_duration = getattr(
self.ps, 'get_battery_full_charge_duration', lambda: 'N/A')()
safe_shutdown_level = self.safe_get(
self.ps.get_battery_safe_shutdown_level, default=None)
battery_safe_shutdown_level = f"{safe_shutdown_level}%" if safe_shutdown_level is not None else 'Not set' battery_safe_shutdown_level = f"{safe_shutdown_level}%" if safe_shutdown_level is not None else 'Not set'
battery_safe_shutdown_delay = self.safe_get(self.ps.get_battery_safe_shutdown_delay, default='N/A') battery_safe_shutdown_delay = self.safe_get(
battery_auto_power_on = self.safe_get(self.ps.get_battery_auto_power_on, default=False) self.ps.get_battery_safe_shutdown_delay, default='N/A')
battery_soft_poweroff = self.safe_get(self.ps.get_battery_soft_poweroff, default=False) if model == 'Pisugar 3' else False battery_auto_power_on = self.safe_get(
system_time = self.safe_get(self.ps.get_system_time, default='N/A') self.ps.get_battery_auto_power_on, default=False)
rtc_adjust_ppm = self.safe_get(self.ps.get_rtc_adjust_ppm, default='Not supported') if model == 'Pisugar 3' else 'Not supported' battery_soft_poweroff = self.safe_get(
rtc_alarm_repeat = self.safe_get(self.ps.get_rtc_alarm_repeat, default='N/A') self.ps.get_battery_soft_poweroff, default=False) if model == 'Pisugar 3' else False
single_tap_enabled = self.safe_get(lambda: self.ps.get_tap_enable(tap='single'), default=False) system_time = self.safe_get(
double_tap_enabled = self.safe_get(lambda: self.ps.get_tap_enable(tap='double'), default=False) self.ps.get_system_time, default='N/A')
long_tap_enabled = self.safe_get(lambda: self.ps.get_tap_enable(tap='long'), default=False) rtc_adjust_ppm = self.safe_get(
single_tap_shell = self.safe_get(lambda: self.ps.get_tap_shell(tap='single'), default='N/A') self.ps.get_rtc_adjust_ppm, default='Not supported') if model == 'Pisugar 3' else 'Not supported'
double_tap_shell = self.safe_get(lambda: self.ps.get_tap_shell(tap='double'), default='N/A') rtc_alarm_repeat = self.safe_get(
long_tap_shell = self.safe_get(lambda: self.ps.get_tap_shell(tap='long'), default='N/A') self.ps.get_rtc_alarm_repeat, default='N/A')
anti_mistouch = self.safe_get(self.ps.get_anti_mistouch, default=False) if model == 'Pisugar 3' else False single_tap_enabled = self.safe_get(
temperature = self.safe_get(self.ps.get_temperature, default='N/A') lambda: self.ps.get_tap_enable(tap='single'), default=False)
double_tap_enabled = self.safe_get(
lambda: self.ps.get_tap_enable(tap='double'), default=False)
long_tap_enabled = self.safe_get(
lambda: self.ps.get_tap_enable(tap='long'), default=False)
single_tap_shell = self.safe_get(
lambda: self.ps.get_tap_shell(tap='single'), default='N/A')
double_tap_shell = self.safe_get(
lambda: self.ps.get_tap_shell(tap='double'), default='N/A')
long_tap_shell = self.safe_get(
lambda: self.ps.get_tap_shell(tap='long'), default='N/A')
anti_mistouch = self.safe_get(
self.ps.get_anti_mistouch, default=False) if model == 'Pisugar 3' else False
temperature = self.safe_get(
self.ps.get_temperature, default='N/A')
ret = ''' ret = '''
<!DOCTYPE html> <!DOCTYPE html>
@ -561,7 +736,7 @@ class PiSugar(plugins.Plugin):
<tr><td>Battery Level</td><td>{battery_level}%</td></tr> <tr><td>Battery Level</td><td>{battery_level}%</td></tr>
<tr><td>Battery Voltage</td><td>{battery_voltage}V</td></tr> <tr><td>Battery Voltage</td><td>{battery_voltage}V</td></tr>
<tr><td>Battery Current</td><td>{battery_current}A</td></tr> <tr><td>Battery Current</td><td>{battery_current}A</td></tr>
<tr><td>Battery Allow Charging</td><td>{"Yes" if battery_allow_charging and self.is_new_model else "No"}</td></tr> <tr><td>Battery Allow Charging</td><td>{"Yes" if battery_allow_charging else "No"}</td></tr>
<tr><td>Battery Charging Range</td><td>{battery_charging_range}</td></tr> <tr><td>Battery Charging Range</td><td>{battery_charging_range}</td></tr>
<tr><td>Duration of Keep Charging When Full</td><td>{battery_full_charge_duration} seconds</td></tr> <tr><td>Duration of Keep Charging When Full</td><td>{battery_full_charge_duration} seconds</td></tr>
<tr><td>Battery Safe Shutdown Level</td><td>{battery_safe_shutdown_level}</td></tr> <tr><td>Battery Safe Shutdown Level</td><td>{battery_safe_shutdown_level}</td></tr>
@ -628,13 +803,25 @@ class PiSugar(plugins.Plugin):
# Make sure "bat" is in the UI state (guard to prevent KeyError) # Make sure "bat" is in the UI state (guard to prevent KeyError)
if 'bat' not in ui._state._state: if 'bat' not in ui._state._state:
return return
try:
self.ready = self.ps.ready
except Exception as e:
# Log at debug to avoid clutter since it might be a false positive
logging.warning(f"[PiSugarX] {e}")
if self.ready:
capacity = self.safe_get(self.ps.get_battery_level, default=0)
voltage = self.safe_get(self.ps.get_battery_voltage, default=0.00)
temp = self.safe_get(self.ps.get_temperature, default=0)
capacity = self.safe_get(self.ps.get_battery_level, default=0) else:
voltage = self.safe_get(self.ps.get_battery_voltage, default=0.00) capacity = 0
temp = self.safe_get(self.ps.get_temperature, default=0) voltage = 0.00
temp = 0
logging.info(f"[PiSugarX] PiSugar is not ready")
# Check if battery is plugged in # Check if battery is plugged in
battery_plugged = self.safe_get(self.ps.get_battery_power_plugged, default=False) battery_plugged = self.safe_get(
self.ps.get_battery_power_plugged, default=False)
if battery_plugged: if battery_plugged:
# If plugged in, display "CHG" # If plugged in, display "CHG"
@ -663,13 +850,3 @@ class PiSugar(plugins.Plugin):
ui.set('bat', f"{capacity:.0f}%") ui.set('bat', f"{capacity:.0f}%")
elif self.default_display == 'temp': elif self.default_display == 'temp':
ui.set('bat', f"{temp}°C") ui.set('bat', f"{temp}°C")
charging = self.safe_get(self.ps.get_battery_charging, default=None)
safe_shutdown_level = self.safe_get(self.ps.get_battery_safe_shutdown_level, default=0)
if charging is not None:
if capacity <= safe_shutdown_level:
logging.info(
f"[PiSugarX] Empty battery (<= {safe_shutdown_level}%): shutting down"
)
ui.update(force=True, new_data={"status": "Battery exhausted, bye ..."})