diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py index 4f9bb4a..c530caa 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py @@ -123,11 +123,11 @@ class Display(View): self._display = InkyPHAT(self._display_color) self._display.set_border(InkyPHAT.BLACK) self._render_cb = self._inky_render - + elif self._is_papirus(): - from papirus import Papirus + from pwnagotchi.ui.papirus.epd import EPD os.environ['EPD_SIZE'] = '2.0' - self._display = Papirus() + self._display = EPD() self._display.clear() self._render_cb = self._papirus_render diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/papirus/__init__.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/papirus/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/papirus/epd.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/papirus/epd.py new file mode 100644 index 0000000..923993b --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/papirus/epd.py @@ -0,0 +1,213 @@ +#qCopyright 2013-2015 Pervasive Displays, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +# express or implied. See the License for the specific language +# governing permissions and limitations under the License. + + +from PIL import Image +from PIL import ImageOps +from pwnagotchi.ui.papirus.lm75b import LM75B +import re +import os +import sys + +if sys.version_info < (3,): + def b(x): + return x +else: + def b(x): + return x.encode('ISO-8859-1') + +class EPDError(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + + +class EPD(object): + + """EPD E-Ink interface + +to use: + from EPD import EPD + + epd = EPD([path='/path/to/epd'], [auto=boolean], [rotation = 0|90|180|270]) + + image = Image.new('1', epd.size, 0) + # draw on image + epd.clear() # clear the panel + epd.display(image) # tranfer image data + epd.update() # refresh the panel image - not needed if auto=true +""" + + + PANEL_RE = re.compile('^([A-Za-z]+)\s+(\d+\.\d+)\s+(\d+)x(\d+)\s+COG\s+(\d+)\s+FILM\s+(\d+)\s*$', flags=0) + + def __init__(self, *args, **kwargs): + self._epd_path = '/dev/epd' + self._width = 200 + self._height = 96 + self._panel = 'EPD 2.0' + self._cog = 0 + self._film = 0 + self._auto = False + self._lm75b = LM75B() + self._rotation = 0 + self._uselm75b = True + + if len(args) > 0: + self._epd_path = args[0] + elif 'epd' in kwargs: + self._epd_path = kwargs['epd'] + + if ('auto' in kwargs) and kwargs['auto']: + self._auto = True + if ('rotation' in kwargs): + rot = kwargs['rotation'] + if rot in (0, 90, 180, 270): + self._rotation = rot + else: + raise EPDError('rotation can only be 0, 90, 180 or 270') + + with open(os.path.join(self._epd_path, 'version')) as f: + self._version = f.readline().rstrip('\n') + + with open(os.path.join(self._epd_path, 'panel')) as f: + line = f.readline().rstrip('\n') + m = self.PANEL_RE.match(line) + if m is None: + raise EPDError('invalid panel string') + self._panel = m.group(1) + ' ' + m.group(2) + self._width = int(m.group(3)) + self._height = int(m.group(4)) + self._cog = int(m.group(5)) + self._film = int(m.group(6)) + + if self._width < 1 or self._height < 1: + raise EPDError('invalid panel geometry') + if self._rotation in (90, 270): + self._width, self._height = self._height, self._width + + @property + def size(self): + return (self._width, self._height) + + @property + def width(self): + return self._width + + @property + def height(self): + return self._height + + @property + def panel(self): + return self._panel + + @property + def version(self): + return self._version + + @property + def cog(self): + return self._cog + + @property + def film(self): + return self._film + + @property + def auto(self): + return self._auto + + @auto.setter + def auto(self, flag): + if flag: + self._auto = True + else: + self._auto = False + + @property + def rotation(self): + return self._rotation + + @rotation.setter + def rotation(self, rot): + if rot not in (0, 90, 180, 270): + raise EPDError('rotation can only be 0, 90, 180 or 270') + if abs(self._rotation - rot) == 90 or abs(self._rotation - rot) == 270: + self._width, self._height = self._height, self._width + self._rotation = rot + + @property + def use_lm75b(self): + return self._uselm75b + + @use_lm75b.setter + def use_lm75b(self, flag): + if flag: + self._uselm75b = True + else: + self._uselm75b = False + + def error_status(self): + with open(os.path.join(self._epd_path, 'error'), 'r') as f: + return(f.readline().rstrip('\n')) + + def rotation_angle(self, rotation): + angles = { 90 : Image.ROTATE_90, 180 : Image.ROTATE_180, 270 : Image.ROTATE_270 } + return angles[rotation] + + def display(self, image): + + # attempt grayscale conversion, and then to single bit + # better to do this before calling this if the image is to + # be dispayed several times + if image.mode != "1": + image = ImageOps.grayscale(image).convert("1", dither=Image.FLOYDSTEINBERG) + + if image.mode != "1": + raise EPDError('only single bit images are supported') + + if image.size != self.size: + raise EPDError('image size mismatch') + + if self._rotation != 0: + image = image.transpose(self.rotation_angle(self._rotation)) + + with open(os.path.join(self._epd_path, 'LE', 'display_inverse'), 'r+b') as f: + f.write(image.tobytes()) + + if self.auto: + self.update() + + + def update(self): + self._command('U') + + def partial_update(self): + self._command('P') + + def fast_update(self): + self._command('F') + + def clear(self): + self._command('C') + + def _command(self, c): + if self._uselm75b: + with open(os.path.join(self._epd_path, 'temperature'), 'wb') as f: + f.write(b(repr(self._lm75b.getTempC()))) + with open(os.path.join(self._epd_path, 'command'), 'wb') as f: + f.write(b(c)) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/papirus/lm75b.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/papirus/lm75b.py new file mode 100644 index 0000000..f3087f2 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/papirus/lm75b.py @@ -0,0 +1,46 @@ +# Minimal support for LM75b temperature sensor on the Papirus HAT / Papirus Zero +# This module allows you to read the temperature. +# The OS-output (Over-temperature Shutdown) connected to GPIO xx (pin 11) is not supported +# by this module +# + +from __future__ import (print_function, division) + +import smbus + +LM75B_ADDRESS = 0x48 + +LM75B_TEMP_REGISTER = 0 +LM75B_CONF_REGISTER = 1 +LM75B_THYST_REGISTER = 2 +LM75B_TOS_REGISTER = 3 + +LM75B_CONF_NORMAL = 0 + +class LM75B(object): + def __init__(self, address=LM75B_ADDRESS, busnum=1): + self._address = address + self._bus = smbus.SMBus(busnum) + self._bus.write_byte_data(self._address, LM75B_CONF_REGISTER, LM75B_CONF_NORMAL) + + def getTempCFloat(self): + """Return temperature in degrees Celsius as float""" + raw = self._bus.read_word_data(self._address, LM75B_TEMP_REGISTER) & 0xFFFF + raw = ((raw << 8) & 0xFF00) + (raw >> 8) + return (raw / 32.0) / 8.0 + + def getTempFFloat(self): + """Return temperature in degrees Fahrenheit as float""" + return (self.getTempCFloat() * (9.0 / 5.0)) + 32.0 + + def getTempC(self): + """Return temperature in degrees Celsius as integer, so it can be + used to write to /dev/epd/temperature""" + raw = self._bus.read_word_data(self._address, LM75B_TEMP_REGISTER) & 0xFFFF + raw = ((raw << 8) & 0xFF00) + (raw >> 8) + return (raw + 128) // 256 # round to nearest integer + +if __name__ == "__main__": + sens = LM75B() + print(sens.getTempC(), sens.getTempFFloat()) + diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/requirements.txt b/sdcard/rootfs/root/pwnagotchi/scripts/requirements.txt index 9b0d40d..0a82ef7 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/requirements.txt +++ b/sdcard/rootfs/root/pwnagotchi/scripts/requirements.txt @@ -9,3 +9,4 @@ tweepy file_read_backwards numpy inky +smbus