diff --git a/README.md b/README.md index 095c7eb..14e415c 100644 --- a/README.md +++ b/README.md @@ -202,14 +202,14 @@ running = False def on_loaded(): - core.log("GPS plugin loaded for %s" % device) + logging.info("GPS plugin loaded for %s" % device) def on_ready(agent): global running if os.path.exists(device): - core.log("enabling GPS bettercap's module for %s" % device) + logging.info("enabling GPS bettercap's module for %s" % device) try: agent.run('gps off') except: @@ -220,7 +220,7 @@ def on_ready(agent): agent.run('gps on') running = True else: - core.log("no GPS detected") + logging.info("no GPS detected") def on_handshake(agent, filename, access_point, client_station): @@ -229,7 +229,7 @@ def on_handshake(agent, filename, access_point, client_station): gps = info['gps'] gps_filename = filename.replace('.pcap', '.gps.json') - core.log("saving GPS to %s (%s)" % (gps_filename, gps)) + logging.info("saving GPS to %s (%s)" % (gps_filename, gps)) with open(gps_filename, 'w+t') as fp: json.dump(gps, fp) ``` diff --git a/scripts/preview.py b/scripts/preview.py index 261448e..2b45933 100755 --- a/scripts/preview.py +++ b/scripts/preview.py @@ -6,6 +6,7 @@ import time import argparse from http.server import HTTPServer import shutil +import logging import yaml sys.path.insert(0, @@ -13,7 +14,6 @@ sys.path.insert(0, '../sdcard/rootfs/root/pwnagotchi/scripts/')) from pwnagotchi.ui.display import Display, VideoHandler -import core class CustomDisplay(Display): @@ -22,11 +22,11 @@ class CustomDisplay(Display): if self._video_address is not None: self._httpd = HTTPServer((self._video_address, self._video_port), CustomVideoHandler) - core.log("ui available at http://%s:%d/" % (self._video_address, + logging.info("ui available at http://%s:%d/" % (self._video_address, self._video_port)) self._httpd.serve_forever() else: - core.log("could not get ip of usb0, video server not starting") + logging.info("could not get ip of usb0, video server not starting") def _on_view_rendered(self, img): CustomVideoHandler.render(img) @@ -45,7 +45,7 @@ class CustomVideoHandler(VideoHandler): try: img.save("/tmp/pwnagotchi-{rand}.png".format(rand=id(CustomVideoHandler)), format='PNG') except BaseException: - core.log("could not write preview") + logging.exception("could not write preview") def do_GET(self): if self.path == '/': @@ -69,7 +69,7 @@ class CustomVideoHandler(VideoHandler): with open("/tmp/pwnagotchi-{rand}.png".format(rand=id(CustomVideoHandler)), 'rb') as fp: shutil.copyfileobj(fp, self.wfile) except BaseException: - core.log("could not open preview") + logging.exception("could not open preview") else: self.send_response(404) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/bettercap/client.py b/sdcard/rootfs/root/pwnagotchi/scripts/bettercap/client.py index 98c71c2..d55fc33 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/bettercap/client.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/bettercap/client.py @@ -1,8 +1,7 @@ +import logging import requests from requests.auth import HTTPBasicAuth -import core - class Client(object): def __init__(self, hostname='localhost', scheme='http', port=8081, username='user', password='pass'): @@ -19,11 +18,11 @@ class Client(object): return r.json() except Exception as e: if r.status_code == 200: - core.log("error while decoding json: error='%s' resp='%s'" % (e, r.text)) + logging.error("error while decoding json: error='%s' resp='%s'" % (e, r.text)) else: err = "error %d: %s" % (r.status_code, r.text.strip()) if verbose_errors: - core.log(err) + logging.info(err) raise Exception(err) return r.text diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/core/__init__.py b/sdcard/rootfs/root/pwnagotchi/scripts/core/__init__.py index 4321676..eb718d3 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/core/__init__.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/core/__init__.py @@ -1,24 +1,7 @@ -import sys import glob import os import time import subprocess -from threading import Lock -from datetime import datetime - -logfile = None -loglock = Lock() - - -def log(msg): - tstamp = str(datetime.now()) - line = "[%s] %s" % (tstamp, msg.rstrip()) - print(line) - sys.stdout.flush() - if logfile is not None: - with loglock: - with open(logfile, 'a+t') as fp: - fp.write("%s\n" % line) def secs_to_hhmmss(secs): diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/main.py b/sdcard/rootfs/root/pwnagotchi/scripts/main.py index 2327955..7caa81e 100755 --- a/sdcard/rootfs/root/pwnagotchi/scripts/main.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/main.py @@ -2,11 +2,10 @@ import os import argparse import time -import traceback +import logging import yaml -import core import pwnagotchi import pwnagotchi.utils as utils import pwnagotchi.version as version @@ -28,8 +27,12 @@ parser.add_argument('--manual', dest="do_manual", action="store_true", default=F parser.add_argument('--clear', dest="do_clear", action="store_true", default=False, help="Clear the ePaper display and exit.") +parser.add_argument('--debug', dest="debug", action="store_true", default=False, + help="Enable debug logs.") + args = parser.parse_args() config = utils.load_config(args) +utils.setup_logging(args, config) if args.do_clear: print("clearing the display ...") @@ -75,19 +78,18 @@ plugins.on('loaded') display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()}) agent = Agent(view=display, config=config) -core.log("%s@%s (v%s)" % (pwnagotchi.name(), agent._identity, version.version)) +logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent._identity, version.version)) # for key, value in config['personality'].items(): -# core.log(" %s: %s" % (key, value)) +# logging.info(" %s: %s" % (key, value)) for _, plugin in plugins.loaded.items(): - core.log("plugin '%s' v%s loaded from %s" % (plugin.__name__, plugin.__version__, plugin.__file__)) + logging.info("plugin '%s' v%s loaded from %s" % (plugin.__name__, plugin.__version__, plugin.__file__)) if args.do_manual: - core.log("entering manual mode ...") + logging.info("entering manual mode ...") log = SessionParser(config['main']['log']) - - core.log("the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % ( + logging.info("the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % ( log.duration_human, log.epochs, log.train_epochs, @@ -98,17 +100,18 @@ if args.do_manual: while True: display.on_manual_mode(log) time.sleep(1) + if config['twitter']['enabled'] and log.is_new() and Agent.is_connected() and log.handshakes > 0: import tweepy - core.log("detected a new session and internet connectivity!") + logging.info("detected a new session and internet connectivity!") picture = '/dev/shm/pwnagotchi.png' - display.update() + display.update(force=True) display.image().save(picture, 'png') display.set('status', 'Tweeting...') - display.update() + display.update(force=True) try: auth = tweepy.OAuthHandler(config['twitter']['consumer_key'], config['twitter']['consumer_secret']) @@ -119,14 +122,12 @@ if args.do_manual: api.update_with_media(filename=picture, status=tweet) log.save_session_id() - core.log("tweeted: %s" % tweet) + logging.info("tweeted: %s" % tweet) except Exception as e: - core.log("error: %s" % e) + logging.exception("error while tweeting") quit() -core.logfile = config['main']['log'] - agent.start_ai() agent.setup_events() agent.set_starting() @@ -151,7 +152,7 @@ while True: agent.set_channel(ch) if not agent.is_stale() and agent.any_activity(): - core.log("%d access points on channel %d" % (len(aps), ch)) + logging.info("%d access points on channel %d" % (len(aps), ch)) # for each ap on this channel for ap in aps: @@ -170,5 +171,4 @@ while True: # affect ours ... neat ^_^ agent.next_epoch() except Exception as e: - core.log("main loop exception: %s" % e) - core.log("%s" % traceback.format_exc()) + logging.exception("main loop exception") diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/agent.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/agent.py index 9dcb7fb..578e380 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/agent.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/agent.py @@ -4,6 +4,7 @@ import os import re import socket from datetime import datetime +import logging import _thread import core @@ -82,7 +83,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): plugins.on('rebooting', self) def setup_events(self): - core.log("connecting to %s ..." % self.url) + logging.info("connecting to %s ..." % self.url) for tag in self._config['bettercap']['silence']: try: @@ -109,30 +110,30 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): s = self.session() for iface in s['interfaces']: if iface['name'] == mon_iface: - core.log("found monitor interface: %s" % iface['name']) + logging.info("found monitor interface: %s" % iface['name']) has_mon = True break if has_mon is False: if mon_start_cmd is not None and mon_start_cmd != '': - core.log("starting monitor interface ...") + logging.info("starting monitor interface ...") self.run('!%s' % mon_start_cmd) else: - core.log("waiting for monitor interface %s ..." % mon_iface) + logging.info("waiting for monitor interface %s ..." % mon_iface) time.sleep(1) - core.log("supported channels: %s" % self._supported_channels) - core.log("handshakes will be collected inside %s" % self._config['bettercap']['handshakes']) + logging.info("supported channels: %s" % self._supported_channels) + logging.info("handshakes will be collected inside %s" % self._config['bettercap']['handshakes']) self._reset_wifi_settings() wifi_running = self.is_module_running('wifi') if wifi_running and restart: - core.log("restarting wifi module ...") + logging.debug("restarting wifi module ...") self.restart('wifi.recon') self.run('wifi.clear') elif not wifi_running: - core.log("starting wifi module ...") + logging.debug("starting wifi module ...") self.start('wifi.recon') self.start_advertising() @@ -150,13 +151,13 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): for ch in self._epoch.non_overlapping_channels: if ch not in busy_channels: self._epoch.non_overlapping_channels[ch] += 1 - core.log("channel %d is free from %d epochs" % (ch, self._epoch.non_overlapping_channels[ch])) + logging.info("channel %d is free from %d epochs" % (ch, self._epoch.non_overlapping_channels[ch])) elif self._epoch.non_overlapping_channels[ch] > 0: self._epoch.non_overlapping_channels[ch] -= 1 # report any channel that has been free for at least 3 epochs for ch, num_epochs_free in self._epoch.non_overlapping_channels.items(): if num_epochs_free >= 3: - core.log("channel %d has been free for %d epochs" % (ch, num_epochs_free)) + logging.info("channel %d has been free for %d epochs" % (ch, num_epochs_free)) self.set_free_channel(ch) def recon(self): @@ -172,14 +173,14 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): if not channels: self._current_channel = 0 - # core.log("RECON %ds" % recon_time) + logging.debug("RECON %ds" % recon_time) self.run('wifi.recon.channel clear') else: - # core.log("RECON %ds ON CHANNELS %s" % (recon_time, ','.join(map(str, channels)))) + logging.debug("RECON %ds ON CHANNELS %s" % (recon_time, ','.join(map(str, channels)))) try: self.run('wifi.recon.channel %s' % ','.join(map(str, channels))) except Exception as e: - core.log("error: %s" % e) + logging.exception("error") self.wait_for(recon_time, sleeping=False) @@ -204,7 +205,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): if self._filter_included(ap): aps.append(ap) except Exception as e: - core.log("error: %s" % e) + logging.exception("error") aps.sort(key=lambda ap: ap['channel']) return self.set_access_points(aps) @@ -289,7 +290,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): self._view.set_closest_peer(peer) def _save_recovery_data(self): - core.log("writing recovery data to %s ..." % RECOVERY_DATA_FILE) + logging.warning("writing recovery data to %s ..." % RECOVERY_DATA_FILE) with open(RECOVERY_DATA_FILE, 'w') as fp: data = { 'started_at': self._started_at, @@ -304,7 +305,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): try: with open(RECOVERY_DATA_FILE, 'rt') as fp: data = json.load(fp) - core.log("found recovery data: %s" % data) + logging.info("found recovery data: %s" % data) self._started_at = data['started_at'] self._epoch.epoch = data['epoch'] self._handshakes = data['handshakes'] @@ -312,7 +313,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): self._last_pwnd = data['last_pwnd'] if delete: - core.log("deleting %s" % RECOVERY_DATA_FILE) + logging.info("deleting %s" % RECOVERY_DATA_FILE) os.unlink(RECOVERY_DATA_FILE) except: if not no_exceptions: @@ -323,7 +324,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): self.run('events.clear') - core.log("event polling started ...") + logging.debug("event polling started ...") while True: time.sleep(1) @@ -349,21 +350,21 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): new_shakes += 1 ap_and_station = self._find_ap_sta_in(sta_mac, ap_mac, s) if ap_and_station is None: - core.log("!!! captured new handshake: %s !!!" % key) + logging.warning("!!! captured new handshake: %s !!!" % key) self._last_pwnd = ap_mac plugins.on('handshake', self, filename, ap_mac, sta_mac) else: (ap, sta) = ap_and_station self._last_pwnd = ap['hostname'] if ap['hostname'] != '' and ap[ 'hostname'] != '' else ap_mac - core.log("!!! captured new handshake on channel %d: %s (%s) -> %s [%s (%s)] !!!" % ( \ + logging.warning("!!! captured new handshake on channel %d: %s (%s) -> %s [%s (%s)] !!!" % ( \ ap['channel'], sta['mac'], sta['vendor'], ap['hostname'], ap['mac'], ap['vendor'])) plugins.on('handshake', self, filename, ap, sta) except Exception as e: - core.log("error: %s" % e) + logging.exception("error") finally: self._update_handshakes(new_shakes) @@ -404,7 +405,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): return self._history[who] < self._config['personality']['max_interactions'] def _on_miss(self, who): - core.log("it looks like %s is not in range anymore :/" % who) + logging.info("it looks like %s is not in range anymore :/" % who) self._epoch.track(miss=True) self._view.on_miss(who) @@ -416,18 +417,18 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): if 'is an unknown BSSID' in error: self._on_miss(who) else: - core.log("error: %s" % e) + logging.error("%s" % e) def associate(self, ap, throttle=0): if self.is_stale(): - core.log("recon is stale, skipping assoc(%s)" % ap['mac']) + logging.debug("recon is stale, skipping assoc(%s)" % ap['mac']) return if self._config['personality']['associate'] and self._should_interact(ap['mac']): self._view.on_assoc(ap) try: - core.log("sending association frame to %s (%s %s) on channel %d [%d clients]..." % ( \ + logging.info("sending association frame to %s (%s %s) on channel %d [%d clients]..." % ( \ ap['hostname'], ap['mac'], ap['vendor'], ap['channel'], len(ap['clients']))) self.run('wifi.assoc %s' % ap['mac']) self._epoch.track(assoc=True) @@ -441,14 +442,14 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): def deauth(self, ap, sta, throttle=0): if self.is_stale(): - core.log("recon is stale, skipping deauth(%s)" % sta['mac']) + logging.debug("recon is stale, skipping deauth(%s)" % sta['mac']) return if self._config['personality']['deauth'] and self._should_interact(sta['mac']): self._view.on_deauth(sta) try: - core.log("deauthing %s (%s) from %s (%s %s) on channel %d ..." % ( + logging.info("deauthing %s (%s) from %s (%s %s) on channel %d ..." % ( sta['mac'], sta['vendor'], ap['hostname'], ap['mac'], ap['vendor'], ap['channel'])) self.run('wifi.deauth %s' % sta['mac']) self._epoch.track(deauth=True) @@ -462,7 +463,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): def set_channel(self, channel, verbose=True): if self.is_stale(): - core.log("recon is stale, skipping set_channel(%d)" % channel) + logging.debug("recon is stale, skipping set_channel(%d)" % channel) return # if in the previous loop no client stations has been deauthenticated @@ -478,10 +479,12 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): if channel != self._current_channel: if self._current_channel != 0 and wait > 0: if verbose: - core.log("waiting for %ds on channel %d ..." % (wait, self._current_channel)) + logging.info("waiting for %ds on channel %d ..." % (wait, self._current_channel)) + else: + logging.debug("waiting for %ds on channel %d ..." % (wait, self._current_channel)) self.wait_for(wait) if verbose and self._epoch.any_activity: - core.log("CHANNEL %d" % channel) + logging.info("CHANNEL %d" % channel) try: self.run('wifi.recon.channel %d' % channel) self._current_channel = channel @@ -491,7 +494,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): plugins.on('channel_hop', self, channel) except Exception as e: - core.log("error: %s" % e) + logging.error("error: %s" % e) def is_stale(self): return self._epoch.num_missed > self._config['personality']['max_misses_for_recon'] @@ -502,7 +505,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): def _reboot(self): self.set_rebooting() self._save_recovery_data() - core.log("rebooting the system ...") + logging.warning("rebooting the system ...") os.system("/usr/bin/sync") os.system("/usr/sbin/shutdown -r now") @@ -514,24 +517,24 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): # after X misses during an epoch, set the status to lonely if was_stale: - core.log("agent missed %d interactions -> lonely" % did_miss) + logging.warning("agent missed %d interactions -> lonely" % did_miss) self.set_lonely() # after X times being bored, the status is set to sad elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']: - core.log("%d epochs with no activity -> sad" % self._epoch.inactive_for) + logging.warning("%d epochs with no activity -> sad" % self._epoch.inactive_for) self.set_sad() # after X times being inactive, the status is set to bored elif self._epoch.inactive_for >= self._config['personality']['bored_num_epochs']: - core.log("%d epochs with no activity -> bored" % self._epoch.inactive_for) + logging.warning("%d epochs with no activity -> bored" % self._epoch.inactive_for) self.set_bored() # after X times being active, the status is set to happy / excited elif self._epoch.active_for >= self._config['personality']['excited_num_epochs']: - core.log("%d epochs with activity -> excited" % self._epoch.active_for) + logging.warning("%d epochs with activity -> excited" % self._epoch.active_for) self.set_excited() plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data()) if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']: - core.log("%d epochs without visible access points -> rebooting ..." % self._epoch.blind_for) + logging.critical("%d epochs without visible access points -> rebooting ..." % self._epoch.blind_for) self._reboot() self._epoch.blind_for = 0 diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/__init__.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/__init__.py index 42b857f..359a38c 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/__init__.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/__init__.py @@ -7,16 +7,16 @@ import warnings # https://stackoverflow.com/questions/15777951/how-to-suppress-pandas-future-warning warnings.simplefilter(action='ignore', category=FutureWarning) -import core +import logging def load(config, agent, epoch, from_disk=True): config = config['ai'] if not config['enabled']: - core.log("ai disabled") + logging.info("ai disabled") return False - core.log("[ai] bootstrapping dependencies ...") + logging.info("[ai] bootstrapping dependencies ...") from stable_baselines import A2C from stable_baselines.common.policies import MlpLstmPolicy @@ -27,16 +27,16 @@ def load(config, agent, epoch, from_disk=True): env = wrappers.Environment(agent, epoch) env = DummyVecEnv([lambda: env]) - core.log("[ai] bootstrapping model ...") + logging.info("[ai] bootstrapping model ...") a2c = A2C(MlpLstmPolicy, env, **config['params']) if from_disk and os.path.exists(config['path']): - core.log("[ai] loading %s ..." % config['path']) + logging.info("[ai] loading %s ..." % config['path']) a2c.load(config['path'], env) else: - core.log("[ai] model created:") + logging.info("[ai] model created:") for key, value in config['params'].items(): - core.log(" %s: %s" % (key, value)) + logging.info(" %s: %s" % (key, value)) return a2c diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/epoch.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/epoch.py index 8508eb9..0ef7869 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/epoch.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/epoch.py @@ -1,5 +1,6 @@ import time import threading +import logging import core import pwnagotchi @@ -87,13 +88,13 @@ class Epoch(object): aps_per_chan[ch_idx] += 1.0 sta_per_chan[ch_idx] += len(ap['clients']) except IndexError as e: - core.log("got data on channel %d, we can store %d channels" % (ap['channel'], wifi.NumChannels)) + logging.error("got data on channel %d, we can store %d channels" % (ap['channel'], wifi.NumChannels)) for peer in peers: try: peers_per_chan[peer.last_channel - 1] += 1.0 except IndexError as e: - core.log( + logging.error( "got peer data on channel %d, we can store %d channels" % (peer.last_channel, wifi.NumChannels)) # normalize @@ -172,7 +173,7 @@ class Epoch(object): self._epoch_data['reward'] = self._reward(self.epoch + 1, self._epoch_data) self._epoch_data_ready.set() - core.log("[epoch %d] duration=%s slept_for=%s blind=%d inactive=%d active=%d hops=%d missed=%d " + logging.info("[epoch %d] duration=%s slept_for=%s blind=%d inactive=%d active=%d hops=%d missed=%d " "deauths=%d assocs=%d handshakes=%d cpu=%d%% mem=%d%% temperature=%dC reward=%s" % ( self.epoch, core.secs_to_hhmmss(self.epoch_duration), diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/gym.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/gym.py index 0e713a1..22e6441 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/gym.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/gym.py @@ -1,8 +1,8 @@ +import logging import gym from gym import spaces import numpy as np -import core import pwnagotchi.ai.featurizer as featurizer import pwnagotchi.ai.reward as reward from pwnagotchi.ai.parameter import Parameter @@ -83,7 +83,7 @@ class Environment(gym.Env): return params def _next_epoch(self): - # core.log("[ai] waiting for epoch to finish ...") + logging.debug("[ai] waiting for epoch to finish ...") return self._epoch.wait_for_epoch_data() def _apply_policy(self, policy): @@ -110,7 +110,7 @@ class Environment(gym.Env): return self.last['state_v'], self.last['reward'], not self._agent.is_training(), {} def reset(self): - # core.log("[ai] resetting environment ...") + # logging.info("[ai] resetting environment ...") self._epoch_num = 0 state = self._next_epoch() self.last['state'] = state @@ -120,7 +120,7 @@ class Environment(gym.Env): def _render_histogram(self, hist): for ch in range(featurizer.histogram_size): if hist[ch]: - core.log(" CH %d: %s" % (ch + 1, hist[ch])) + logging.info(" CH %d: %s" % (ch + 1, hist[ch])) def render(self, mode='human', close=False, force=False): # when using a vectorialized environment, render gets called twice @@ -133,18 +133,13 @@ class Environment(gym.Env): self._last_render = self._epoch_num - core.log("[ai] --- training epoch %d/%d ---" % (self._epoch_num, self._agent.training_epochs())) - core.log("[ai] REWARD: %f" % self.last['reward']) + logging.info("[ai] --- training epoch %d/%d ---" % (self._epoch_num, self._agent.training_epochs())) + logging.info("[ai] REWARD: %f" % self.last['reward']) - # core.log("[ai] policy: %s" % ', '.join("%s:%s" % (name, value) for name, value in self.last['params'].items())) + logging.debug("[ai] policy: %s" % ', '.join("%s:%s" % (name, value) for name, value in self.last['params'].items())) - core.log("[ai] observation:") + logging.info("[ai] observation:") for name, value in self.last['state'].items(): if 'histogram' in name: - core.log(" %s" % name.replace('_histogram', '')) + logging.info(" %s" % name.replace('_histogram', '')) self._render_histogram(value) - - # core.log("[ai] outcome:") - # for name, value in self.last['state'].items(): - # if 'histogram' not in name: - # core.log(" %s: %s" % (name, value)) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/train.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/train.py index 34140dd..96e6789 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/train.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/train.py @@ -4,8 +4,7 @@ import time import random import os import json - -import core +import logging import pwnagotchi.plugins as plugins import pwnagotchi.ai as ai @@ -56,7 +55,7 @@ class Stats(object): def load(self): with self._lock: if os.path.exists(self.path) and os.path.getsize(self.path) > 0: - core.log("[ai] loading %s" % self.path) + logging.info("[ai] loading %s" % self.path) with open(self.path, 'rt') as fp: obj = json.load(fp) @@ -66,7 +65,7 @@ class Stats(object): def save(self): with self._lock: - core.log("[ai] saving %s" % self.path) + logging.info("[ai] saving %s" % self.path) data = json.dumps({ 'born_at': self.born_at, @@ -114,7 +113,7 @@ class AsyncTrainer(object): _thread.start_new_thread(self._ai_worker, ()) def _save_ai(self): - core.log("[ai] saving model to %s ..." % self._nn_path) + logging.info("[ai] saving model to %s ..." % self._nn_path) temp = "%s.tmp" % self._nn_path self._model.save(temp) os.replace(temp, self._nn_path) @@ -133,15 +132,15 @@ class AsyncTrainer(object): def on_ai_policy(self, new_params): plugins.on('ai_policy', self, new_params) - core.log("[ai] setting new policy:") + logging.info("[ai] setting new policy:") for name, value in new_params.items(): if name in self._config['personality']: curr_value = self._config['personality'][name] if curr_value != value: - core.log("[ai] ! %s: %s -> %s" % (name, curr_value, value)) + logging.info("[ai] ! %s: %s -> %s" % (name, curr_value, value)) self._config['personality'][name] = value else: - core.log("[ai] param %s not in personality configuration!" % name) + logging.error("[ai] param %s not in personality configuration!" % name) self.run('set wifi.ap.ttl %d' % self._config['personality']['ap_ttl']) self.run('set wifi.sta.ttl %d' % self._config['personality']['sta_ttl']) @@ -152,12 +151,12 @@ class AsyncTrainer(object): plugins.on('ai_ready', self) def on_ai_best_reward(self, r): - core.log("[ai] best reward so far: %s" % r) + logging.info("[ai] best reward so far: %s" % r) self._view.on_motivated(r) plugins.on('ai_best_reward', self, r) def on_ai_worst_reward(self, r): - core.log("[ai] worst reward so far: %s" % r) + logging.info("[ai] worst reward so far: %s" % r) self._view.on_demotivated(r) plugins.on('ai_worst_reward', self, r) @@ -174,12 +173,12 @@ class AsyncTrainer(object): self._model.env.render() # enter in training mode? if random.random() > self._config['ai']['laziness']: - core.log("[ai] learning for %d epochs ..." % epochs_per_episode) + logging.info("[ai] learning for %d epochs ..." % epochs_per_episode) try: self.set_training(True, epochs_per_episode) self._model.learn(total_timesteps=epochs_per_episode, callback=self.on_ai_training_step) except Exception as e: - core.log("[ai] error while training: %s" % e) + logging.exception("[ai] error while training") finally: self.set_training(False) obs = self._model.env.reset() diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/log.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/log.py index edf66ed..8648521 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/log.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/log.py @@ -37,6 +37,8 @@ class SessionParser(object): self.last_saved_session_id = self.last_session_id def _parse_datetime(self, dt): + dt = dt.split('.')[0] + dt = dt.split(',')[0] dt = datetime.strptime(dt.split('.')[0], '%Y-%m-%d %H:%M:%S') return time.mktime(dt.timetuple()) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/advertise.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/advertise.py index 9d5c788..90374c6 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/advertise.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/advertise.py @@ -2,9 +2,9 @@ import time import json import _thread import threading +import logging from scapy.all import Dot11, Dot11FCS, Dot11Elt, RadioTap, sendp, sniff -import core import pwnagotchi.ui.faces as faces import pwnagotchi.mesh.wifi as wifi @@ -54,7 +54,6 @@ class Advertiser(object): self._lost_peer_cb = lost_cb def on_face_change(self, old, new): - # core.log("face change: %s -> %s" % (old, new)) self.update({'face': new}) def start(self): @@ -84,12 +83,12 @@ class Advertiser(object): self._stopped.set() def _sender(self): - core.log("started advertiser thread (period:%s sid:%s) ..." % (str(self._period), self._me.session_id)) + logging.info("started advertiser thread (period:%s sid:%s) ..." % (str(self._period), self._me.session_id)) while self._running: try: sendp(self._frame, iface=self._iface, verbose=False, count=5, inter=self._period) except Exception as e: - core.log("error: %s" % e) + logging.exception("error") time.sleep(self._period) def _on_advertisement(self, src_session_id, channel, rssi, adv): @@ -97,7 +96,7 @@ class Advertiser(object): with self._peers_lock: if ident not in self._peers: peer = Peer(src_session_id, channel, rssi, adv) - core.log("detected unit %s (v%s) on channel %d (%s dBm) [sid:%s pwnd_tot:%d uptime:%d]" % ( \ + logging.info("detected unit %s (v%s) on channel %d (%s dBm) [sid:%s pwnd_tot:%d uptime:%d]" % ( \ peer.full_name(), peer.version(), channel, @@ -158,10 +157,10 @@ class Advertiser(object): raise Exception("unknown frame id %d" % dot11elt.ID) except Exception as e: - core.log("error decoding packet from %s: %s" % (dot11.addr3, e)) + logging.exception("error decoding packet from %s" % dot11.addr3) def _listener(self): - # core.log("started advertisements listener ...") + # logging.info("started advertisements listener ...") expr = "type mgt subtype beacon and ether src %s" % wifi.SignatureAddress sniff(iface=self._iface, filter=expr, prn=self._on_packet, store=0, stop_filter=lambda x: self._stopped.isSet()) @@ -173,7 +172,7 @@ class Advertiser(object): for ident, peer in self._peers.items(): inactive_for = peer.inactive_for() if inactive_for >= Advertiser.MAX_STALE_TIME: - core.log("peer %s lost (inactive for %ds)" % (peer.full_name(), inactive_for)) + logging.info("peer %s lost (inactive for %ds)" % (peer.full_name(), inactive_for)) self._lost_peer_cb(peer) stale.append(ident) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/peer.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/peer.py index 6f9f1f9..0d3b061 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/peer.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/peer.py @@ -1,8 +1,8 @@ import time +import logging import pwnagotchi.mesh.wifi as wifi import pwnagotchi.ui.faces as faces -import core class Peer(object): @@ -18,10 +18,10 @@ class Peer(object): def update(self, sid, channel, rssi, adv): if self.name() != adv['name']: - core.log("peer %s changed name: %s -> %s" % (self.full_name(), self.name(), adv['name'])) + logging.info("peer %s changed name: %s -> %s" % (self.full_name(), self.name(), adv['name'])) if self.session_id != sid: - core.log("peer %s changed session id: %s -> %s" % (self.full_name(), self.session_id, sid)) + logging.info("peer %s changed session id: %s -> %s" % (self.full_name(), self.session_id, sid)) self.presence[channel - 1] += 1 self.adv = adv diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/utils.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/utils.py index 92e4939..be7bb0f 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/utils.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/utils.py @@ -1,6 +1,6 @@ import _thread +import logging -import core import pwnagotchi import pwnagotchi.version as version import pwnagotchi.plugins as plugins @@ -35,7 +35,7 @@ class AsyncAdvertiser(object): self._advertiser.start() self._view.on_state_change('face', self._advertiser.on_face_change) else: - core.log("advertising is disabled") + logging.warning("advertising is disabled") def _on_new_unit(self, peer): self._view.on_new_peer(peer) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/plugins/default/example.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/plugins/default/example.py index 4a63275..e527f0f 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/plugins/default/example.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/plugins/default/example.py @@ -5,15 +5,16 @@ __license__ = 'GPL3' __description__ = 'An example plugin for pwnagotchi that implements all the available callbacks.' __enabled__ = False # IMPORTANT: set this to True to enable your plugin. +import logging + from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.view import BLACK import pwnagotchi.ui.fonts as fonts -import core # called when the plugin is loaded def on_loaded(): - core.log("WARNING: plugin %s should be disabled!" % __name__) + logging.warning("WARNING: plugin %s should be disabled!" % __name__) # called to setup the ui elements @@ -39,7 +40,7 @@ def on_display_setup(display): # called when everything is ready and the main loop is about to start def on_ready(agent): - core.log("unit is ready") + logging.info("unit is ready") # you can run custom bettercap commands if you want # agent.run('ble.recon on') # or set a custom state diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/plugins/default/gps.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/plugins/default/gps.py index c3c53a9..7c57b24 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/plugins/default/gps.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/plugins/default/gps.py @@ -5,7 +5,7 @@ __license__ = 'GPL3' __description__ = 'Save GPS coordinates whenever an handshake is captured.' __enabled__ = True # set to false if you just don't use GPS -import core +import logging import json import os @@ -15,14 +15,14 @@ running = False def on_loaded(): - core.log("GPS plugin loaded for %s" % device) + logging.info("GPS plugin loaded for %s" % device) def on_ready(agent): global running if os.path.exists(device): - core.log("enabling GPS bettercap's module for %s" % device) + logging.info("enabling GPS bettercap's module for %s" % device) try: agent.run('gps off') except: @@ -33,7 +33,7 @@ def on_ready(agent): agent.run('gps on') running = True else: - core.log("no GPS detected") + logging.warning("no GPS detected") def on_handshake(agent, filename, access_point, client_station): @@ -42,6 +42,6 @@ def on_handshake(agent, filename, access_point, client_station): gps = info['gps'] gps_filename = filename.replace('.pcap', '.gps.json') - core.log("saving GPS to %s (%s)" % (gps_filename, gps)) + logging.info("saving GPS to %s (%s)" % (gps_filename, gps)) with open(gps_filename, 'w+t') as fp: json.dump(gps, fp) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py index 657aefb..c8b301a 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py @@ -2,7 +2,7 @@ import _thread from threading import Lock import shutil -import core +import logging import os import pwnagotchi, pwnagotchi.plugins as plugins @@ -78,13 +78,12 @@ class Display(View): self._render_cb = None self._display = None self._httpd = None - self.canvas = None if self._enabled: self._init_display() else: self.on_render(self._on_view_rendered) - core.log("display module is disabled") + logging.warning("display module is disabled") if self._video_enabled: _thread.start_new_thread(self._http_serve, ()) @@ -92,10 +91,10 @@ class Display(View): def _http_serve(self): if self._video_address is not None: self._httpd = HTTPServer((self._video_address, self._video_port), VideoHandler) - core.log("ui available at http://%s:%d/" % (self._video_address, self._video_port)) + logging.info("ui available at http://%s:%d/" % (self._video_address, self._video_port)) self._httpd.serve_forever() else: - core.log("could not get ip of usb0, video server not starting") + logging.info("could not get ip of usb0, video server not starting") def _is_inky(self): return self._display_type in ('inkyphat', 'inky') @@ -111,14 +110,14 @@ class Display(View): def _init_display(self): if self._is_inky(): - core.log("initializing inky display") + logging.info("initializing inky display") from inky import InkyPHAT self._display = InkyPHAT(self._display_color) self._display.set_border(InkyPHAT.BLACK) self._render_cb = self._inky_render elif self._is_papirus(): - core.log("initializing papirus display") + logging.info("initializing papirus display") from pwnagotchi.ui.papirus.epd import EPD os.environ['EPD_SIZE'] = '2.0' self._display = EPD() @@ -126,7 +125,7 @@ class Display(View): self._render_cb = self._papirus_render elif self._is_waveshare1(): - core.log("initializing waveshare v1 display") + logging.info("initializing waveshare v1 display") from pwnagotchi.ui.waveshare.v1.epd2in13 import EPD self._display = EPD() self._display.init(self._display.lut_full_update) @@ -135,7 +134,7 @@ class Display(View): self._render_cb = self._waveshare_render elif self._is_waveshare2(): - core.log("initializing waveshare v2 display") + logging.info("initializing waveshare v2 display") from pwnagotchi.ui.waveshare.v2.waveshare import EPD self._display = EPD() self._display.init(self._display.FULL_UPDATE) @@ -144,63 +143,61 @@ class Display(View): self._render_cb = self._waveshare_render else: - core.log("unknown display type %s" % self._display_type) + logging.critical("unknown display type %s" % self._display_type) plugins.on('display_setup', self._display) self.on_render(self._on_view_rendered) - def image(self): - img = None - if self.canvas is not None: - img = self.canvas if self._rotation == 0 else self.canvas.rotate(-self._rotation) - return img - def _inky_render(self): if self._display_color != 'mono': display_colors = 3 else: display_colors = 2 - imgbuf = self.canvas.convert('RGB').convert('P', palette=1, colors=display_colors) - + img_buffer = self._canvas.convert('RGB').convert('P', palette=1, colors=display_colors) if self._display_color == 'red': - imgbuf.putpalette([ + img_buffer.putpalette([ 255, 255, 255, # index 0 is white 0, 0, 0, # index 1 is black 255, 0, 0 # index 2 is red ]) elif self._display_color == 'yellow': - imgbuf.putpalette([ + img_buffer.putpalette([ 255, 255, 255, # index 0 is white 0, 0, 0, # index 1 is black 255, 255, 0 # index 2 is yellow ]) else: - imgbuf.putpalette([ + img_buffer.putpalette([ 255, 255, 255, # index 0 is white 0, 0, 0 # index 1 is black ]) - self._display.set_image(imgbuf) + self._display.set_image(img_buffer) self._display.show() def _papirus_render(self): - self._display.display(self.canvas) + self._display.display(self._canvas) self._display.partial_update() def _waveshare_render(self): - buf = self._display.getbuffer(self.canvas) + buf = self._display.getbuffer(self._canvas) if self._is_waveshare1(): self._display.display(buf) elif self._is_waveshare2(): self._display.displayPartial(buf) + def image(self): + img = None + if self._canvas is not None: + img = self._canvas if self._rotation == 0 else self._canvas.rotate(-self._rotation) + return img + def _on_view_rendered(self, img): - # core.log("display::_on_view_rendered") VideoHandler.render(img) if self._enabled: - self.canvas = (img if self._rotation == 0 else img.rotate(self._rotation)) + self._canvas = (img if self._rotation == 0 else img.rotate(self._rotation)) if self._render_cb is not None: self._render_cb() diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/view.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/view.py index 0c5eb8a..f86b88f 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/view.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/view.py @@ -1,6 +1,7 @@ import _thread from threading import Lock import time +import logging from PIL import Image, ImageDraw import core @@ -114,7 +115,7 @@ class View(object): _thread.start_new_thread(self._refresh_handler, ()) self._ignore_changes = () else: - core.log("ui.fps is 0, the display will only update for major changes") + logging.warning("ui.fps is 0, the display will only update for major changes") self._ignore_changes = ('uptime', 'name') def add_element(self, key, elem): @@ -135,7 +136,7 @@ class View(object): def _refresh_handler(self): delay = 1.0 / self._config['ui']['fps'] - # core.log("view refresh handler started with period of %.2fs" % delay) + # logging.info("view refresh handler started with period of %.2fs" % delay) while True: name = self._state.get('name') @@ -313,10 +314,10 @@ class View(object): self.set('status', self._voice.custom(text)) self.update() - def update(self): + def update(self, force=False): with self._lock: changes = self._state.changes(ignore=self._ignore_changes) - if len(changes): + if force or len(changes): self._canvas = Image.new('1', (self._width, self._height), WHITE) drawer = ImageDraw.Draw(self._canvas) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/utils.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/utils.py index 358b2fe..8761a59 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/utils.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/utils.py @@ -1,5 +1,7 @@ import yaml import os +import logging + # https://stackoverflow.com/questions/823196/yaml-merge-in-python def merge_config(user, default): @@ -21,4 +23,20 @@ def load_config(args): user_config = yaml.safe_load(fp) config = merge_config(user_config, config) - return config \ No newline at end of file + return config + + +def setup_logging(args, config): + formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] %(message)s") + root = logging.getLogger() + + root.setLevel(logging.DEBUG if args.debug else logging.INFO) + + if config['main']['log']: + file_handler = logging.FileHandler(config['main']['log']) + file_handler.setFormatter(formatter) + root.addHandler(file_handler) + + console_handler = logging.StreamHandler() + console_handler.setFormatter(formatter) + root.addHandler(console_handler)