├── .idea
├── .gitignore
├── inspectionProfiles
│ ├── Project_Default.xml
│ └── profiles_settings.xml
├── markdown.xml
├── misc.xml
├── modules.xml
├── pwnagotchi-plugins.iml
└── vcs.xml
├── LICENSE
├── README.md
├── images
├── age.jpg
└── ups_hat_c.jpg
├── plugins
├── age.py
├── exp.py
└── ups_hat_c.py
└── waveshare_37inch
└── waveshare3in7.py
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/markdown.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/pwnagotchi-plugins.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Hanna.Diamond
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pwnagotchi-plugins
2 |
3 | # Custom Plugin Setup
4 | If you have not set up a directory for custom plugins, create the directory and add its path to your config.toml.
5 | `main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins/"`
6 |
7 | # Age Plugin
8 | A plugin that adds age and int stats based on the the time training and the number of epochs trained.
9 | Whenever your pwnagotchi has lived through another 100 epochs or epochs trained, a new status will appear!
10 | 
11 |
12 | ## Setup
13 | 1. Copy over `age.py` into your custom plugins directory
14 | 2. In your `config.toml` file add:
15 | ```toml
16 | main.plugins.age.enabled = true
17 | main.plugins.age.age_x_coord = 0
18 | main.plugins.age.age_y_coord = 32
19 | main.plugins.age.int_x_coord = 67
20 | main.plugins.age.int_y_coord = 32
21 | ```
22 | 3. Restart your device to see your new stats!
23 |
24 | # UPS HAT (C) Plugin
25 | A plugin that will add a battery capacity and charging indicator for the Waveshare UPS HAT (C)
26 | 
27 |
28 | ## Requirements
29 | - "i2c" in raspi-config enabled
30 | - smbus installed `sudo apt-get install -y python-smbus`
31 | ## Setup
32 | 1. Copy over `ups_hat_c.py` into your custom plugins directory
33 | 2. In your `config.toml` file, add:
34 | ```toml
35 | main.plugins.ups_hat_c.enabled = true
36 | main.plugins.ups_hat_c.label_on = true # show BAT label or just percentage
37 | main.plugins.ups_hat_c.shutdown = 5 # battery percent at which the device will turn off
38 | main.plugins.ups_hat_c.bat_x_coord = 140
39 | main.plugins.ups_hat_c.bat_y_coord = 0
40 | ```
41 | 3. Restart your device to see your new indicator!
42 |
43 | # Waveshare 3.7 Inch Display
44 |
45 | ## Setup
46 | 3. Copy `waveshare3in7.py` into `/usr/local/lib/python3.11/dist-packages/pwnagotchi/ui/hw`
47 | 4. In the `config.toml` set `ui.display.type = "waveshare3in7"`
48 | 5. In `/usr/local/lib/python3.11/dist-packages/pwnagotchi/ui/components.py` in the `class LabeledValue`, replace `def draw` with
49 | ```python
50 | def draw(self, canvas, drawer):
51 | if self.label is None:
52 | drawer.text(self.xy, self.value, font=self.label_font, fill=self.color)
53 | else:
54 | pos = self.xy
55 | drawer.text(pos, self.label, font=self.label_font, fill=self.color)
56 | 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)
57 | ```
58 |
--------------------------------------------------------------------------------
/images/age.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hannadiamond/pwnagotchi-plugins/80483470eee670bf0e11b0f1a731d2c3171e0ac9/images/age.jpg
--------------------------------------------------------------------------------
/images/ups_hat_c.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hannadiamond/pwnagotchi-plugins/80483470eee670bf0e11b0f1a731d2c3171e0ac9/images/ups_hat_c.jpg
--------------------------------------------------------------------------------
/plugins/age.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | import logging
4 | from datetime import datetime
5 |
6 | import pwnagotchi
7 | import pwnagotchi.plugins as plugins
8 | import pwnagotchi.ui.faces as faces
9 | import pwnagotchi.ui.fonts as fonts
10 | from pwnagotchi.ui.components import LabeledValue
11 | from pwnagotchi.ui.view import BLACK
12 |
13 | DATA_PATH = '/root/brain.json'
14 |
15 | class Age(plugins.Plugin):
16 | __author__ = 'HannaDiamond'
17 | __version__ = '2.0.1'
18 | __license__ = 'MIT'
19 | __description__ = 'A plugin that will add age and strength stats based on epochs and trained epochs'
20 |
21 | def __init__(self):
22 | self.train_epochs = 0
23 | self.device_start_time = datetime.now()
24 |
25 | def on_loaded(self):
26 | self.load_data()
27 |
28 |
29 | def on_ui_setup(self, ui):
30 | ui.add_element('Age', LabeledValue(color=BLACK, label='♥ Age', value=0,
31 | position=(int(self.options["age_x_coord"]),
32 | int(self.options["age_y_coord"])),
33 | label_font=fonts.Bold, text_font=fonts.Medium))
34 | ui.add_element('Int', LabeledValue(color=BLACK, label='Int', value=0,
35 | position=(int(self.options["int_x_coord"]),
36 | int(self.options["int_y_coord"])),
37 | label_font=fonts.Bold, text_font=fonts.Medium))
38 |
39 | def on_unload(self, ui):
40 | with ui._lock:
41 | ui.remove_element('Age')
42 | ui.remove_element('Int')
43 |
44 | def on_ui_update(self, ui):
45 | ui.set('Age', "%s" % self.calculate_device_age())
46 | ui.set('Int', "%s" % self.abrev_number(self.train_epochs))
47 |
48 |
49 | def on_ai_training_step(self, agent, _locals, _globals):
50 | self.train_epochs += 1
51 | if self.train_epochs % 100 == 0:
52 | self.intelligence_checkpoint(agent)
53 | self.age_checkpoint(agent)
54 |
55 | def abrev_number(self, num):
56 | if num < 100000:
57 | return str(num)
58 | else:
59 | magnitude = 0
60 | while abs(num) >= 1000:
61 | magnitude += 1
62 | num /= 1000.0
63 | abbr = ['', 'K', 'M', 'B', 'T', 'P'][magnitude]
64 | return '{}{}'.format('{:.2f}'.format(num).rstrip('0').rstrip('.'), abbr)
65 |
66 | def age_checkpoint(self, agent):
67 | view = agent.view()
68 | view.set('face', faces.HAPPY)
69 | view.set('status', "Wow, I've lived for " + self.calculate_device_age())
70 | view.update(force=True)
71 |
72 | def intelligence_checkpoint(self, agent):
73 | view = agent.view()
74 | view.set('face', faces.MOTIVATED)
75 | view.set('status', "Look at my intelligence go up! \n" \
76 | "I've trained for " + self.abrev_number(self.train_epochs) + " epochs")
77 | view.update(force=True)
78 |
79 | def calculate_device_age(self):
80 | current_time = datetime.now()
81 | age_delta = current_time - self.device_start_time
82 |
83 | years = age_delta.days // 365
84 | remaining_days = age_delta.days % 365
85 | months = remaining_days // 30
86 | days = remaining_days % 30
87 |
88 | age_str = ""
89 | if years != 0:
90 | age_str = f'{years}y'
91 | if months != 0:
92 | age_str = f'{age_str} {months}m'
93 | if len(age_str) !=0 :
94 | age_str = f'{age_str} {days}d'
95 | else:
96 | age_str = f'{days}d'
97 |
98 | return age_str
99 |
100 | def load_data(self):
101 | if os.path.exists(DATA_PATH):
102 | with open(DATA_PATH) as f:
103 | data = json.load(f)
104 | self.train_epochs = data['epochs_trained']
105 |
--------------------------------------------------------------------------------
/plugins/exp.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | import random
4 | import json
5 |
6 | import pwnagotchi
7 | import pwnagotchi.agent
8 | import pwnagotchi.plugins as plugins
9 | import pwnagotchi.ui.fonts as fonts
10 | from pwnagotchi.ui.components import LabeledValue
11 | from pwnagotchi.ui.view import BLACK
12 |
13 | # Static Variables
14 | MULTIPLIER_ASSOCIATION = 1
15 | MULTIPLIER_DEAUTH = 2
16 | MULTIPLIER_HANDSHAKE = 3
17 | MULTIPLIER_AI_BEST_REWARD = 5
18 | TAG = "[EXP Plugin]"
19 | FACE_LEVELUP = '(≧◡◡≦)'
20 | BAR_ERROR = "| error |"
21 | FILE_SAVE = "exp_stats"
22 | FILE_SAVE_LEGACY = "exp"
23 | JSON_KEY_LEVEL = "level"
24 | JSON_KEY_EXP ="exp"
25 | JSON_KEY_EXP_TOT ="exp_tot"
26 | JSON_KEY_STRENGTH = "strength"
27 |
28 | class EXP(plugins.Plugin):
29 | __author__ = 'GaelicThunder, HannaDiamond, Kaska89, Rai, JayofElony, & Terminator'
30 | __version__ = '2.0.1'
31 | __license__ = 'GPL3'
32 | __description__ = 'Get exp every time a handshake get captured.'
33 |
34 | # Attention number masking
35 | def LogInfo(self, text):
36 | logging.info(TAG + " " +text)
37 |
38 | # Attention number masking
39 | def LogDebug(self, text):
40 | logging.debug(TAG + " " +text)
41 |
42 |
43 | def __init__(self):
44 | self.percent=0
45 | self.strength=1
46 | self.calculateInitialXP = False
47 | self.exp=0
48 | self.lv=1
49 | self.exp_tot=0
50 | # Sets the file type I recommend json
51 | self.save_file_mode = self.save_file_modes("json")
52 | self.save_file = self.getSaveFileName(self.save_file_mode)
53 | # Migrate from old save system
54 | self.migrateLegacySave()
55 |
56 | # Create save file
57 | if not os.path.exists(self.save_file):
58 | self.Save(self.save_file, self.save_file_mode)
59 | else:
60 | try:
61 | # Try loading
62 | self.Load(self.save_file, self.save_file_mode)
63 | except:
64 | # Likely throws an exception if json file is corrupted, so we need to calculate from scratch
65 | self.calculateInitialXP = True
66 |
67 | # No previous data, try get it
68 | if self.lv == 1 and self.exp == 0:
69 | self.calculateInitialXP = True
70 | if self.exp_tot == 0:
71 | self.LogInfo("Need to calculate Total Exp")
72 | self.exp_tot = self.calcActualSum(self.lv, self.exp)
73 | self.Save(self.save_file, self.save_file_mode)
74 |
75 | self.expneeded = self.calcExpNeeded(self.lv)
76 |
77 | def on_loaded(self):
78 | # logging.info("Exp plugin loaded for %s" % self.options['device'])
79 | self.LogInfo("Plugin Loaded")
80 |
81 | def save_file_modes(self,argument):
82 | switcher = {
83 | "txt": 0,
84 | "json": 1,
85 | }
86 | return switcher.get(argument, 0)
87 |
88 | def Save(self, file, save_file_mode):
89 | self.LogDebug('Saving Exp')
90 | if save_file_mode == 0:
91 | self.saveToTxtFile(file)
92 | if save_file_mode == 1:
93 | self.saveToJsonFile(file)
94 |
95 | def saveToTxtFile(self, file):
96 | outfile=open(file, 'w')
97 | print(self.exp,file=outfile)
98 | print(self.lv,file=outfile)
99 | print(self.exp_tot,file=outfile)
100 | print(self.strength,file=outfile)
101 | outfile.close()
102 |
103 | def loadFromTxtFile(self, file):
104 | if os.path.exists(file):
105 | outfile= open(file, 'r+')
106 | lines = outfile.readlines()
107 | linecounter = 1
108 | for line in lines:
109 | if linecounter == 1:
110 | self.exp = int(line)
111 | elif linecounter == 2:
112 | self.lv == int(line)
113 | elif linecounter == 3:
114 | self.exp_tot == int(line)
115 | elif linecounter == 4:
116 | self.strength == int(line)
117 | linecounter += 1
118 | outfile.close()
119 |
120 | def saveToJsonFile(self,file):
121 | data = {
122 | JSON_KEY_LEVEL : self.lv,
123 | JSON_KEY_EXP : self.exp,
124 | JSON_KEY_EXP_TOT : self.exp_tot,
125 | JSON_KEY_STRENGTH : self.strength
126 | }
127 |
128 | with open(file, 'w') as f:
129 | f.write(json.dumps(data, sort_keys=True, indent=4, separators=(',', ': ')))
130 |
131 | def loadFromJsonFile(self, file):
132 | # Tot exp is introduced with json, no check needed
133 | data = {}
134 | with open(file, 'r') as f:
135 | data = json.loads(f.read())
136 |
137 | if bool(data):
138 | self.lv = data[JSON_KEY_LEVEL]
139 | self.exp = data[JSON_KEY_EXP]
140 | self.exp_tot = data[JSON_KEY_EXP_TOT]
141 | self.strength = data[JSON_KEY_STRENGTH]
142 | else:
143 | self.LogInfo("Empty json")
144 |
145 | # TODO: one day change save file mode to file date
146 | def Load(self, file, save_file_mode):
147 | self.LogDebug('Loading Exp')
148 | if save_file_mode == 0:
149 | self.loadFromTxtFile(file)
150 | if save_file_mode == 1:
151 | self.loadFromJsonFile(file)
152 |
153 | def getSaveFileName(self, save_file_mode):
154 | file = os.path.dirname(os.path.realpath(__file__))
155 | file = file + "/" +FILE_SAVE
156 | if save_file_mode == 0:
157 | file = file + ".txt"
158 | elif save_file_mode == 1:
159 | file = file + ".json"
160 | else:
161 | # See switcher
162 | file = file + ".txt"
163 | return file
164 |
165 | def migrateLegacySave(self):
166 | legacyFile = os.path.dirname(os.path.realpath(__file__))
167 | legacyFile = legacyFile + "/" + FILE_SAVE_LEGACY +".txt"
168 | if os.path.exists(legacyFile):
169 | self.loadFromTxtFile(legacyFile)
170 | self.LogInfo("Migrating Legacy Save...")
171 | self.Save(self.save_file, self.save_file_mode)
172 | os.remove(legacyFile)
173 |
174 | def barString(self, symbols_count, p):
175 | if p > 100:
176 | return BAR_ERROR
177 | length = symbols_count-2
178 | bar_char = '▥'
179 | blank_char = ' '
180 | bar_length = int(round((length / 100)*p))
181 | blank_length = length - bar_length
182 | res = '|' + bar_char * bar_length + blank_char * blank_length + '|'
183 | return res
184 |
185 | def on_ui_setup(self, ui):
186 | ui.add_element('Lv', LabeledValue(color=BLACK, label='Lv', value=0,
187 | position=(int(self.options["lvl_x_coord"]),
188 | int(self.options["lvl_y_coord"])),
189 | label_font=fonts.Bold, text_font=fonts.Medium))
190 | ui.add_element('Exp', LabeledValue(color=BLACK, label='Exp', value=0,
191 | position=(int(self.options["exp_x_coord"]),
192 | int(self.options["exp_y_coord"])),
193 | label_font=fonts.Bold, text_font=fonts.Medium))
194 | ui.add_element('Str', LabeledValue(color=BLACK, label='Str', value=0,
195 | position=(int(self.options["str_x_coord"]),
196 | int(self.options["str_y_coord"])),
197 | label_font=fonts.Bold, text_font=fonts.Medium))
198 |
199 | def on_ui_update(self, ui):
200 | self.expneeded=self.calcExpNeeded(self.lv)
201 | self.percent=int((self.exp/self.expneeded)*100)
202 | symbols_count=int(self.options["bar_symbols_count"])
203 | bar=self.barString(symbols_count, self.percent)
204 | ui.set('Lv', "%d" % self.lv)
205 | ui.set('Exp', "%s" % bar)
206 | self.calcStrength()
207 | ui.set('Str', "%d" % self.strength)
208 |
209 | def calcExpNeeded(self, level):
210 | # If the pwnagotchi is lvl <1 it causes the keys to be deleted
211 | if level == 1:
212 | return 5
213 | return int((level**3)/2)
214 |
215 | def exp_check(self, agent):
216 | self.LogDebug("EXP CHECK")
217 | if self.exp>=self.expneeded:
218 | self.exp=1
219 | self.lv=self.lv+1
220 | self.expneeded=self.calcExpNeeded(self.lv)
221 | self.displayLevelUp(agent)
222 |
223 | def parseSessionStats(self):
224 | sum = 0
225 | dir = pwnagotchi.config['main']['plugins']['session-stats']['save_directory']
226 | # TODO: remove
227 | self.LogInfo("Session-Stats dir: " + dir)
228 | for filename in os.listdir(dir):
229 | self.LogInfo("Parsing " + filename + "...")
230 | if filename.endswith(".json") & filename.startswith("stats"):
231 | try:
232 | sum += self.parseSessionStatsFile(os.path.join(dir,filename))
233 | except:
234 | self.LogInfo("ERROR parsing File: "+ filename)
235 |
236 | return sum
237 |
238 | def parseSessionStatsFile(self, path):
239 | sum = 0
240 | deauths = 0
241 | handshakes = 0
242 | associations = 0
243 | with open(path) as json_file:
244 | data = json.load(json_file)
245 | for entry in data["data"]:
246 | deauths += data["data"][entry]["num_deauths"]
247 | handshakes += data["data"][entry]["num_handshakes"]
248 | associations += data["data"][entry]["num_associations"]
249 |
250 |
251 | sum += deauths * MULTIPLIER_DEAUTH
252 | sum += handshakes * MULTIPLIER_HANDSHAKE
253 | sum += associations * MULTIPLIER_ASSOCIATION
254 |
255 | return sum
256 |
257 |
258 | # If initial sum is 0, we try to parse it
259 | def calculateInitialSum(self, agent):
260 | sessionStatsActive = False
261 | sum = 0
262 | # Check if session stats is loaded
263 | for plugin in pwnagotchi.plugins.loaded:
264 | if plugin == "session-stats":
265 | sessionStatsActive = True
266 | break
267 |
268 | if sessionStatsActive:
269 | try:
270 | self.LogInfo("parsing session-stats")
271 | sum = self.parseSessionStats()
272 | except:
273 | self.LogInfo("Error parsing session-stats")
274 |
275 |
276 | else:
277 | self.LogInfo("parsing last session")
278 | sum = self.lastSessionPoints(agent)
279 |
280 | self.LogInfo(str(sum) + " Points calculated")
281 | return sum
282 |
283 |
284 |
285 | # Get Last Sessions Points
286 | def lastSessionPoints(self, agent):
287 | summary = 0
288 | summary += agent.LastSession.handshakes * MULTIPLIER_HANDSHAKE
289 | summary += agent.LastSession.associated * MULTIPLIER_ASSOCIATION
290 | summary += agent.LastSession.deauthed * MULTIPLIER_DEAUTH
291 | return summary
292 |
293 |
294 | # Helper function to calculate multiple Levels from a sum of EXPs
295 | def calcLevelFromSum(self, sum, agent):
296 | sum1 = sum
297 | level = 1
298 | while sum1 > self.calcExpNeeded(level):
299 | sum1 -= self.calcExpNeeded(level)
300 | level += 1
301 | self.lv = level
302 | self.exp = sum1
303 | self.expneeded = self.calcExpNeeded(level) - sum1
304 | if level > 1:
305 | #get Excited ;-)
306 | self.displayLevelUp(agent)
307 |
308 | def calcActualSum(self, level, exp):
309 | lvlCounter = 1
310 | sum = exp
311 | # I know it wouldn't work if you change the lvl algorithm
312 | while lvlCounter < level:
313 | sum += self.calcExpNeeded(lvlCounter)
314 | lvlCounter += 1
315 | return sum
316 |
317 | def displayLevelUp(self, agent):
318 | view = agent.view()
319 | view.set('face', FACE_LEVELUP)
320 | view.set('status', "Level Up!")
321 | view.update(force=True)
322 |
323 | def calcStrength(self):
324 | # Custom formula for strength calculation
325 | self.strength = self.exp * self.lv * 0.05
326 |
327 | # Event Handling
328 | def on_association(self, agent, access_point):
329 | self.exp += MULTIPLIER_ASSOCIATION
330 | self.exp_tot += MULTIPLIER_ASSOCIATION
331 | self.exp_check(agent)
332 | self.Save(self.save_file, self.save_file_mode)
333 |
334 | def on_deauthentication(self, agent, access_point, client_station):
335 | self.exp += MULTIPLIER_DEAUTH
336 | self.exp_tot += MULTIPLIER_DEAUTH
337 | self.exp_check(agent)
338 | self.Save(self.save_file, self.save_file_mode)
339 |
340 | def on_handshake(self, agent, filename, access_point, client_station):
341 | self.exp += MULTIPLIER_HANDSHAKE
342 | self.exp_tot += MULTIPLIER_HANDSHAKE
343 | self.exp_check(agent)
344 | self.Save(self.save_file, self.save_file_mode)
345 |
346 | def on_ai_best_reward(self, agent, reward):
347 | self.exp += MULTIPLIER_AI_BEST_REWARD
348 | self.exp_tot += MULTIPLIER_AI_BEST_REWARD
349 | self.exp_check(agent)
350 | self.Save(self.save_file, self.save_file_mode)
351 |
352 | def on_ready(self, agent):
353 | if self.calculateInitialXP:
354 | self.LogInfo("Initial point calculation")
355 | sum = self.calculateInitialSum(agent)
356 | self.exp_tot = sum
357 | self.calcLevelFromSum(sum, agent)
358 | self.Save(self.save_file, self.save_file_mode)
359 |
--------------------------------------------------------------------------------
/plugins/ups_hat_c.py:
--------------------------------------------------------------------------------
1 | # functions for get UPS status - needs enable "i2c" in raspi-config, smbus installed (sudo apt-get install -y python-smbus)
2 |
3 |
4 | import logging
5 | import time
6 |
7 | import pwnagotchi
8 | import pwnagotchi.plugins as plugins
9 | import pwnagotchi.ui.fonts as fonts
10 | from pwnagotchi.ui.components import LabeledValue
11 | from pwnagotchi.ui.view import BLACK
12 |
13 | # Config Register (R/W)
14 | _REG_CONFIG = 0x00
15 | # SHUNT VOLTAGE REGISTER (R)
16 | _REG_SHUNTVOLTAGE = 0x01
17 |
18 | # BUS VOLTAGE REGISTER (R)
19 | _REG_BUSVOLTAGE = 0x02
20 |
21 | # POWER REGISTER (R)
22 | _REG_POWER = 0x03
23 |
24 | # CURRENT REGISTER (R)
25 | _REG_CURRENT = 0x04
26 |
27 | # CALIBRATION REGISTER (R/W)
28 | _REG_CALIBRATION = 0x05
29 |
30 |
31 | class UPS:
32 | def __init__(self):
33 | # only import when the module is loaded and enabled
34 | import smbus
35 | self._bus = smbus.SMBus(1)
36 | self._addr = 0x43
37 |
38 | # Set chip to known config values to start
39 | self._cal_value = 0
40 | self._current_lsb = 0
41 | self._power_lsb = 0
42 | self.set_calibration_32V_2A()
43 |
44 | def read(self, address):
45 | data = self._bus.read_i2c_block_data(self._addr, address, 2)
46 | return ((data[0] * 256) + data[1])
47 |
48 | def write(self, address, data):
49 | temp = [0, 0]
50 | temp[1] = data & 0xFF
51 | temp[0] = (data & 0xFF00) >> 8
52 | self._bus.write_i2c_block_data(self._addr, address, temp)
53 |
54 | def set_calibration_32V_2A(self):
55 | """Configures to INA219 to be able to measure up to 32V and 2A of current. Counter
56 | overflow occurs at 3.2A.
57 | ..note :: These calculations assume a 0.1 shunt ohm resistor is present
58 | """
59 |
60 | self._cal_value = 0
61 | self._current_lsb = 1 # Current LSB = 100uA per bit
62 | self._cal_value = 4096
63 | self._power_lsb = .002 # Power LSB = 2mW per bit
64 |
65 | # Set Calibration register to 'Cal' calculated above
66 | self.write(_REG_CALIBRATION, self._cal_value)
67 |
68 | # Set Config register to take into account the settings above
69 | self.bus_voltage_range = 0x01
70 | self.gain = 0x03
71 | self.bus_adc_resolution = 0x0D
72 | self.shunt_adc_resolution = 0x0D
73 | self.mode = 0x07
74 | self.config = self.bus_voltage_range << 13 | \
75 | self.gain << 11 | \
76 | self.bus_adc_resolution << 7 | \
77 | self.shunt_adc_resolution << 3 | \
78 | self.mode
79 | self.write(_REG_CONFIG, self.config)
80 |
81 | def getBusVoltage_V(self):
82 | self.write(_REG_CALIBRATION, self._cal_value)
83 | self.read(_REG_BUSVOLTAGE)
84 | return (self.read(_REG_BUSVOLTAGE) >> 3) * 0.004
85 |
86 | def getCurrent_mA(self):
87 | value = self.read(_REG_CURRENT)
88 | if value > 32767:
89 | value -= 65535
90 | if (value * self._current_lsb) < 0:
91 | return ""
92 | else:
93 | return "+"
94 |
95 |
96 | class UPSC(plugins.Plugin):
97 | __author__ = 'HannaDiamond'
98 | __version__ = '1.0.1'
99 | __license__ = 'MIT'
100 | __description__ = 'A plugin that will add a battery capacity and charging indicator for the UPS HAT C'
101 |
102 | def __init__(self):
103 | self.ups = None
104 |
105 | def on_loaded(self):
106 | self.ups = UPS()
107 |
108 | def on_ui_setup(self, ui):
109 | if self.options["label_on"]:
110 | ui.add_element('ups', LabeledValue(color=BLACK, label='BAT', value="--%",
111 | position=(int(self.options["bat_x_coord"]),
112 | int(self.options["bat_y_coord"])),
113 | label_font=fonts.Bold, text_font=fonts.Medium))
114 | else:
115 | ui.add_element('ups', LabeledValue(color=BLACK, label='', value="--%",
116 | position=(int(self.options["bat_x_coord"]),
117 | int(self.options["bat_y_coord"])),
118 | label_font=fonts.Bold, text_font=fonts.Medium))
119 |
120 | def on_unload(self, ui):
121 | with ui._lock:
122 | ui.remove_element('ups')
123 |
124 | def on_ui_update(self, ui):
125 | bus_voltage = self.ups.getBusVoltage_V()
126 | capacity = int((bus_voltage - 3) / 1.2 * 100)
127 | if (capacity > 100): capacity = 100
128 | if (capacity < 0): capacity = 0
129 |
130 | charging = self.ups.getCurrent_mA()
131 | ui.set('ups', str(capacity) + "%" + charging)
132 |
133 | if capacity <= self.options['shutdown']:
134 | logging.info('[ups_hat_c] Empty battery (<= %s%%): shutting down' % self.options['shutdown'])
135 | ui.update(force=True, new_data={'status': 'Battery exhausted, bye ...'})
136 | time.sleep(3)
137 | pwnagotchi.shutdown()
138 |
--------------------------------------------------------------------------------
/waveshare_37inch/waveshare3in7.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | import pwnagotchi.ui.fonts as fonts
4 | from pwnagotchi.ui.hw.base import DisplayImpl
5 |
6 |
7 | class Waveshare3in7(DisplayImpl):
8 | def __init__(self, config):
9 | super(Waveshare3in7, self).__init__(config, 'waveshare3in7')
10 |
11 | def layout(self):
12 | fonts.setup(20, 19, 20, 45, 35, 19)
13 | self._layout['width'] = 480
14 | self._layout['height'] = 280
15 | self._layout['face'] = (0, 75)
16 | self._layout['name'] = (5, 35)
17 | self._layout['channel'] = (0, 0)
18 | self._layout['aps'] = (65, 0)
19 | self._layout['uptime'] = (355, 0)
20 | self._layout['line1'] = [0, 25, 480, 25]
21 | self._layout['line2'] = [0, 255, 480, 255]
22 | self._layout['friend_face'] = (0, 146)
23 | self._layout['friend_name'] = (40, 146)
24 | self._layout['shakes'] = (0, 258)
25 | self._layout['mode'] = (430, 258)
26 | self._layout['status'] = {
27 | 'pos': (225, 35),
28 | 'font': fonts.status_font(fonts.Medium),
29 | 'max': 21
30 | }
31 | return self._layout
32 |
33 | def initialize(self):
34 | logging.info("initializing waveshare 3.7 inch lcd display")
35 | from pwnagotchi.ui.hw.libs.waveshare.epaper.v3in7.epd3in7 import EPD
36 | self._display = EPD()
37 | self._display.init(0)
38 | self._display.Clear(0)
39 | self._display.init(1) # 1Gray mode
40 |
41 | def render(self, canvas):
42 | buf = self._display.getbuffer(canvas)
43 | self._display.display_1Gray(buf)
44 |
45 | def clear(self):
46 | self._display.Clear(0)
47 |
--------------------------------------------------------------------------------