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
================================================
================================================
FILE: .idea/inspectionProfiles/profiles_settings.xml
================================================
================================================
FILE: .idea/markdown.xml
================================================
================================================
FILE: .idea/misc.xml
================================================
================================================
FILE: .idea/modules.xml
================================================
================================================
FILE: .idea/pwnagotchi-plugins.iml
================================================
================================================
FILE: .idea/vcs.xml
================================================
================================================
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)