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