Repository: hannadiamond/pwnagotchi-plugins
Branch: main
Commit: 80483470eee6
Files: 14
Total size: 27.4 KB
Directory structure:
gitextract_cqjvcuup/
├── .idea/
│ ├── .gitignore
│ ├── inspectionProfiles/
│ │ ├── Project_Default.xml
│ │ └── profiles_settings.xml
│ ├── markdown.xml
│ ├── misc.xml
│ ├── modules.xml
│ ├── pwnagotchi-plugins.iml
│ └── vcs.xml
├── LICENSE
├── README.md
├── plugins/
│ ├── age.py
│ ├── exp.py
│ └── ups_hat_c.py
└── waveshare_37inch/
└── waveshare3in7.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .idea/.gitignore
================================================
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
================================================
FILE: .idea/inspectionProfiles/Project_Default.xml
================================================
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="ERROR" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>
================================================
FILE: .idea/inspectionProfiles/profiles_settings.xml
================================================
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>
================================================
FILE: .idea/markdown.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownSettings">
<option name="hideErrorsInCodeBlocks" value="true" />
</component>
</project>
================================================
FILE: .idea/misc.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (automated_segmentation)" project-jdk-type="Python SDK" />
</project>
================================================
FILE: .idea/modules.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/pwnagotchi-plugins.iml" filepath="$PROJECT_DIR$/.idea/pwnagotchi-plugins.iml" />
</modules>
</component>
</project>
================================================
FILE: .idea/pwnagotchi-plugins.iml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
================================================
FILE: .idea/vcs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2022 Hanna.Diamond
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# pwnagotchi-plugins
# Custom Plugin Setup
If you have not set up a directory for custom plugins, create the directory and add its path to your config.toml.
`main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins/"`
# Age Plugin
A plugin that adds age and int stats based on the the time training and the number of epochs trained.
Whenever your pwnagotchi has lived through another 100 epochs or epochs trained, a new status will appear!

## Setup
1. Copy over `age.py` into your custom plugins directory
2. In your `config.toml` file add:
```toml
main.plugins.age.enabled = true
main.plugins.age.age_x_coord = 0
main.plugins.age.age_y_coord = 32
main.plugins.age.int_x_coord = 67
main.plugins.age.int_y_coord = 32
```
3. Restart your device to see your new stats!
# UPS HAT (C) Plugin
A plugin that will add a battery capacity and charging indicator for the Waveshare UPS HAT (C)

