[
  {
    "path": ".idea/.gitignore",
    "content": "# Default ignored files\n/shelf/\n/workspace.xml\n# Editor-based HTTP Client requests\n/httpRequests/\n# Datasource local storage ignored files\n/dataSources/\n/dataSources.local.xml\n"
  },
  {
    "path": ".idea/inspectionProfiles/Project_Default.xml",
    "content": "<component name=\"InspectionProjectProfileManager\">\n  <profile version=\"1.0\">\n    <option name=\"myName\" value=\"Project Default\" />\n    <inspection_tool class=\"Eslint\" enabled=\"true\" level=\"WARNING\" enabled_by_default=\"true\" />\n    <inspection_tool class=\"PyPackageRequirementsInspection\" enabled=\"true\" level=\"ERROR\" enabled_by_default=\"true\">\n      <option name=\"ignoredPackages\">\n        <value>\n          <list size=\"1\">\n            <item index=\"0\" class=\"java.lang.String\" itemvalue=\"\" />\n          </list>\n        </value>\n      </option>\n    </inspection_tool>\n  </profile>\n</component>"
  },
  {
    "path": ".idea/inspectionProfiles/profiles_settings.xml",
    "content": "<component name=\"InspectionProjectProfileManager\">\n  <settings>\n    <option name=\"USE_PROJECT_PROFILE\" value=\"false\" />\n    <version value=\"1.0\" />\n  </settings>\n</component>"
  },
  {
    "path": ".idea/markdown.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"MarkdownSettings\">\n    <option name=\"hideErrorsInCodeBlocks\" value=\"true\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/misc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectRootManager\" version=\"2\" project-jdk-name=\"Python 3.8 (automated_segmentation)\" project-jdk-type=\"Python SDK\" />\n</project>"
  },
  {
    "path": ".idea/modules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n      <module fileurl=\"file://$PROJECT_DIR$/.idea/pwnagotchi-plugins.iml\" filepath=\"$PROJECT_DIR$/.idea/pwnagotchi-plugins.iml\" />\n    </modules>\n  </component>\n</project>"
  },
  {
    "path": ".idea/pwnagotchi-plugins.iml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"PYTHON_MODULE\" version=\"4\">\n  <component name=\"NewModuleRootManager\">\n    <content url=\"file://$MODULE_DIR$\" />\n    <orderEntry type=\"inheritedJdk\" />\n    <orderEntry type=\"sourceFolder\" forTests=\"false\" />\n  </component>\n</module>"
  },
  {
    "path": ".idea/vcs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping directory=\"$PROJECT_DIR$\" vcs=\"Git\" />\n  </component>\n</project>"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 Hanna.Diamond\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# pwnagotchi-plugins\n\n# Custom Plugin Setup\nIf you have not set up a directory for custom plugins, create the directory and add its path to your config.toml.\n`main.custom_plugins = \"/usr/local/share/pwnagotchi/custom-plugins/\"`\n\n# Age Plugin\nA plugin that adds age and int stats based on the the time training and the number of epochs trained.\nWhenever your pwnagotchi has lived through another 100 epochs or epochs trained, a new status will appear!\n ![Age](images/age.jpg)\n\n## Setup\n1. Copy over `age.py` into your custom plugins directory\n2. In your `config.toml` file add:\n```toml\nmain.plugins.age.enabled = true\nmain.plugins.age.age_x_coord = 0\nmain.plugins.age.age_y_coord = 32\nmain.plugins.age.int_x_coord = 67\nmain.plugins.age.int_y_coord = 32\n```\n3. Restart your device to see your new stats!\n\n# UPS HAT (C) Plugin\nA plugin that will add a battery capacity and charging indicator for the Waveshare UPS HAT (C)\n ![UPS Hat C](images/ups_hat_c.jpg)\n\n## Requirements\n- \"i2c\" in raspi-config enabled \n- smbus installed `sudo apt-get install -y python-smbus`\n## Setup\n1. Copy over `ups_hat_c.py` into your custom plugins directory\n2. In your `config.toml` file, add:\n```toml\nmain.plugins.ups_hat_c.enabled = true\nmain.plugins.ups_hat_c.label_on = true  # show BAT label or just percentage\nmain.plugins.ups_hat_c.shutdown = 5  # battery percent at which the device will turn off\nmain.plugins.ups_hat_c.bat_x_coord = 140\nmain.plugins.ups_hat_c.bat_y_coord = 0\n```\n3. Restart your device to see your new indicator!\n\n# Waveshare 3.7 Inch Display \n\n## Setup\n3. Copy `waveshare3in7.py` into `/usr/local/lib/python3.11/dist-packages/pwnagotchi/ui/hw`\n4. In the `config.toml` set `ui.display.type = \"waveshare3in7\"`\n5. In `/usr/local/lib/python3.11/dist-packages/pwnagotchi/ui/components.py` in the `class LabeledValue`, replace `def draw` with\n```python\n    def draw(self, canvas, drawer):\n        if self.label is None:\n            drawer.text(self.xy, self.value, font=self.label_font, fill=self.color)\n        else:\n            pos = self.xy\n            drawer.text(pos, self.label, font=self.label_font, fill=self.color)\n            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)\n```\n"
  },
  {
    "path": "plugins/age.py",
    "content": "import os\nimport json\nimport logging\nfrom datetime import datetime\n\nimport pwnagotchi\nimport pwnagotchi.plugins as plugins\nimport pwnagotchi.ui.faces as faces\nimport pwnagotchi.ui.fonts as fonts\nfrom pwnagotchi.ui.components import LabeledValue\nfrom pwnagotchi.ui.view import BLACK\n\nDATA_PATH = '/root/brain.json'\n\nclass Age(plugins.Plugin):\n    __author__ = 'HannaDiamond'\n    __version__ = '2.0.1'\n    __license__ = 'MIT'\n    __description__ = 'A plugin that will add age and strength stats based on epochs and trained epochs'\n\n    def __init__(self):\n        self.train_epochs = 0\n        self.device_start_time = datetime.now()\n\n    def on_loaded(self):\n        self.load_data()\n\n\n    def on_ui_setup(self, ui):\n        ui.add_element('Age', LabeledValue(color=BLACK, label='♥ Age', value=0,\n                                           position=(int(self.options[\"age_x_coord\"]),\n                                                     int(self.options[\"age_y_coord\"])),\n                                           label_font=fonts.Bold, text_font=fonts.Medium))\n        ui.add_element('Int', LabeledValue(color=BLACK, label='Int', value=0,\n                                                position=(int(self.options[\"int_x_coord\"]),\n                                                          int(self.options[\"int_y_coord\"])),\n                                                label_font=fonts.Bold, text_font=fonts.Medium))\n\n    def on_unload(self, ui):\n        with ui._lock:\n            ui.remove_element('Age')\n            ui.remove_element('Int')\n\n    def on_ui_update(self, ui):\n        ui.set('Age', \"%s\" % self.calculate_device_age())\n        ui.set('Int', \"%s\" % self.abrev_number(self.train_epochs))\n\n\n    def on_ai_training_step(self, agent, _locals, _globals):\n        self.train_epochs += 1\n        if self.train_epochs % 100 == 0:\n            self.intelligence_checkpoint(agent)\n            self.age_checkpoint(agent)\n\n    def abrev_number(self, num):\n        if num < 100000:\n            return str(num)\n        else:\n            magnitude = 0\n            while abs(num) >= 1000:\n                magnitude += 1\n                num /= 1000.0\n                abbr = ['', 'K', 'M', 'B', 'T', 'P'][magnitude]\n            return '{}{}'.format('{:.2f}'.format(num).rstrip('0').rstrip('.'), abbr)\n\n    def age_checkpoint(self, agent):\n        view = agent.view()\n        view.set('face', faces.HAPPY)\n        view.set('status', \"Wow, I've lived for \" + self.calculate_device_age())\n        view.update(force=True)\n\n    def intelligence_checkpoint(self, agent):\n        view = agent.view()\n        view.set('face', faces.MOTIVATED)\n        view.set('status', \"Look at my intelligence go up! \\n\" \\\n                \"I've trained for \" + self.abrev_number(self.train_epochs) + \" epochs\")\n        view.update(force=True)\n\n    def calculate_device_age(self):\n        current_time = datetime.now()\n        age_delta = current_time - self.device_start_time\n\n        years = age_delta.days // 365\n        remaining_days = age_delta.days % 365\n        months = remaining_days // 30\n        days = remaining_days % 30\n\n        age_str = \"\"\n        if years != 0:\n            age_str = f'{years}y'\n        if months != 0:\n            age_str = f'{age_str} {months}m'\n        if len(age_str) !=0 :\n            age_str = f'{age_str} {days}d'\n        else:\n            age_str = f'{days}d'\n\n        return age_str\n\n    def load_data(self):\n        if os.path.exists(DATA_PATH):\n            with open(DATA_PATH) as f:\n                data = json.load(f)\n                self.train_epochs = data['epochs_trained']\n"
  },
  {
    "path": "plugins/exp.py",
    "content": "import logging\r\nimport os\r\nimport random\r\nimport json\r\n\r\nimport pwnagotchi\r\nimport pwnagotchi.agent\r\nimport pwnagotchi.plugins as plugins\r\nimport pwnagotchi.ui.fonts as fonts\r\nfrom pwnagotchi.ui.components import LabeledValue\r\nfrom pwnagotchi.ui.view import BLACK\r\n\r\n# Static Variables\r\nMULTIPLIER_ASSOCIATION = 1\r\nMULTIPLIER_DEAUTH = 2\r\nMULTIPLIER_HANDSHAKE = 3\r\nMULTIPLIER_AI_BEST_REWARD = 5\r\nTAG = \"[EXP Plugin]\"\r\nFACE_LEVELUP = '(≧◡◡≦)'\r\nBAR_ERROR = \"|   error  |\"\r\nFILE_SAVE = \"exp_stats\"\r\nFILE_SAVE_LEGACY = \"exp\"\r\nJSON_KEY_LEVEL = \"level\"\r\nJSON_KEY_EXP =\"exp\"\r\nJSON_KEY_EXP_TOT =\"exp_tot\"\r\nJSON_KEY_STRENGTH = \"strength\"\r\n\r\nclass EXP(plugins.Plugin):\r\n    __author__ = 'GaelicThunder, HannaDiamond, Kaska89, Rai, JayofElony, & Terminator'\r\n    __version__ = '2.0.1'\r\n    __license__ = 'GPL3'\r\n    __description__ = 'Get exp every time a handshake get captured.'\r\n\r\n    # Attention number masking\r\n    def LogInfo(self, text):\r\n        logging.info(TAG + \" \" +text)\r\n    \r\n    # Attention number masking\r\n    def LogDebug(self, text):\r\n        logging.debug(TAG + \" \" +text)\r\n    \r\n    \r\n    def __init__(self):\r\n        self.percent=0\r\n        self.strength=1\r\n        self.calculateInitialXP = False\r\n        self.exp=0\r\n        self.lv=1\r\n        self.exp_tot=0\r\n        # Sets the file type I recommend json\r\n        self.save_file_mode = self.save_file_modes(\"json\")\r\n        self.save_file = self.getSaveFileName(self.save_file_mode)\r\n        # Migrate from old save system\r\n        self.migrateLegacySave()\r\n\r\n        # Create save file\r\n        if not os.path.exists(self.save_file):\r\n            self.Save(self.save_file, self.save_file_mode)\r\n        else:\r\n            try:\r\n                # Try loading\r\n                self.Load(self.save_file, self.save_file_mode)\r\n            except:\r\n                # Likely throws an exception if json file is corrupted, so we need to calculate from scratch\r\n                self.calculateInitialXP = True\r\n\r\n        # No previous data, try get it\r\n        if self.lv == 1 and self.exp == 0:\r\n            self.calculateInitialXP = True\r\n        if self.exp_tot == 0:\r\n            self.LogInfo(\"Need to calculate Total Exp\")\r\n            self.exp_tot = self.calcActualSum(self.lv, self.exp)\r\n            self.Save(self.save_file, self.save_file_mode)\r\n            \r\n        self.expneeded = self.calcExpNeeded(self.lv)\r\n        \r\n    def on_loaded(self):\r\n        # logging.info(\"Exp plugin loaded for %s\" % self.options['device'])\r\n        self.LogInfo(\"Plugin Loaded\")\r\n\r\n    def save_file_modes(self,argument): \r\n        switcher = { \r\n            \"txt\": 0, \r\n            \"json\": 1,  \r\n        }\r\n        return switcher.get(argument, 0) \r\n\r\n    def Save(self, file, save_file_mode):\r\n        self.LogDebug('Saving Exp')\r\n        if save_file_mode == 0:\r\n            self.saveToTxtFile(file)\r\n        if save_file_mode == 1:\r\n            self.saveToJsonFile(file)\r\n\r\n    def saveToTxtFile(self, file):\r\n        outfile=open(file, 'w')\r\n        print(self.exp,file=outfile)\r\n        print(self.lv,file=outfile)\r\n        print(self.exp_tot,file=outfile)\r\n        print(self.strength,file=outfile)\r\n        outfile.close()\r\n\r\n    def loadFromTxtFile(self, file):\r\n        if os.path.exists(file):\r\n            outfile= open(file, 'r+')\r\n            lines = outfile.readlines()\r\n            linecounter = 1\r\n            for line in lines:\r\n                if linecounter == 1:\r\n                    self.exp = int(line)\r\n                elif linecounter == 2:\r\n                    self.lv == int(line)\r\n                elif linecounter == 3:\r\n                    self.exp_tot == int(line)\r\n                elif linecounter == 4:\r\n                    self.strength == int(line)\r\n                linecounter += 1\r\n            outfile.close()\r\n    \r\n    def saveToJsonFile(self,file):\r\n        data = {\r\n            JSON_KEY_LEVEL : self.lv,\r\n            JSON_KEY_EXP : self.exp,\r\n            JSON_KEY_EXP_TOT : self.exp_tot,\r\n            JSON_KEY_STRENGTH : self.strength\r\n        }\r\n\r\n        with open(file, 'w') as f:\r\n            f.write(json.dumps(data, sort_keys=True, indent=4, separators=(',', ': ')))\r\n\r\n    def loadFromJsonFile(self, file):\r\n        # Tot exp is introduced with json, no check needed\r\n        data = {}\r\n        with open(file, 'r') as f:\r\n            data = json.loads(f.read())\r\n        \r\n        if bool(data):\r\n            self.lv = data[JSON_KEY_LEVEL]\r\n            self.exp = data[JSON_KEY_EXP]\r\n            self.exp_tot = data[JSON_KEY_EXP_TOT]\r\n            self.strength = data[JSON_KEY_STRENGTH]\r\n        else:\r\n            self.LogInfo(\"Empty json\")\r\n    \r\n    # TODO: one day change save file mode to file date\r\n    def Load(self, file, save_file_mode):\r\n        self.LogDebug('Loading Exp')\r\n        if save_file_mode == 0:\r\n            self.loadFromTxtFile(file)\r\n        if save_file_mode == 1:\r\n            self.loadFromJsonFile(file)\r\n    \r\n    def getSaveFileName(self, save_file_mode):\r\n        file = os.path.dirname(os.path.realpath(__file__))\r\n        file = file + \"/\" +FILE_SAVE\r\n        if save_file_mode == 0:\r\n            file = file + \".txt\"\r\n        elif save_file_mode == 1:\r\n            file = file + \".json\"\r\n        else:\r\n            # See switcher\r\n            file = file + \".txt\"\r\n        return file\r\n    \r\n    def migrateLegacySave(self):\r\n        legacyFile = os.path.dirname(os.path.realpath(__file__))\r\n        legacyFile = legacyFile + \"/\" + FILE_SAVE_LEGACY +\".txt\"\r\n        if os.path.exists(legacyFile):\r\n            self.loadFromTxtFile(legacyFile)\r\n            self.LogInfo(\"Migrating Legacy Save...\")\r\n            self.Save(self.save_file, self.save_file_mode)\r\n            os.remove(legacyFile)\r\n\r\n    def barString(self, symbols_count, p):\r\n        if p > 100:\r\n            return BAR_ERROR\r\n        length = symbols_count-2\r\n        bar_char = '▥'\r\n        blank_char = ' '\r\n        bar_length = int(round((length / 100)*p))\r\n        blank_length = length - bar_length\r\n        res = '|' + bar_char * bar_length + blank_char * blank_length + '|'\r\n        return res\r\n\r\n    def on_ui_setup(self, ui):\r\n        ui.add_element('Lv', LabeledValue(color=BLACK, label='Lv', value=0,\r\n                                          position=(int(self.options[\"lvl_x_coord\"]),\r\n                                                    int(self.options[\"lvl_y_coord\"])),\r\n                                          label_font=fonts.Bold, text_font=fonts.Medium))\r\n        ui.add_element('Exp', LabeledValue(color=BLACK, label='Exp', value=0,\r\n                                           position=(int(self.options[\"exp_x_coord\"]),\r\n                                                     int(self.options[\"exp_y_coord\"])),\r\n                                          label_font=fonts.Bold, text_font=fonts.Medium))\r\n        ui.add_element('Str', LabeledValue(color=BLACK, label='Str', value=0,\r\n                                           position=(int(self.options[\"str_x_coord\"]),\r\n                                                     int(self.options[\"str_y_coord\"])),\r\n                                          label_font=fonts.Bold, text_font=fonts.Medium))\r\n\r\n    def on_ui_update(self, ui):\r\n        self.expneeded=self.calcExpNeeded(self.lv)\r\n        self.percent=int((self.exp/self.expneeded)*100)\r\n        symbols_count=int(self.options[\"bar_symbols_count\"])\r\n        bar=self.barString(symbols_count, self.percent) \r\n        ui.set('Lv', \"%d\" % self.lv)\r\n        ui.set('Exp', \"%s\" % bar)\r\n        self.calcStrength()\r\n        ui.set('Str', \"%d\" % self.strength)\r\n\r\n    def calcExpNeeded(self, level):\r\n        # If the pwnagotchi is lvl <1 it causes the keys to be deleted\r\n        if level == 1:\r\n            return 5\r\n        return int((level**3)/2)\r\n\r\n    def exp_check(self, agent):\r\n        self.LogDebug(\"EXP CHECK\")\r\n        if self.exp>=self.expneeded:\r\n            self.exp=1\r\n            self.lv=self.lv+1\r\n            self.expneeded=self.calcExpNeeded(self.lv)\r\n            self.displayLevelUp(agent)\r\n\r\n    def parseSessionStats(self):\r\n        sum = 0\r\n        dir = pwnagotchi.config['main']['plugins']['session-stats']['save_directory']\r\n        # TODO: remove\r\n        self.LogInfo(\"Session-Stats dir: \" + dir)\r\n        for filename in os.listdir(dir):\r\n            self.LogInfo(\"Parsing \" + filename + \"...\")\r\n            if filename.endswith(\".json\") & filename.startswith(\"stats\"):\r\n                try:\r\n                    sum += self.parseSessionStatsFile(os.path.join(dir,filename))\r\n                except:\r\n                    self.LogInfo(\"ERROR parsing File: \"+ filename)\r\n                \r\n        return sum\r\n\r\n    def parseSessionStatsFile(self, path):\r\n        sum = 0\r\n        deauths = 0\r\n        handshakes = 0\r\n        associations = 0\r\n        with open(path) as json_file:\r\n            data = json.load(json_file)\r\n            for entry in data[\"data\"]:\r\n                deauths += data[\"data\"][entry][\"num_deauths\"]\r\n                handshakes += data[\"data\"][entry][\"num_handshakes\"]\r\n                associations += data[\"data\"][entry][\"num_associations\"]\r\n            \r\n\r\n        sum += deauths * MULTIPLIER_DEAUTH\r\n        sum += handshakes * MULTIPLIER_HANDSHAKE\r\n        sum += associations * MULTIPLIER_ASSOCIATION\r\n\r\n        return sum\r\n\r\n\r\n    # If initial sum is 0, we try to parse it\r\n    def calculateInitialSum(self, agent):\r\n        sessionStatsActive = False\r\n        sum = 0\r\n        # Check if session stats is loaded\r\n        for plugin in pwnagotchi.plugins.loaded:\r\n            if plugin == \"session-stats\":\r\n                sessionStatsActive = True\r\n                break\r\n        \r\n        if sessionStatsActive:\r\n            try:\r\n                self.LogInfo(\"parsing session-stats\")\r\n                sum = self.parseSessionStats()\r\n            except:\r\n                self.LogInfo(\"Error parsing session-stats\")\r\n            \r\n            \r\n        else:\r\n            self.LogInfo(\"parsing last session\")\r\n            sum = self.lastSessionPoints(agent)\r\n\r\n        self.LogInfo(str(sum) + \" Points calculated\")\r\n        return sum\r\n\r\n\r\n        \r\n    # Get Last Sessions Points\r\n    def lastSessionPoints(self, agent):\r\n        summary = 0\r\n        summary += agent.LastSession.handshakes * MULTIPLIER_HANDSHAKE\r\n        summary += agent.LastSession.associated * MULTIPLIER_ASSOCIATION\r\n        summary += agent.LastSession.deauthed * MULTIPLIER_DEAUTH\r\n        return summary\r\n\r\n    \r\n    # Helper function to calculate multiple Levels from a sum of EXPs\r\n    def calcLevelFromSum(self, sum, agent):\r\n        sum1 = sum\r\n        level = 1\r\n        while sum1 > self.calcExpNeeded(level):\r\n            sum1 -= self.calcExpNeeded(level)\r\n            level += 1         \r\n        self.lv = level\r\n        self.exp = sum1\r\n        self.expneeded = self.calcExpNeeded(level) - sum1\r\n        if level > 1:\r\n            #get Excited ;-)\r\n            self.displayLevelUp(agent)\r\n\r\n    def calcActualSum(self, level, exp):\r\n        lvlCounter = 1\r\n        sum = exp\r\n        # I know it wouldn't work if you change the lvl algorithm\r\n        while lvlCounter < level:\r\n            sum += self.calcExpNeeded(lvlCounter)\r\n            lvlCounter += 1\r\n        return sum\r\n    \r\n    def displayLevelUp(self, agent):\r\n        view =  agent.view()\r\n        view.set('face', FACE_LEVELUP)\r\n        view.set('status', \"Level Up!\")\r\n        view.update(force=True)\r\n        \r\n    def calcStrength(self):\r\n        # Custom formula for strength calculation\r\n        self.strength = self.exp * self.lv * 0.05\r\n\r\n    # Event Handling\r\n    def on_association(self, agent, access_point):\r\n        self.exp += MULTIPLIER_ASSOCIATION\r\n        self.exp_tot += MULTIPLIER_ASSOCIATION\r\n        self.exp_check(agent)\r\n        self.Save(self.save_file, self.save_file_mode)\r\n        \r\n    def on_deauthentication(self, agent, access_point, client_station):\r\n        self.exp += MULTIPLIER_DEAUTH\r\n        self.exp_tot += MULTIPLIER_DEAUTH\r\n        self.exp_check(agent)\r\n        self.Save(self.save_file, self.save_file_mode)\r\n        \r\n    def on_handshake(self, agent, filename, access_point, client_station):\r\n        self.exp += MULTIPLIER_HANDSHAKE\r\n        self.exp_tot += MULTIPLIER_HANDSHAKE\r\n        self.exp_check(agent)\r\n        self.Save(self.save_file, self.save_file_mode)\r\n        \r\n    def on_ai_best_reward(self, agent, reward):\r\n        self.exp += MULTIPLIER_AI_BEST_REWARD\r\n        self.exp_tot += MULTIPLIER_AI_BEST_REWARD\r\n        self.exp_check(agent)\r\n        self.Save(self.save_file, self.save_file_mode)\r\n\r\n    def on_ready(self, agent):\r\n        if self.calculateInitialXP:\r\n            self.LogInfo(\"Initial point calculation\")\r\n            sum = self.calculateInitialSum(agent)\r\n            self.exp_tot = sum\r\n            self.calcLevelFromSum(sum, agent)\r\n            self.Save(self.save_file, self.save_file_mode)\r\n                "
  },
  {
    "path": "plugins/ups_hat_c.py",
    "content": "# functions for get UPS status - needs enable \"i2c\" in raspi-config, smbus installed (sudo apt-get install -y python-smbus)\n\n\nimport logging\nimport time\n\nimport pwnagotchi\nimport pwnagotchi.plugins as plugins\nimport pwnagotchi.ui.fonts as fonts\nfrom pwnagotchi.ui.components import LabeledValue\nfrom pwnagotchi.ui.view import BLACK\n\n# Config Register (R/W)\n_REG_CONFIG = 0x00\n# SHUNT VOLTAGE REGISTER (R)\n_REG_SHUNTVOLTAGE = 0x01\n\n# BUS VOLTAGE REGISTER (R)\n_REG_BUSVOLTAGE = 0x02\n\n# POWER REGISTER (R)\n_REG_POWER = 0x03\n\n# CURRENT REGISTER (R)\n_REG_CURRENT = 0x04\n\n# CALIBRATION REGISTER (R/W)\n_REG_CALIBRATION = 0x05\n\n\nclass UPS:\n    def __init__(self):\n        # only import when the module is loaded and enabled\n        import smbus\n        self._bus = smbus.SMBus(1)\n        self._addr = 0x43\n\n        # Set chip to known config values to start\n        self._cal_value = 0\n        self._current_lsb = 0\n        self._power_lsb = 0\n        self.set_calibration_32V_2A()\n\n    def read(self, address):\n        data = self._bus.read_i2c_block_data(self._addr, address, 2)\n        return ((data[0] * 256) + data[1])\n\n    def write(self, address, data):\n        temp = [0, 0]\n        temp[1] = data & 0xFF\n        temp[0] = (data & 0xFF00) >> 8\n        self._bus.write_i2c_block_data(self._addr, address, temp)\n\n    def set_calibration_32V_2A(self):\n        \"\"\"Configures to INA219 to be able to measure up to 32V and 2A of current. Counter\n           overflow occurs at 3.2A.\n           ..note :: These calculations assume a 0.1 shunt ohm resistor is present\n        \"\"\"\n\n        self._cal_value = 0\n        self._current_lsb = 1  # Current LSB = 100uA per bit\n        self._cal_value = 4096\n        self._power_lsb = .002  # Power LSB = 2mW per bit\n\n        # Set Calibration register to 'Cal' calculated above\n        self.write(_REG_CALIBRATION, self._cal_value)\n\n        # Set Config register to take into account the settings above\n        self.bus_voltage_range = 0x01\n        self.gain = 0x03\n        self.bus_adc_resolution = 0x0D\n        self.shunt_adc_resolution = 0x0D\n        self.mode = 0x07\n        self.config = self.bus_voltage_range << 13 | \\\n                      self.gain << 11 | \\\n                      self.bus_adc_resolution << 7 | \\\n                      self.shunt_adc_resolution << 3 | \\\n                      self.mode\n        self.write(_REG_CONFIG, self.config)\n\n    def getBusVoltage_V(self):\n        self.write(_REG_CALIBRATION, self._cal_value)\n        self.read(_REG_BUSVOLTAGE)\n        return (self.read(_REG_BUSVOLTAGE) >> 3) * 0.004\n\n    def getCurrent_mA(self):\n        value = self.read(_REG_CURRENT)\n        if value > 32767:\n            value -= 65535\n        if (value * self._current_lsb) < 0:\n            return \"\"\n        else:\n            return \"+\"\n\n\nclass UPSC(plugins.Plugin):\n    __author__ = 'HannaDiamond'\n    __version__ = '1.0.1'\n    __license__ = 'MIT'\n    __description__ = 'A plugin that will add a battery capacity and charging indicator for the UPS HAT C'\n\n    def __init__(self):\n        self.ups = None\n\n    def on_loaded(self):\n        self.ups = UPS()\n\n    def on_ui_setup(self, ui):\n        if self.options[\"label_on\"]:\n            ui.add_element('ups', LabeledValue(color=BLACK, label='BAT', value=\"--%\",\n                                               position=(int(self.options[\"bat_x_coord\"]),\n                                                         int(self.options[\"bat_y_coord\"])),\n                                               label_font=fonts.Bold, text_font=fonts.Medium))\n        else:\n            ui.add_element('ups', LabeledValue(color=BLACK, label='', value=\"--%\",\n                                               position=(int(self.options[\"bat_x_coord\"]),\n                                                         int(self.options[\"bat_y_coord\"])),\n                                               label_font=fonts.Bold, text_font=fonts.Medium))\n\n    def on_unload(self, ui):\n        with ui._lock:\n            ui.remove_element('ups')\n\n    def on_ui_update(self, ui):\n        bus_voltage = self.ups.getBusVoltage_V()\n        capacity = int((bus_voltage - 3) / 1.2 * 100)\n        if (capacity > 100): capacity = 100\n        if (capacity < 0): capacity = 0\n\n        charging = self.ups.getCurrent_mA()\n        ui.set('ups', str(capacity) + \"%\" + charging)\n\n        if capacity <= self.options['shutdown']:\n            logging.info('[ups_hat_c] Empty battery (<= %s%%): shutting down' % self.options['shutdown'])\n            ui.update(force=True, new_data={'status': 'Battery exhausted, bye ...'})\n            time.sleep(3)\n            pwnagotchi.shutdown()\n"
  },
  {
    "path": "waveshare_37inch/waveshare3in7.py",
    "content": "import logging\n\nimport pwnagotchi.ui.fonts as fonts\nfrom pwnagotchi.ui.hw.base import DisplayImpl\n\n\nclass Waveshare3in7(DisplayImpl):\n    def __init__(self, config):\n        super(Waveshare3in7, self).__init__(config, 'waveshare3in7')\n\n    def layout(self):\n        fonts.setup(20, 19, 20, 45, 35, 19)\n        self._layout['width'] = 480\n        self._layout['height'] = 280\n        self._layout['face'] = (0, 75)\n        self._layout['name'] = (5, 35)\n        self._layout['channel'] = (0, 0)\n        self._layout['aps'] = (65, 0)\n        self._layout['uptime'] = (355, 0)\n        self._layout['line1'] = [0, 25, 480, 25]\n        self._layout['line2'] = [0, 255, 480, 255]\n        self._layout['friend_face'] = (0, 146)\n        self._layout['friend_name'] = (40, 146)\n        self._layout['shakes'] = (0, 258)\n        self._layout['mode'] = (430, 258)\n        self._layout['status'] = {\n            'pos': (225, 35),\n            'font': fonts.status_font(fonts.Medium),\n            'max': 21\n        }\n        return self._layout\n\n    def initialize(self):\n        logging.info(\"initializing waveshare 3.7 inch lcd display\")\n        from pwnagotchi.ui.hw.libs.waveshare.epaper.v3in7.epd3in7 import EPD\n        self._display = EPD()\n        self._display.init(0)\n        self._display.Clear(0)\n        self._display.init(1)  # 1Gray mode\n\n    def render(self, canvas):\n        buf = self._display.getbuffer(canvas)\n        self._display.display_1Gray(buf)\n\n    def clear(self):\n        self._display.Clear(0)\n"
  }
]