diff --git a/.idea/misc.xml b/.idea/misc.xml
index 6178365c..a971a2c9 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file
diff --git a/.idea/pwnagotchi.iml b/.idea/pwnagotchi.iml
index 7e680cfc..8d3ec578 100644
--- a/.idea/pwnagotchi.iml
+++ b/.idea/pwnagotchi.iml
@@ -4,7 +4,7 @@
-
+
diff --git a/pwnagotchi/ai/gym.py b/pwnagotchi/ai/gym.py
index 7134628b..6c0cac10 100644
--- a/pwnagotchi/ai/gym.py
+++ b/pwnagotchi/ai/gym.py
@@ -1,6 +1,6 @@
import logging
-import gym
-from gym import spaces
+import gymnasium
+from gymnasium import spaces
import numpy as np
import pwnagotchi.ai.featurizer as featurizer
@@ -8,7 +8,7 @@ import pwnagotchi.ai.reward as reward
from pwnagotchi.ai.parameter import Parameter
-class Environment(gym.Env):
+class Environment(gymnasium.Env):
metadata = {'render.modes': ['human']}
params = [
Parameter('min_rssi', min_value=-200, max_value=-50),
diff --git a/pwnagotchi/ai/parameter.py b/pwnagotchi/ai/parameter.py
index 79f464c5..812e9a4d 100644
--- a/pwnagotchi/ai/parameter.py
+++ b/pwnagotchi/ai/parameter.py
@@ -1,4 +1,4 @@
-from gym import spaces
+from gymnasium import spaces
class Parameter(object):
diff --git a/pwnagotchi/ai/train.py b/pwnagotchi/ai/train.py
index d8986393..c007230f 100644
--- a/pwnagotchi/ai/train.py
+++ b/pwnagotchi/ai/train.py
@@ -181,7 +181,7 @@ class AsyncTrainer(object):
if os.path.isfile(self._nn_path):
back = "%s.bak" % self._nn_path
os.replace(self._nn_path, back)
- self._view.set("mode", "AI")
+ self._view.set("mode", " AI")
self._model.learn(total_timesteps=epochs_per_episode, callback=self.on_ai_training_step)
except Exception as e:
logging.exception("[ai] error while training (%s)", e)
diff --git a/pwnagotchi/defaults.toml b/pwnagotchi/defaults.toml
index 6337fad6..1590c776 100644
--- a/pwnagotchi/defaults.toml
+++ b/pwnagotchi/defaults.toml
@@ -28,8 +28,8 @@ main.plugins.grid.exclude = [
"YourHomeNetworkHere"
]
-main.plugins.auto-update.enabled = false
-main.plugins.auto-update.install = false
+main.plugins.auto-update.enabled = true
+main.plugins.auto-update.install = true
main.plugins.auto-update.interval = 24
main.plugins.net-pos.enabled = false
@@ -39,13 +39,6 @@ main.plugins.gps.enabled = false
main.plugins.gps.speed = 19200
main.plugins.gps.device = "/dev/ttyUSB0"
-main.plugins.exp.enabled = true
-main.plugins.exp.lvl_x_coord = 0
-main.plugins.exp.lvl_y_coord = 81
-main.plugins.exp.exp_x_coord = 38
-main.plugins.exp.exp_y_coord = 81
-main.plugins.exp.bar_symbols_count = 12
-
main.plugins.bluetoothsniffer.enabled = false
main.plugins.bluetoothsniffer.timer = 45 # On how may seconds to scan for bluetooth devices
main.plugins.bluetoothsniffer.devices_file = "/root/handshakes/bluetooth_devices.json" # Path to the JSON file with bluetooth devices
@@ -146,12 +139,6 @@ main.log.path = "/var/log/pwnagotchi.log"
main.log.rotation.enabled = true
main.log.rotation.size = "10M"
-main.plugins.age.enabled = false
-main.plugins.age.age_x_coord = 0
-main.plugins.age.age_y_coord = 32
-main.plugins.age.str_x_coord = 67
-main.plugins.age.str_y_coord = 32
-
ai.enabled = true
ai.path = "/root/brain.nn"
ai.laziness = 0.1
diff --git a/pwnagotchi/plugins/__init__.py b/pwnagotchi/plugins/__init__.py
index b14e6880..2606b727 100644
--- a/pwnagotchi/plugins/__init__.py
+++ b/pwnagotchi/plugins/__init__.py
@@ -1,18 +1,15 @@
import os
import glob
+import _thread
import threading
import importlib, importlib.util
import logging
-from concurrent.futures import ThreadPoolExecutor
default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default")
loaded = {}
database = {}
locks = {}
-THREAD_POOL_SIZE = 10
-executor = ThreadPoolExecutor(max_workers=THREAD_POOL_SIZE)
-
class Plugin:
@classmethod
@@ -45,7 +42,7 @@ def toggle_plugin(name, enable=True):
global loaded, database
if pwnagotchi.config:
- if name not in pwnagotchi.config['main']['plugins']:
+ if not name in pwnagotchi.config['main']['plugins']:
pwnagotchi.config['main']['plugins'][name] = dict()
pwnagotchi.config['main']['plugins'][name]['enabled'] = enable
save_config(pwnagotchi.config, '/etc/pwnagotchi/config.toml')
@@ -97,7 +94,7 @@ def one(plugin_name, event_name, *args, **kwargs):
try:
lock_name = "%s::%s" % (plugin_name, cb_name)
locked_cb_args = (lock_name, callback, *args, *kwargs)
- executor.submit(locked_cb, *locked_cb_args)
+ _thread.start_new_thread(locked_cb, locked_cb_args)
except Exception as e:
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
logging.error(e, exc_info=True)
@@ -145,4 +142,4 @@ def load(config):
plugin.options = config['main']['plugins'][name]
on('loaded')
- on('config_changed', config)
+ on('config_changed', config)
\ No newline at end of file
diff --git a/pwnagotchi/plugins/default/age.py b/pwnagotchi/plugins/default/age.py
deleted file mode 100644
index 7b59e430..00000000
--- a/pwnagotchi/plugins/default/age.py
+++ /dev/null
@@ -1,88 +0,0 @@
-import os
-import json
-import logging
-
-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
-
-
-class Age(plugins.Plugin):
- __author__ = 'HannaDiamond'
- __version__ = '1.0.1'
- __license__ = 'MIT'
- __description__ = 'A plugin that will add age and strength stats based on epochs and trained epochs'
-
- def __init__(self):
- self.epochs = 0
- self.train_epochs = 0
-
- def on_loaded(self):
- data_path = '/root/brain.json'
- self.load_data(data_path)
-
-
- 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('Strength', 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_unload(self, ui):
- with ui._lock:
- ui.remove_element('Age')
- ui.remove_element('Strength')
-
- def on_ui_update(self, ui):
- ui.set('Age', str(self.abrev_number(self.epochs)))
- ui.set('Strength', str(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.strength_checkpoint(agent)
-
- def on_epoch(self, agent, epoch, epoch_data):
- self.epochs += 1
- if self.epochs % 100 == 0:
- 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 " + str(self.abrev_number(self.epochs)) + " epochs!")
- view.update(force=True)
-
- def strength_checkpoint(self, agent):
- view = agent.view()
- view.set('face', faces.MOTIVATED)
- view.set('status', "Look at my strength go up! \n"
- "I've trained for " + str(self.abrev_number(self.train_epochs)) + " epochs")
- view.update(force=True)
-
- def load_data(self, data_path):
- if os.path.exists(data_path):
- with open(data_path) as f:
- data = json.load(f)
- self.epochs = data['epochs_lived']
- self.train_epochs = data['epochs_trained']
-
diff --git a/pwnagotchi/plugins/default/exp.py b/pwnagotchi/plugins/default/exp.py
deleted file mode 100644
index 11812340..00000000
--- a/pwnagotchi/plugins/default/exp.py
+++ /dev/null
@@ -1,336 +0,0 @@
-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"
-
-
-class EXP(plugins.Plugin):
- __author__ = 'GaelicThunder'
- __version__ = '1.0.5'
- __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.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)
- 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)
- 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
- }
-
- 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]
- 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))
-
- 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)
-
- 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)
-
- # 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)
diff --git a/pwnagotchi/plugins/default/fix_brcmf_plugin.py b/pwnagotchi/plugins/default/fix_brcmf_plugin.py
index c67b9e0e..5747b601 100644
--- a/pwnagotchi/plugins/default/fix_brcmf_plugin.py
+++ b/pwnagotchi/plugins/default/fix_brcmf_plugin.py
@@ -226,7 +226,7 @@ class Fix_BRCMF(plugins.Plugin):
logging.info("[FixBRCMF] recon paused. Now trying wlan0mon reload")
try:
- cmd_output = subprocess.check_output("sudo ifconfig wlan0mon down && sudo iw dev wlan0mon del", shell=True)
+ cmd_output = subprocess.check_output("sudo airmon-ng stop wlan0mon", shell=True)
self._status = "dn"
self.logPrintView("info", "[FixBRCMF] wlan0mon down and deleted: %s" % cmd_output,
display, {"status": "wlan0mon d-d-d-down!", "face": faces.BORED})
diff --git a/requirements.txt b/requirements.txt
index d5f6079b..f1c2f445 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,24 +1,24 @@
-gym
+Gymnasium
shimmy
-pycryptodome>=3.9.4
-requests>=2.21.0
-PyYAML>=5.3.1
-scapy>=2.4.3
-tweepy>=3.7.0
-file-read-backwards>=2.0.0
-inky>=1.2.0
-smbus2>=0.3.0
-Pillow>=5.4.1
-spidev>=3.4
-gast>=0.2.2
-flask>=1.0.2
-flask-cors>=3.0.7
-flask-wtf>=0.14.3
-dbus-python>=1.2.12
-toml>=0.10.0
-python-dateutil>=2.8.1
-websockets>=8.1
-torch>=2.0.1
-torchvision>=0.15.2
-stable-baselines3>=1.4.0
+pycryptodome
+requests
+PyYAML
+scapy
+tweepy
+file-read-backwards
+inky
+smbus2
+Pillow
+spidev
+gast
+flask
+flask-cors
+flask-wtf
+dbus-python
+toml
+python-dateutil
+websockets
+torch
+torchvision
+stable_baselines3
RPi.GPIO