## Requirements
- "i2c" in raspi-config enabled
- smbus installed `sudo apt-get install -y python-smbus`
## Setup
1. Copy over `ups_hat_c.py` into your custom plugins directory
2. In your `config.toml` file, add:
```toml
main.plugins.ups_hat_c.enabled = true
main.plugins.ups_hat_c.label_on = true # show BAT label or just percentage
main.plugins.ups_hat_c.shutdown = 5 # battery percent at which the device will turn off
main.plugins.ups_hat_c.bat_x_coord = 140
main.plugins.ups_hat_c.bat_y_coord = 0
```
3. Restart your device to see your new indicator!
# Waveshare 3.7 Inch Display
## Setup
3. Copy `waveshare3in7.py` into `/usr/local/lib/python3.11/dist-packages/pwnagotchi/ui/hw`
4. In the `config.toml` set `ui.display.type = "waveshare3in7"`
5. In `/usr/local/lib/python3.11/dist-packages/pwnagotchi/ui/components.py` in the `class LabeledValue`, replace `def draw` with
```python
def draw(self, canvas, drawer):
if self.label is None:
drawer.text(self.xy, self.value, font=self.label_font, fill=self.color)
else:
pos = self.xy
drawer.text(pos, self.label, font=self.label_font, fill=self.color)
drawer.text((pos[0] + self.label_spacing + self.label_font.getsize(self.label)[0], pos[1]), self.value, font=self.text_font, fill=self.color)
```
================================================
FILE: plugins/age.py
================================================
import os
import json
import logging
from datetime import datetime
import pwnagotchi
import pwnagotchi.plugins as plugins
import pwnagotchi.ui.faces as faces
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK
DATA_PATH = '/root/brain.json'
class Age(plugins.Plugin):
__author__ = 'HannaDiamond'
__version__ = '2.0.1'
__license__ = 'MIT'
__description__ = 'A plugin that will add age and strength stats based on epochs and trained epochs'
def __init__(self):
self.train_epochs = 0
self.device_start_time = datetime.now()
def on_loaded(self):
self.load_data()
def on_ui_setup(self, ui):
ui.add_element('Age', LabeledValue(color=BLACK, label='♥ Age', value=0,
position=(int(self.options["age_x_coord"]),
int(self.options["age_y_coord"])),
label_font=fonts.Bold, text_font=fonts.Medium))
ui.add_element('Int', LabeledValue(color=BLACK, label='Int', value=0,
position=(int(self.options["int_x_coord"]),
int(self.options["int_y_coord"])),
label_font=fonts.Bold, text_font=fonts.Medium))
def on_unload(self, ui):
with ui._lock:
ui.remove_element('Age')
ui.remove_element('Int')
def on_ui_update(self, ui):
ui.set('Age', "%s" % self.calculate_device_age())
ui.set('Int', "%s" % self.abrev_number(self.train_epochs))
def on_ai_training_step(self, agent, _locals, _globals):
self.train_epochs += 1
if self.train_epochs % 100 == 0:
self.intelligence_checkpoint(agent)
self.age_checkpoint(agent)
def abrev_number(self, num):
if num < 100000:
return str(num)
else:
magnitude = 0
while abs(num) >= 1000:
magnitude += 1
num /= 1000.0
abbr = ['', 'K', 'M', 'B', 'T', 'P'][magnitude]
return '{}{}'.format('{:.2f}'.format(num).rstrip('0').rstrip('.'), abbr)
def age_checkpoint(self, agent):
view = agent.view()
view.set('face', faces.HAPPY)
view.set('status', "Wow, I've lived for " + self.calculate_device_age())
view.update(force=True)
def intelligence_checkpoint(self, agent):
view = agent.view()
view.set('face', faces.MOTIVATED)
view.set('status', "Look at my intelligence go up! \n" \
"I've trained for " + self.abrev_number(self.train_epochs) + " epochs")
view.update(force=True)
def calculate_device_age(self):
current_time = datetime.now()
age_delta = current_time - self.device_start_time
years = age_delta.days // 365
remaining_days = age_delta.days % 365
months = remaining_days // 30
days = remaining_days % 30
age_str = ""
if years != 0:
age_str = f'{years}y'
if months != 0:
age_str = f'{age_str} {months}m'
if len(age_str) !=0 :
age_str = f'{age_str} {days}d'
else:
age_str = f'{days}d'
return age_str
def load_data(self):
if os.path.exists(DATA_PATH):
with open(DATA_PATH) as f:
data = json.load(f)
self.train_epochs = data['epochs_trained']
================================================
FILE: plugins/exp.py
================================================
import logging
import os
import random
import json
import pwnagotchi
import pwnagotchi.agent
import pwnagotchi.plugins as plugins
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK
# Static Variables
MULTIPLIER_ASSOCIATION = 1
MULTIPLIER_DEAUTH = 2
MULTIPLIER_HANDSHAKE = 3
MULTIPLIER_AI_BEST_REWARD = 5
TAG = "[EXP Plugin]"
FACE_LEVELUP = '(≧◡◡≦)'
BAR_ERROR = "| error |"
FILE_SAVE = "exp_stats"
FILE_SAVE_LEGACY = "exp"
JSON_KEY_LEVEL = "level"
JSON_KEY_EXP ="exp"
JSON_KEY_EXP_TOT ="exp_tot"
JSON_KEY_STRENGTH = "strength"
class EXP(plugins.Plugin):
__author__ = 'GaelicThunder, HannaDiamond, Kaska89, Rai, JayofElony, & Terminator'
__version__ = '2.0.1'
__license__ = 'GPL3'
__description__ = 'Get exp every time a handshake get captured.'
# Attention number masking
def LogInfo(self, text):
logging.info(TAG + " " +text)
# Attention number masking
def LogDebug(self, text):
logging.debug(TAG + " " +text)
def __init__(self):
self.percent=0
self.strength=1
self.calculateInitialXP = False
self.exp=0
self.lv=1
self.exp_tot=0
# Sets the file type I recommend json
self.save_file_mode = self.save_file_modes("json")
self.save_file = self.getSaveFileName(self.save_file_mode)
# Migrate from old save system
self.migrateLegacySave()
# Create save file
if not os.path.exists(self.save_file):
self.Save(self.save_file, self.save_file_mode)
else:
try:
# Try loading
self.Load(self.save_file, self.save_file_mode)
except:
# Likely throws an exception if json file is corrupted, so we need to calculate from scratch
self.calculateInitialXP = True
# No previous data, try get it
if self.lv == 1 and self.exp == 0:
self.calculateInitialXP = True
if self.exp_tot == 0:
self.LogInfo("Need to calculate Total Exp")
self.exp_tot = self.calcActualSum(self.lv, self.exp)
self.Save(self.save_file, self.save_file_mode)
self.expneeded = self.calcExpNeeded(self.lv)
def on_loaded(self):
# logging.info("Exp plugin loaded for %s" % self.options['device'])
self.LogInfo("Plugin Loaded")
def save_file_modes(self,argument):
switcher = {
"txt": 0,
"json": 1,
}
return switcher.get(argument, 0)
def Save(self, file, save_file_mode):
self.LogDebug('Saving Exp')
if save_file_mode == 0:
self.saveToTxtFile(file)
if save_file_mode == 1:
self.saveToJsonFile(file)
def saveToTxtFile(self, file):
outfile=open(file, 'w')
print(self.exp,file=outfile)
print(self.lv,file=outfile)
print(self.exp_tot,file=outfile)
print(self.strength,file=outfile)
outfile.close()
def loadFromTxtFile(self, file):
if os.path.exists(file):
outfile= open(file, 'r+')
lines = outfile.readlines()
linecounter = 1
for line in lines:
if linecounter == 1:
self.exp = int(line)
elif linecounter == 2:
self.lv == int(line)
elif linecounter == 3:
self.exp_tot == int(line)
elif linecounter == 4:
self.strength == int(line)
linecounter += 1
outfile.close()
def saveToJsonFile(self,file):
data = {
JSON_KEY_LEVEL : self.lv,
JSON_KEY_EXP : self.exp,
JSON_KEY_EXP_TOT : self.exp_tot,
JSON_KEY_STRENGTH : self.strength
}
with open(file, 'w') as f:
f.write(json.dumps(data, sort_keys=True, indent=4, separators=(',', ': ')))
def loadFromJsonFile(self, file):
# Tot exp is introduced with json, no check needed
data = {}
with open(file, 'r') as f:
data = json.loads(f.read())
if bool(data):
self.lv = data[JSON_KEY_LEVEL]
self.exp = data[JSON_KEY_EXP]
self.exp_tot = data[JSON_KEY_EXP_TOT]
self.strength = data[JSON_KEY_STRENGTH]
else:
self.LogInfo("Empty json")
# TODO: one day change save file mode to file date
def Load(self, file, save_file_mode):
self.LogDebug('Loading Exp')
if save_file_mode == 0:
self.loadFromTxtFile(file)
if save_file_mode == 1:
self.loadFromJsonFile(file)
def getSaveFileName(self, save_file_mode):
file = os.path.dirname(os.path.realpath(__file__))
file = file + "/" +FILE_SAVE
if save_file_mode == 0:
file = file + ".txt"
elif save_file_mode == 1:
file = file + ".json"
else:
# See switcher
file = file + ".txt"
return file
def migrateLegacySave(self):
legacyFile = os.path.dirname(os.path.realpath(__file__))
legacyFile = legacyFile + "/" + FILE_SAVE_LEGACY +".txt"
if os.path.exists(legacyFile):
self.loadFromTxtFile(legacyFile)
self.LogInfo("Migrating Legacy Save...")
self.Save(self.save_file, self.save_file_mode)
os.remove(legacyFile)
def barString(self, symbols_count, p):
if p > 100:
return BAR_ERROR
length = symbols_count-2
bar_char = '▥'
blank_char = ' '
bar_length = int(round((length / 100)*p))
blank_length = length - bar_length
res = '|' + bar_char * bar_length + blank_char * blank_length + '|'
return res
def on_ui_setup(self, ui):
ui.add_element('Lv', LabeledValue(color=BLACK, label='Lv', value=0,
position=(int(self.options["lvl_x_coord"]),
int(self.options["lvl_y_coord"])),
label_font=fonts.Bold, text_font=fonts.Medium))
ui.add_element('Exp', LabeledValue(color=BLACK, label='Exp', value=0,
position=(int(self.options["exp_x_coord"]),
int(self.options["exp_y_coord"])),
label_font=fonts.Bold, text_font=fonts.Medium))
ui.add_element('Str', LabeledValue(color=BLACK, label='Str', value=0,
position=(int(self.options["str_x_coord"]),
int(self.options["str_y_coord"])),
label_font=fonts.Bold, text_font=fonts.Medium))
def on_ui_update(self, ui):
self.expneeded=self.calcExpNeeded(self.lv)
self.percent=int((self.exp/self.expneeded)*100)
symbols_count=int(self.options["bar_symbols_count"])
bar=self.barString(symbols_count, self.percent)
ui.set('Lv', "%d" % self.lv)
ui.set('Exp', "%s" % bar)
self.calcStrength()
ui.set('Str', "%d" % self.strength)
def calcExpNeeded(self, level):
# If the pwnagotchi is lvl <1 it causes the keys to be deleted
if level == 1:
return 5
return int((level**3)/2)
def exp_check(self, agent):
self.LogDebug("EXP CHECK")
if self.exp>=self.expneeded:
self.exp=1
self.lv=self.lv+1
self.expneeded=self.calcExpNeeded(self.lv)
self.displayLevelUp(agent)
def parseSessionStats(self):
sum = 0
dir = pwnagotchi.config['main']['plugins']['session-stats']['save_directory']
# TODO: remove
self.LogInfo("Session-Stats dir: " + dir)
for filename in os.listdir(dir):
self.LogInfo("Parsing " + filename + "...")
if filename.endswith(".json") & filename.startswith("stats"):
try:
sum += self.parseSessionStatsFile(os.path.join(dir,filename))
except:
self.LogInfo("ERROR parsing File: "+ filename)
return sum
def parseSessionStatsFile(self, path):
sum = 0
deauths = 0
handshakes = 0
associations = 0
with open(path) as json_file:
data = json.load(json_file)
for entry in data["data"]:
deauths += data["data"][entry]["num_deauths"]
handshakes += data["data"][entry]["num_handshakes"]
associations += data["data"][entry]["num_associations"]
sum += deauths * MULTIPLIER_DEAUTH
sum += handshakes * MULTIPLIER_HANDSHAKE
sum += associations * MULTIPLIER_ASSOCIATION
return sum
# If initial sum is 0, we try to parse it
def calculateInitialSum(self, agent):
sessionStatsActive = False
sum = 0
# Check if session stats is loaded
for plugin in pwnagotchi.plugins.loaded:
if plugin == "session-stats":
sessionStatsActive = True
break
if sessionStatsActive:
try:
self.LogInfo("parsing session-stats")
sum = self.parseSessionStats()
except:
self.LogInfo("Error parsing session-stats")
else:
self.LogInfo("parsing last session")
sum = self.lastSessionPoints(agent)
self.LogInfo(str(sum) + " Points calculated")
return sum
# Get Last Sessions Points
def lastSessionPoints(self, agent):
summary = 0
summary += agent.LastSession.handshakes * MULTIPLIER_HANDSHAKE
summary += agent.LastSession.associated * MULTIPLIER_ASSOCIATION
summary += agent.LastSession.deauthed * MULTIPLIER_DEAUTH
return summary
# Helper function to calculate multiple Levels from a sum of EXPs
def calcLevelFromSum(self, sum, agent):
sum1 = sum
level = 1
while sum1 > self.calcExpNeeded(level):
sum1 -= self.calcExpNeeded(level)
level += 1
self.lv = level
self.exp = sum1
self.expneeded = self.calcExpNeeded(level) - sum1
if level > 1:
#get Excited ;-)
self.displayLevelUp(agent)
def calcActualSum(self, level, exp):
lvlCounter = 1
sum = exp
# I know it wouldn't work if you change the lvl algorithm
while lvlCounter < level:
sum += self.calcExpNeeded(lvlCounter)
lvlCounter += 1
return sum
def displayLevelUp(self, agent):
view = agent.view()
view.set('face', FACE_LEVELUP)
view.set('status', "Level Up!")
view.update(force=True)
def calcStrength(self):
# Custom formula for strength calculation
self.strength = self.exp * self.lv * 0.05
# Event Handling
def on_association(self, agent, access_point):
self.exp += MULTIPLIER_ASSOCIATION
self.exp_tot += MULTIPLIER_ASSOCIATION
self.exp_check(agent)
self.Save(self.save_file, self.save_file_mode)
def on_deauthentication(self, agent, access_point, client_station):
self.exp += MULTIPLIER_DEAUTH
self.exp_tot += MULTIPLIER_DEAUTH
self.exp_check(agent)
self.Save(self.save_file, self.save_file_mode)
def on_handshake(self, agent, filename, access_point, client_station):
self.exp += MULTIPLIER_HANDSHAKE
self.exp_tot += MULTIPLIER_HANDSHAKE
self.exp_check(agent)
self.Save(self.save_file, self.save_file_mode)
def on_ai_best_reward(self, agent, reward):
self.exp += MULTIPLIER_AI_BEST_REWARD
self.exp_tot += MULTIPLIER_AI_BEST_REWARD
self.exp_check(agent)
self.Save(self.save_file, self.save_file_mode)
def on_ready(self, agent):
if self.calculateInitialXP:
self.LogInfo("Initial point calculation")
sum = self.calculateInitialSum(agent)
self.exp_tot = sum
self.calcLevelFromSum(sum, agent)
self.Save(self.save_file, self.save_file_mode)
================================================
FILE: plugins/ups_hat_c.py
================================================
# functions for get UPS status - needs enable "i2c" in raspi-config, smbus installed (sudo apt-get install -y python-smbus)
import logging
import time
import pwnagotchi
import pwnagotchi.plugins as plugins
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK
# Config Register (R/W)
_REG_CONFIG = 0x00
# SHUNT VOLTAGE REGISTER (R)
_REG_SHUNTVOLTAGE = 0x01
# BUS VOLTAGE REGISTER (R)
_REG_BUSVOLTAGE = 0x02
# POWER REGISTER (R)
_REG_POWER = 0x03
# CURRENT REGISTER (R)
_REG_CURRENT = 0x04
# CALIBRATION REGISTER (R/W)
_REG_CALIBRATION = 0x05
class UPS:
def __init__(self):
# only import when the module is loaded and enabled
import smbus
self._bus = smbus.SMBus(1)
self._addr = 0x43
# Set chip to known config values to start
self._cal_value = 0
self._current_lsb = 0
self._power_lsb = 0
self.set_calibration_32V_2A()
def read(self, address):
data = self._bus.read_i2c_block_data(self._addr, address, 2)
return ((data[0] * 256) + data[1])
def write(self, address, data):
temp = [0, 0]
temp[1] = data & 0xFF
temp[0] = (data & 0xFF00) >> 8
self._bus.write_i2c_block_data(self._addr, address, temp)
def set_calibration_32V_2A(self):
"""Configures to INA219 to be able to measure up to 32V and 2A of current. Counter
overflow occurs at 3.2A.
..note :: These calculations assume a 0.1 shunt ohm resistor is present
"""
self._cal_value = 0
self._current_lsb = 1 # Current LSB = 100uA per bit
self._cal_value = 4096
self._power_lsb = .002 # Power LSB = 2mW per bit
# Set Calibration register to 'Cal' calculated above
self.write(_REG_CALIBRATION, self._cal_value)
# Set Config register to take into account the settings above
self.bus_voltage_range = 0x01
self.gain = 0x03
self.bus_adc_resolution = 0x0D
self.shunt_adc_resolution = 0x0D
self.mode = 0x07
self.config = self.bus_voltage_range << 13 | \
self.gain << 11 | \
self.bus_adc_resolution << 7 | \
self.shunt_adc_resolution << 3 | \
self.mode
self.write(_REG_CONFIG, self.config)
def getBusVoltage_V(self):
self.write(_REG_CALIBRATION, self._cal_value)
self.read(_REG_BUSVOLTAGE)
return (self.read(_REG_BUSVOLTAGE) >> 3) * 0.004
def getCurrent_mA(self):
value = self.read(_REG_CURRENT)
if value > 32767:
value -= 65535
if (value * self._current_lsb) < 0:
return ""
else:
return "+"
class UPSC(plugins.Plugin):
__author__ = 'HannaDiamond'
__version__ = '1.0.1'
__license__ = 'MIT'
__description__ = 'A plugin that will add a battery capacity and charging indicator for the UPS HAT C'
def __init__(self):
self.ups = None
def on_loaded(self):
self.ups = UPS()
def on_ui_setup(self, ui):
if self.options["label_on"]:
ui.add_element('ups', LabeledValue(color=BLACK, label='BAT', value="--%",
position=(int(self.options["bat_x_coord"]),
int(self.options["bat_y_coord"])),
label_font=fonts.Bold, text_font=fonts.Medium))
else:
ui.add_element('ups', LabeledValue(color=BLACK, label='', value="--%",
position=(int(self.options["bat_x_coord"]),
int(self.options["bat_y_coord"])),
label_font=fonts.Bold, text_font=fonts.Medium))
def on_unload(self, ui):
with ui._lock:
ui.remove_element('ups')
def on_ui_update(self, ui):
bus_voltage = self.ups.getBusVoltage_V()
capacity = int((bus_voltage - 3) / 1.2 * 100)
if (capacity > 100): capacity = 100
if (capacity < 0): capacity = 0
charging = self.ups.getCurrent_mA()
ui.set('ups', str(capacity) + "%" + charging)
if capacity <= self.options['shutdown']:
logging.info('[ups_hat_c] Empty battery (<= %s%%): shutting down' % self.options['shutdown'])
ui.update(force=True, new_data={'status': 'Battery exhausted, bye ...'})
time.sleep(3)
pwnagotchi.shutdown()
================================================
FILE: waveshare_37inch/waveshare3in7.py
================================================
import logging
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.ui.hw.base import DisplayImpl
class Waveshare3in7(DisplayImpl):
def __init__(self, config):
super(Waveshare3in7, self).__init__(config, 'waveshare3in7')
def layout(self):
fonts.setup(20, 19, 20, 45, 35, 19)
self._layout['width'] = 480
self._layout['height'] = 280
self._layout['face'] = (0, 75)
self._layout['name'] = (5, 35)
self._layout['channel'] = (0, 0)
self._layout['aps'] = (65, 0)
self._layout['uptime'] = (355, 0)
self._layout['line1'] = [0, 25, 480, 25]
self._layout['line2'] = [0, 255, 480, 255]
self._layout['friend_face'] = (0, 146)
self._layout['friend_name'] = (40, 146)
self._layout['shakes'] = (0, 258)
self._layout['mode'] = (430, 258)
self._layout['status'] = {
'pos': (225, 35),
'font': fonts.status_font(fonts.Medium),
'max': 21
}
return self._layout
def initialize(self):
logging.info("initializing waveshare 3.7 inch lcd display")
from pwnagotchi.ui.hw.libs.waveshare.epaper.v3in7.epd3in7 import EPD
self._display = EPD()
self._display.init(0)
self._display.Clear(0)
self._display.init(1) # 1Gray mode
def render(self, canvas):
buf = self._display.getbuffer(canvas)
self._display.display_1Gray(buf)
def clear(self):
self._display.Clear(0)
gitextract_cqjvcuup/
├── .idea/
│ ├── .gitignore
│ ├── inspectionProfiles/
│ │ ├── Project_Default.xml
│ │ └── profiles_settings.xml
│ ├── markdown.xml
│ ├── misc.xml
│ ├── modules.xml
│ ├── pwnagotchi-plugins.iml
│ └── vcs.xml
├── LICENSE
├── README.md
├── plugins/
│ ├── age.py
│ ├── exp.py
│ └── ups_hat_c.py
└── waveshare_37inch/
└── waveshare3in7.py
SYMBOL INDEX (63 symbols across 4 files)
FILE: plugins/age.py
class Age (line 15) | class Age(plugins.Plugin):
method __init__ (line 21) | def __init__(self):
method on_loaded (line 25) | def on_loaded(self):
method on_ui_setup (line 29) | def on_ui_setup(self, ui):
method on_unload (line 39) | def on_unload(self, ui):
method on_ui_update (line 44) | def on_ui_update(self, ui):
method on_ai_training_step (line 49) | def on_ai_training_step(self, agent, _locals, _globals):
method abrev_number (line 55) | def abrev_number(self, num):
method age_checkpoint (line 66) | def age_checkpoint(self, agent):
method intelligence_checkpoint (line 72) | def intelligence_checkpoint(self, agent):
method calculate_device_age (line 79) | def calculate_device_age(self):
method load_data (line 100) | def load_data(self):
FILE: plugins/exp.py
class EXP (line 28) | class EXP(plugins.Plugin):
method LogInfo (line 35) | def LogInfo(self, text):
method LogDebug (line 39) | def LogDebug(self, text):
method __init__ (line 43) | def __init__(self):
method on_loaded (line 77) | def on_loaded(self):
method save_file_modes (line 81) | def save_file_modes(self,argument):
method Save (line 88) | def Save(self, file, save_file_mode):
method saveToTxtFile (line 95) | def saveToTxtFile(self, file):
method loadFromTxtFile (line 103) | def loadFromTxtFile(self, file):
method saveToJsonFile (line 120) | def saveToJsonFile(self,file):
method loadFromJsonFile (line 131) | def loadFromJsonFile(self, file):
method Load (line 146) | def Load(self, file, save_file_mode):
method getSaveFileName (line 153) | def getSaveFileName(self, save_file_mode):
method migrateLegacySave (line 165) | def migrateLegacySave(self):
method barString (line 174) | def barString(self, symbols_count, p):
method on_ui_setup (line 185) | def on_ui_setup(self, ui):
method on_ui_update (line 199) | def on_ui_update(self, ui):
method calcExpNeeded (line 209) | def calcExpNeeded(self, level):
method exp_check (line 215) | def exp_check(self, agent):
method parseSessionStats (line 223) | def parseSessionStats(self):
method parseSessionStatsFile (line 238) | def parseSessionStatsFile(self, path):
method calculateInitialSum (line 259) | def calculateInitialSum(self, agent):
method lastSessionPoints (line 286) | def lastSessionPoints(self, agent):
method calcLevelFromSum (line 295) | def calcLevelFromSum(self, sum, agent):
method calcActualSum (line 308) | def calcActualSum(self, level, exp):
method displayLevelUp (line 317) | def displayLevelUp(self, agent):
method calcStrength (line 323) | def calcStrength(self):
method on_association (line 328) | def on_association(self, agent, access_point):
method on_deauthentication (line 334) | def on_deauthentication(self, agent, access_point, client_station):
method on_handshake (line 340) | def on_handshake(self, agent, filename, access_point, client_station):
method on_ai_best_reward (line 346) | def on_ai_best_reward(self, agent, reward):
method on_ready (line 352) | def on_ready(self, agent):
FILE: plugins/ups_hat_c.py
class UPS (line 31) | class UPS:
method __init__ (line 32) | def __init__(self):
method read (line 44) | def read(self, address):
method write (line 48) | def write(self, address, data):
method set_calibration_32V_2A (line 54) | def set_calibration_32V_2A(self):
method getBusVoltage_V (line 81) | def getBusVoltage_V(self):
method getCurrent_mA (line 86) | def getCurrent_mA(self):
class UPSC (line 96) | class UPSC(plugins.Plugin):
method __init__ (line 102) | def __init__(self):
method on_loaded (line 105) | def on_loaded(self):
method on_ui_setup (line 108) | def on_ui_setup(self, ui):
method on_unload (line 120) | def on_unload(self, ui):
method on_ui_update (line 124) | def on_ui_update(self, ui):
FILE: waveshare_37inch/waveshare3in7.py
class Waveshare3in7 (line 7) | class Waveshare3in7(DisplayImpl):
method __init__ (line 8) | def __init__(self, config):
method layout (line 11) | def layout(self):
method initialize (line 33) | def initialize(self):
method render (line 41) | def render(self, canvas):
method clear (line 45) | def clear(self):
Condensed preview — 14 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (30K chars).
[
{
"path": ".idea/.gitignore",
"chars": 176,
"preview": "# Default ignored files\n/shelf/\n/workspace.xml\n# Editor-based HTTP Client requests\n/httpRequests/\n# Datasource local sto"
},
{
"path": ".idea/inspectionProfiles/Project_Default.xml",
"chars": 591,
"preview": "<component name=\"InspectionProjectProfileManager\">\n <profile version=\"1.0\">\n <option name=\"myName\" value=\"Project De"
},
{
"path": ".idea/inspectionProfiles/profiles_settings.xml",
"chars": 174,
"preview": "<component name=\"InspectionProjectProfileManager\">\n <settings>\n <option name=\"USE_PROJECT_PROFILE\" value=\"false\" />\n"
},
{
"path": ".idea/markdown.xml",
"chars": 182,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"MarkdownSettings\">\n <option name=\"hid"
},
{
"path": ".idea/misc.xml",
"chars": 210,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"ProjectRootManager\" version=\"2\" project-"
},
{
"path": ".idea/modules.xml",
"chars": 288,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"ProjectModuleManager\">\n <modules>\n "
},
{
"path": ".idea/pwnagotchi-plugins.iml",
"chars": 284,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"PYTHON_MODULE\" version=\"4\">\n <component name=\"NewModuleRootManager"
},
{
"path": ".idea/vcs.xml",
"chars": 180,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"VcsDirectoryMappings\">\n <mapping dire"
},
{
"path": "LICENSE",
"chars": 1070,
"preview": "MIT License\n\nCopyright (c) 2022 Hanna.Diamond\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
},
{
"path": "README.md",
"chars": 2279,
"preview": "# pwnagotchi-plugins\n\n# Custom Plugin Setup\nIf you have not set up a directory for custom plugins, create the directory "
},
{
"path": "plugins/age.py",
"chars": 3601,
"preview": "import os\nimport json\nimport logging\nfrom datetime import datetime\n\nimport pwnagotchi\nimport pwnagotchi.plugins as plugi"
},
{
"path": "plugins/exp.py",
"chars": 12917,
"preview": "import logging\r\nimport os\r\nimport random\r\nimport json\r\n\r\nimport pwnagotchi\r\nimport pwnagotchi.agent\r\nimport pwnagotchi.p"
},
{
"path": "plugins/ups_hat_c.py",
"chars": 4632,
"preview": "# functions for get UPS status - needs enable \"i2c\" in raspi-config, smbus installed (sudo apt-get install -y python-smb"
},
{
"path": "waveshare_37inch/waveshare3in7.py",
"chars": 1509,
"preview": "import logging\n\nimport pwnagotchi.ui.fonts as fonts\nfrom pwnagotchi.ui.hw.base import DisplayImpl\n\n\nclass Waveshare3in7("
}
]
About this extraction
This page contains the full source code of the hannadiamond/pwnagotchi-plugins GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 14 files (27.4 KB), approximately 7.2k tokens, and a symbol index with 63 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.