Files
pwnagotchi/pwnagotchi/ui/view.py

390 lines
13 KiB
Python
Raw Normal View History

2019-09-19 15:15:46 +02:00
import _thread
import logging
import random
import time
from threading import Lock
2019-10-12 17:47:51 +02:00
from PIL import ImageDraw
2019-09-19 15:15:46 +02:00
import pwnagotchi
import pwnagotchi.plugins as plugins
2019-09-19 15:15:46 +02:00
import pwnagotchi.ui.faces as faces
import pwnagotchi.ui.fonts as fonts
import pwnagotchi.ui.web as web
import pwnagotchi.utils as utils
2019-09-19 15:15:46 +02:00
from pwnagotchi.ui.components import *
from pwnagotchi.ui.state import State
from pwnagotchi.voice import Voice
2019-09-19 15:15:46 +02:00
WHITE = 0xff
BLACK = 0x00
ROOT = None
2019-09-28 20:17:45 +02:00
2019-09-19 15:15:46 +02:00
class View(object):
def __init__(self, config, impl, state=None):
global ROOT
# setup faces from the configuration in case the user customized them
faces.load_from_config(config['ui']['faces'])
self._agent = None
2019-09-19 15:15:46 +02:00
self._render_cbs = []
self._config = config
self._canvas = None
self._frozen = False
2019-09-19 15:15:46 +02:00
self._lock = Lock()
2019-09-29 14:17:02 +02:00
self._voice = Voice(lang=config['main']['lang'])
self._implementation = impl
self._layout = impl.layout()
self._width = self._layout['width']
self._height = self._layout['height']
2019-09-19 15:15:46 +02:00
self._state = State(state={
'channel': LabeledValue(color=BLACK, label='CH', value='00', position=self._layout['channel'],
label_font=fonts.Bold,
2019-09-19 15:15:46 +02:00
text_font=fonts.Medium),
'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=self._layout['aps'],
label_font=fonts.Bold,
2019-09-19 15:15:46 +02:00
text_font=fonts.Medium),
'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=self._layout['uptime'],
2019-09-28 20:17:45 +02:00
label_font=fonts.Bold,
2019-09-19 15:15:46 +02:00
text_font=fonts.Medium),
'line1': Line(self._layout['line1'], color=BLACK),
'line2': Line(self._layout['line2'], color=BLACK),
2019-09-19 15:15:46 +02:00
'face': Text(value=faces.SLEEP, position=self._layout['face'], color=BLACK, font=fonts.Huge),
2019-09-19 15:15:46 +02:00
'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=BLACK),
'friend_name': Text(value=None, position=self._layout['friend_name'], font=fonts.BoldSmall,
2019-10-04 00:26:00 +02:00
color=BLACK),
2019-09-19 15:15:46 +02:00
'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=BLACK, font=fonts.Bold),
'status': Text(value=self._voice.default(),
position=self._layout['status']['pos'],
color=BLACK,
font=self._layout['status']['font'],
wrap=True,
# the current maximum number of characters per line, assuming each character is 6 pixels wide
max_length=self._layout['status']['max']),
2019-09-19 15:15:46 +02:00
2019-09-28 20:17:45 +02:00
'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK,
position=self._layout['shakes'], label_font=fonts.Bold,
2019-09-19 15:15:46 +02:00
text_font=fonts.Medium),
'mode': Text(value='AUTO', position=self._layout['mode'],
2019-09-28 20:17:45 +02:00
font=fonts.Bold, color=BLACK),
2019-09-19 15:15:46 +02:00
})
if state:
for key, value in state.items():
self._state.set(key, value)
2019-09-19 15:15:46 +02:00
plugins.on('ui_setup', self)
if config['ui']['fps'] > 0.0:
_thread.start_new_thread(self._refresh_handler, ())
self._ignore_changes = ()
else:
logging.warning("ui.fps is 0, the display will only update for major changes")
self._ignore_changes = ('uptime', 'name')
2019-09-19 15:15:46 +02:00
ROOT = self
def set_agent(self, agent):
self._agent = agent
2019-10-11 23:29:34 +02:00
def has_element(self, key):
self._state.has_element(key)
def add_element(self, key, elem):
self._state.add_element(key, elem)
2019-10-11 23:29:34 +02:00
def remove_element(self, key):
self._state.remove_element(key)
def width(self):
return self._width
def height(self):
return self._height
2019-09-19 15:15:46 +02:00
def on_state_change(self, key, cb):
self._state.add_listener(key, cb)
def on_render(self, cb):
if cb not in self._render_cbs:
self._render_cbs.append(cb)
def _refresh_handler(self):
delay = 1.0 / self._config['ui']['fps']
while True:
try:
name = self._state.get('name')
self.set('name', name.rstrip('').strip() if '' in name else (name + ''))
self.update()
except Exception as e:
logging.warning("non fatal error while updating view: %s" % e)
2019-09-19 15:15:46 +02:00
time.sleep(delay)
def set(self, key, value):
self._state.set(key, value)
def get(self, key):
return self._state.get(key)
2019-09-19 15:15:46 +02:00
def on_starting(self):
2020-01-16 18:52:40 +01:00
self.set('status', self._voice.on_starting() + ("\n(v%s)" % pwnagotchi.__version__))
2019-09-19 15:15:46 +02:00
self.set('face', faces.AWAKE)
self.update()
2019-09-19 15:15:46 +02:00
def on_ai_ready(self):
self.set('mode', ' AI')
2019-09-19 15:15:46 +02:00
self.set('face', faces.HAPPY)
2019-09-29 14:17:02 +02:00
self.set('status', self._voice.on_ai_ready())
2019-09-19 15:15:46 +02:00
self.update()
def on_manual_mode(self, last_session):
2019-09-19 15:15:46 +02:00
self.set('mode', 'MANU')
self.set('face', faces.SAD if (last_session.epochs > 3 and last_session.handshakes == 0) else faces.HAPPY)
self.set('status', self._voice.on_last_session_data(last_session))
self.set('epoch', "%04d" % last_session.epochs)
self.set('uptime', last_session.duration)
2019-09-19 15:15:46 +02:00
self.set('channel', '-')
self.set('aps', "%d" % last_session.associated)
self.set('shakes', '%d (%s)' % (last_session.handshakes, \
2019-10-04 00:26:00 +02:00
utils.total_unique_handshakes(self._config['bettercap']['handshakes'])))
self.set_closest_peer(last_session.last_peer, last_session.peers)
self.update()
2019-09-19 15:15:46 +02:00
def is_normal(self):
return self._state.get('face') not in (
faces.INTENSE,
faces.COOL,
faces.BORED,
faces.HAPPY,
faces.EXCITED,
faces.MOTIVATED,
faces.DEMOTIVATED,
faces.SMART,
faces.SAD,
faces.LONELY)
def on_keys_generation(self):
self.set('face', faces.AWAKE)
self.set('status', self._voice.on_keys_generation())
self.update()
2019-09-19 15:15:46 +02:00
def on_normal(self):
self.set('face', faces.AWAKE)
2019-09-29 14:17:02 +02:00
self.set('status', self._voice.on_normal())
2019-09-19 15:15:46 +02:00
self.update()
def set_closest_peer(self, peer, num_total):
2019-09-19 15:15:46 +02:00
if peer is None:
self.set('friend_face', None)
self.set('friend_name', None)
else:
# ref. https://www.metageek.com/training/resources/understanding-rssi-2.html
if peer.rssi >= -67:
num_bars = 4
elif peer.rssi >= -70:
num_bars = 3
elif peer.rssi >= -80:
num_bars = 2
else:
num_bars = 1
name = '' * num_bars
name += '' * (4 - num_bars)
name += ' %s %d (%d)' % (peer.name(), peer.pwnd_run(), peer.pwnd_total())
if num_total > 1:
if num_total > 9000:
name += ' of over 9000'
else:
name += ' of %d' % num_total
2019-09-19 15:15:46 +02:00
self.set('friend_face', peer.face())
self.set('friend_name', name)
self.update()
def on_new_peer(self, peer):
face = ''
# first time they met, neutral mood
if peer.first_encounter():
face = random.choice((faces.AWAKE, faces.COOL))
# a good friend, positive expression
elif peer.is_good_friend(self._config):
face = random.choice((faces.MOTIVATED, faces.FRIEND, faces.HAPPY))
# normal friend, neutral-positive
else:
face = random.choice((faces.EXCITED, faces.HAPPY, faces.SMART))
self.set('face', face)
2019-09-29 14:17:02 +02:00
self.set('status', self._voice.on_new_peer(peer))
2019-09-19 15:15:46 +02:00
self.update()
time.sleep(3)
2019-09-19 15:15:46 +02:00
def on_lost_peer(self, peer):
self.set('face', faces.LONELY)
2019-09-29 14:17:02 +02:00
self.set('status', self._voice.on_lost_peer(peer))
2019-09-19 15:15:46 +02:00
self.update()
def on_free_channel(self, channel):
self.set('face', faces.SMART)
2019-09-29 14:17:02 +02:00
self.set('status', self._voice.on_free_channel(channel))
2019-09-19 15:15:46 +02:00
self.update()
def on_reading_logs(self, lines_so_far=0):
self.set('face', faces.SMART)
self.set('status', self._voice.on_reading_logs(lines_so_far))
self.update()
2019-09-19 15:15:46 +02:00
def wait(self, secs, sleeping=True):
was_normal = self.is_normal()
part = secs/10.0
2019-09-19 15:15:46 +02:00
for step in range(0, 10):
2019-10-26 22:08:06 -04:00
# if we weren't in a normal state before going
# to sleep, keep that face and status on for
# a while, otherwise the sleep animation will
2019-09-19 15:15:46 +02:00
# always override any minor state change before it
if was_normal or step > 5:
if sleeping:
if secs > 1:
self.set('face', faces.SLEEP)
2019-10-03 12:07:16 +02:00
self.set('status', self._voice.on_napping(int(secs)))
2019-09-19 15:15:46 +02:00
else:
self.set('face', faces.SLEEP2)
2019-09-29 14:17:02 +02:00
self.set('status', self._voice.on_awakening())
2019-09-19 15:15:46 +02:00
else:
2019-10-03 12:07:16 +02:00
self.set('status', self._voice.on_waiting(int(secs)))
good_mood = self._agent.in_good_mood()
2019-09-19 15:15:46 +02:00
if step % 2 == 0:
self.set('face', faces.LOOK_R_HAPPY if good_mood else faces.LOOK_R)
2019-09-19 15:15:46 +02:00
else:
self.set('face', faces.LOOK_L_HAPPY if good_mood else faces.LOOK_L)
2019-09-19 15:15:46 +02:00
time.sleep(part)
secs -= part
self.on_normal()
def on_shutdown(self):
self.set('face', faces.SLEEP)
self.set('status', self._voice.on_shutdown())
self.update(force=True)
self._frozen = True
2019-09-19 15:15:46 +02:00
def on_bored(self):
self.set('face', faces.BORED)
2019-09-29 14:17:02 +02:00
self.set('status', self._voice.on_bored())
2019-09-19 15:15:46 +02:00
self.update()
def on_sad(self):
self.set('face', faces.SAD)
2019-09-29 14:17:02 +02:00
self.set('status', self._voice.on_sad())
2019-09-19 15:15:46 +02:00
self.update()
def on_angry(self):
self.set('face', faces.ANGRY)
self.set('status', self._voice.on_angry())
self.update()
2019-09-19 15:15:46 +02:00
def on_motivated(self, reward):
self.set('face', faces.MOTIVATED)
2019-09-29 14:17:02 +02:00
self.set('status', self._voice.on_motivated(reward))
2019-09-19 15:15:46 +02:00
self.update()
def on_demotivated(self, reward):
self.set('face', faces.DEMOTIVATED)
2019-09-29 14:17:02 +02:00
self.set('status', self._voice.on_demotivated(reward))
2019-09-19 15:15:46 +02:00
self.update()
def on_excited(self):
self.set('face', faces.EXCITED)
2019-09-29 14:17:02 +02:00
self.set('status', self._voice.on_excited())
2019-09-19 15:15:46 +02:00
self.update()
def on_assoc(self, ap):
self.set('face', faces.INTENSE)
2019-09-29 14:17:02 +02:00
self.set('status', self._voice.on_assoc(ap))
2019-09-19 15:15:46 +02:00
self.update()
def on_deauth(self, sta):
self.set('face', faces.COOL)
2019-09-29 14:17:02 +02:00
self.set('status', self._voice.on_deauth(sta))
2019-09-19 15:15:46 +02:00
self.update()
def on_miss(self, who):
self.set('face', faces.SAD)
2019-09-29 14:17:02 +02:00
self.set('status', self._voice.on_miss(who))
2019-09-19 15:15:46 +02:00
self.update()
def on_grateful(self):
self.set('face', faces.GRATEFUL)
self.set('status', self._voice.on_grateful())
self.update()
2019-09-19 15:15:46 +02:00
def on_lonely(self):
self.set('face', faces.LONELY)
2019-09-29 14:17:02 +02:00
self.set('status', self._voice.on_lonely())
2019-09-19 15:15:46 +02:00
self.update()
def on_handshakes(self, new_shakes):
self.set('face', faces.HAPPY)
2019-09-29 14:17:02 +02:00
self.set('status', self._voice.on_handshakes(new_shakes))
2019-09-19 15:15:46 +02:00
self.update()
def on_unread_messages(self, count, total):
self.set('face', faces.EXCITED)
self.set('status', self._voice.on_unread_messages(count, total))
self.update()
time.sleep(5.0)
2019-09-19 15:15:46 +02:00
def on_uploading(self, to):
self.set('face', random.choice((faces.UPLOAD, faces.UPLOAD1, faces.UPLOAD2)))
self.set('status', self._voice.on_uploading(to))
self.update(force=True)
2019-09-19 15:15:46 +02:00
def on_rebooting(self):
self.set('face', faces.BROKEN)
2019-09-29 14:17:02 +02:00
self.set('status', self._voice.on_rebooting())
2019-09-19 15:15:46 +02:00
self.update()
def on_custom(self, text):
self.set('face', faces.DEBUG)
self.set('status', self._voice.custom(text))
self.update()
def update(self, force=False, new_data={}):
for key, val in new_data.items():
self.set(key, val)
2019-09-19 15:15:46 +02:00
with self._lock:
if self._frozen:
return
state = self._state
changes = state.changes(ignore=self._ignore_changes)
if force or len(changes):
self._canvas = Image.new('1', (self._width, self._height), WHITE)
drawer = ImageDraw.Draw(self._canvas)
plugins.on('ui_update', self)
2019-09-19 15:15:46 +02:00
for key, lv in state.items():
lv.draw(self._canvas, drawer)
web.update_frame(self._canvas)
for cb in self._render_cbs:
cb(self._canvas)
2019-09-19 15:15:46 +02:00
self._state.reset()