From ca916b4722a7faffeb6ebda84d7903500dc52760 Mon Sep 17 00:00:00 2001
From: Periklis Fregkos <leajian13@gmail.com>
Date: Mon, 30 Sep 2019 05:03:33 +0300
Subject: [PATCH 1/2] Added Greek support

There are some issues with plural words though.
---
 .../pwnagotchi/locale/gr/LC_MESSAGES/voice.mo | Bin 0 -> 4750 bytes
 .../pwnagotchi/locale/gr/LC_MESSAGES/voice.po | 341 ++++++++++++++++++
 2 files changed, 341 insertions(+)
 create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/gr/LC_MESSAGES/voice.mo
 create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/gr/LC_MESSAGES/voice.po

diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/gr/LC_MESSAGES/voice.mo b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/gr/LC_MESSAGES/voice.mo
new file mode 100644
index 0000000000000000000000000000000000000000..434c3b8426a1ea2236573ce376eefaf58166c189
GIT binary patch
literal 4750
zcmbW4TWlOx8ONt>O6fp=luJv2a<+k_CEl%_geGxF+q4P9ZJb285LJPP@$T{N;N2N(
zXEtUr7R}X(lmwL$Aw`LZ(nug=$JzwfaU91ypgzHw7bM;gf(PIM0T1v}1pK};vtB2(
z4=~aAzh~xrxBquJlixhD?7YKO;QloCnfn}P3;56b`NQ?s2OQ^N@NeM9!FRx?!4=CK
z=M&&&P@eAvKLS?3RbUeoeV4(F;5?|ne}EqaAAK;DTLUg<y%l^E+?`<={21%+fDeJ+
z2Sxuy@D?};ik-^)9On`6yP)`U1r)hi@G;PUqW2G=3;rAYFzBLG^ghm?9QY(C`?i7N
z_t!uLHo-OE1yKBa8x;S41<JlZf(m@_{i*#`;AdEG0Y%>-Q1(aQ<KQ?bep~^??@91O
z;LpGv_c>0IJ%5Z%!t)w%CwL%RUjd(F{R>e1{tMU#uJ}Oe=OM7hdK`QX{5$v*xc-BV
z^91;HQ26;axE6dJ6n}pXqRROl_*wAJpvWyJ=zoFFg8kqZads7W3=}_p0)7hoCAbp&
z6DWQ!hh$lA0M~$rKvX#6;LG6o?EP;+@nadueG%LMz5;#=6n@_ZMbGa+smq5}IL`Cn
zc2M4*0M~<8K-u?eQ2cralsKM%<j;Z6fl{YMP~vZb&}}b~`#iVQi^K+HP8T;S7B7*J
zOSo!ZxNa}um9VWHXmcn{d*QP6K^6~lOTIqIEp<v8+;!zy3EvNJD{kSiz%4a{tIld}
zO2b~1u+5e5DcnoEa^24@oP2>>YER;oOZb->q*Q6NK2_IVsabHgMp0Pwz1R=RDh}Pz
zk%||OIWL7_wM&g?RSsh{8Z>HSZlFh0#S2PN#T(M2!_`LJtB&QIZQ5(Z6&=KW(JFCA
zYhH28`AS=^BNMsK4zD<**sb<dy+Fq~XNRs<F&cO^J(hEK`9+Nrs-|_Unqi}WX?er8
zX6Bh&)$!^`4QMr3_ch*#j%JrC;##1qYBc*Xru@hqtZSW9uZE4f+PX)L_|>W#c}*Pf
zf}yD3?C`r(-|Cv$GZJ{jS*-XucVFz)Ef<AC!Rb?j8l6$7ylNO|l|CCD2_%NP9t`VR
z^(n8$PSgyrETwx`>2Jn$UDMU(J<luNVWAqGwMMbx4mN_Em9F~sxsX%!2X*=|@=EsQ
zVO?)37Mb?L8Ul2n@I-XshZ-%BkJt}`F-4BlkY60anbA_3ozj>pR-r47azu={R}}|f
zdDP4Duh}Ch&S<q-yjZKz#hgcDOLCc0E3ux@W`9@1a*BAbR#wO~u?*WhtCi7ElYBcb
zH=<aTbkQ&29BGKtYN57tDN;eG4*FmAbIvPxKR`YSF5Qqml7OTXRTSG$DX5*c8p@&N
zz*8e$btvcT48tMe#jA%6xOi$b(#2>jdfMstN(5w8ED6<h`XQo64QpMuKhqtujGTTy
zigYRG^fy(p?$<PBWgS=Hpj&Lz{V?+5rcKK(Z+Lhy8>z+gIi!|6O4K$`BZ#|fE5RPM
zyDJ<BOHKSHHPU|Y#mbK>B*#kc9*(*w7E)CaHb%&y8mWgwM=`p4^*|Udmg~NXAL9(h
zRPQs+J`(H&P0Yq4VSOmd<#Nt`sw>dax$Va;@nn-=e}&&RJSndzc12M*sA|ouA`et*
z)@nHnO9M@vwXJg?@X4cXRM8e_uRhSswOjX~=UWGDkuIU-pmY$OtfjVvgy(b4!Acko
zN7hD1UzyG;IIp%xQ_i{jm1QY=RLhdJryhPo7vp?iDgRZHNLTc#?m~B=2f~KKdYtc%
z%6=*TVxt`8_lLd8-LpHtS0DCeXMP)1->cSk_iV~{Z_2OTq<Yr(c6YDoUfbR6?)0KK
zzrXGUQPqpXdM{j;yu<2C>(=#b+VJeUo;7y;LL<UzAz&U1SDS@0>1+%Xiec?J6a?jl
zSJwIc+N<?S1Gih~f7j8g%FMSRx4*A{dneAGLbv-;7{nAxet&bAP+~^WGZdB|Y?d)u
zk954H5fA1!cJ@g`gSwvI9u&h8b=Ipk4v<vGTs4=JnM{tG1v6`Io0)=R-b_v;$C7a~
z&C@CK7H@8wmYG$+w340eGX&vDGiRoeY$@|o;Fg(`Z41eXE|t7yt|dp16tVP#G4lva
zCr3MDrgO}g*AZCYp2avuWQF;e<d~Tet24>b?Bp?*Q^i@Mi>C--DLI7z>eFM0^^weB
zGp`Vu!<TtP(Uo(|8`kd$g?`*!JfX~w%mi{XghEU=Y-l1Ozvq+Fyb_wGOl$GzGUv^7
z`|QHzQQ0|#A+)B9on$#D{9^Syybzoc_3WA8#q5$J4WC35wq1TgZF1J!<nIx)kW=O=
zf*f<H$BGv%*_8<>ww<s}o-?<T(`KGbN{qPL#XB&~qBXx~u`F{g{@>!?tabTpwo48|
za|v3{nVZRJ$>jp>%$aL8z1GD$qS`hm+JrRrS?*ej0(#9p8Bb25=X*94v*bQ?!79J^
zp_P$HuUk`6OC-nIP0m^~=2V*M&Qo5`!695it*~R>N=^&Qi_w}nt9J&=lM-O6sPlf&
zz`Jggz@&;6D`CkYELNo{CT&UFVb7AY7`2kV3CN}%N!@v#Oi3xH^)a8E6EPkjw)B9;
z$4RLiwk28J*hSkKM$y?a*P;6iblhw!skA@bIK=<EBmTd`++0k~5XG$c+!Cg6X#$^9
zzp;N@2$;!^cm@FDVVt;Z(J?q=fCxwFnQtz@@#*BalrI%3>dq4GEcML3Obf|7)gja#
z<o0!oNM){^mF_l|NxDoG{@BrTUHlYdi2oQD$E8(iG^^<bLQ<QZuLN_-(n+6XBji0;
z$I=^U>ZGbid_BSL2?-XHC(XjWd(v-<Y+s9gj7ZrhX><(Jg-B$&Ot(w+HqJ|B+W1aT
zP&Dx^XqTG3yTO@*)RhfNs_q6ZNcgs$F35mELWr?-nU1dP%P8G*3Eh-3t$?&l1zT*)
zA{)~+6wY?J_F+p7F1}vKnT#nCDm5Sp#`|pMi+Ib$qZZwEM|=_0E%H9qj>UTRL+QZe
zDCX#BDH)LwV<?mEy$Lr*#X%;K)h71dV<NrD3uyr9Wt;gd7Wo`9SE)#8D3qkbYC>F|
zBD~`S*pkm9V}}F=@QIqRT5ct0mhM})Yo>Ni#svnM%ndu8srqz;&rw{`<D3|7n^+1j
z?Q@0^#|^~U6A|m@`E@%D6<XOgC5LJHKOpl1yAS<RT&?!^mT=#;d5gG2YGU!*%UsUt
z!Hzc>!0(dwi;`<0g*gfc^;lPwojsK{<5-p>>st1yMUkAe2g)>oUzYk^4ZC!BdZ-E@
XKe;+P`WC<1I&!w?GHtZvlEVKF>Fjqx

literal 0
HcmV?d00001

diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/gr/LC_MESSAGES/voice.po b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/gr/LC_MESSAGES/voice.po
new file mode 100644
index 0000000..2d21d99
--- /dev/null
+++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/gr/LC_MESSAGES/voice.po
@@ -0,0 +1,341 @@
+# pwnigotchi voice data
+# Copyright (C) 2019
+# This file is distributed under the same license as the pwnagotchi package.
+# FIRST AUTHOR <33197631+dadav@users.noreply.github.com>, 2019.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: 0.0.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-09-29 13:42+0200\n"
+"PO-Revision-Date: 2019-09-29 14:00+0200\n"
+"Last-Translator: dadav <33197631+dadav@users.noreply.github.com>\n"
+"Language-Team: pwnagotchi <33197631+dadav@users.noreply.github.com>\n"
+"Language: greek\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: voice.py:16
+msgid "ZzzzZZzzzzZzzz"
+msgstr ""
+
+#: voice.py:21
+msgid ""
+"Hi, I'm Pwnagotchi!\n"
+"Starting ..."
+msgstr ""
+"Γεία, είμαι το Pwnagotchi!\n"
+"Εκκινούμαι ..."
+
+#: voice.py:22
+msgid ""
+"New day, new hunt,\n"
+"new pwns!"
+msgstr ""
+"Νέα μέρα, νέο κυνήγι,\n"
+"νέα pwns!"
+
+#: voice.py:23
+msgid "Hack the Planet!"
+msgstr "Hackαρε τον Πλανήτη!"
+
+#: voice.py:28
+msgid "AI ready."
+msgstr "ΤΝ έτοιμη."
+
+#: voice.py:29
+msgid ""
+"The neural network\n"
+"is ready."
+msgstr ""
+"Το νευρωνικό δίκτυο\n"
+"είναι έτοιμο."
+
+#: voice.py:39
+#, python-brace-format
+msgid ""
+"Hey, channel {channel} is\n"
+"free! Your AP will\n"
+"say thanks."
+msgstr ""
+"Ε, το κανάλι {channel} είναι\n"
+"ελεύθερο! Το AP σου θα\n"
+"είναι ευγνώμων."
+
+#: voice.py:44
+msgid "I'm bored ..."
+msgstr "Βαριέμαι ..."
+
+#: voice.py:45
+msgid "Let's go for a walk!"
+msgstr "Πάμε μια βόλτα!"
+
+#: voice.py:49
+msgid ""
+"This is the best\n"
+"day of my life!"
+msgstr ""
+"Είναι η καλύτερη\n"
+"μέρα της ζωής μου!"
+
+#: voice.py:53
+msgid "Shitty day :/"
+msgstr "Σκατένια μέρα :/"
+
+#: voice.py:58
+msgid "I'm extremely bored ..."
+msgstr "Βαριέμαι υπερβολικά πολύ ..."
+
+#: voice.py:59
+msgid "I'm very sad ..."
+msgstr "Είμαι πολύ λυπημένο ..."
+
+#: voice.py:60
+msgid "I'm sad"
+msgstr "Είμαι λυπημένο ..."
+
+#: voice.py:66
+msgid "I'm living the life!"
+msgstr "Το ζω!"
+
+#: voice.py:67
+msgid "I pwn therefore I am."
+msgstr "Pwnάρω άρα υπάρχω."
+
+#: voice.py:68
+msgid "So many networks!!!"
+msgstr "Τόσα πολλά δίκτυα!!!"
+
+#: voice.py:69
+msgid ""
+"I'm having so much\n"
+"fun!"
+msgstr "Έχει πολύ πλάκα!"
+
+#: voice.py:70
+msgid ""
+"My crime is that of\n"
+"curiosity ..."
+msgstr ""
+"Το μόνο μου έγκλημα\n"
+"είναι η περιέργεια ..."
+
+#: voice.py:75
+#, python-brace-format
+msgid ""
+"Hello\n"
+"{name}!\n"
+"Nice to meet you. {name}"
+msgstr ""
+"Γειά σου\n"
+"{name}!\n"
+"Χάρηκα για τη γνωριμία. {name}"
+
+#: voice.py:76
+#, python-brace-format
+msgid ""
+"Unit\n"
+"{name}\n"
+"is nearby! {name}"
+msgstr ""
+"Η μονάδα\n"
+"{name}\n"
+"είναι κοντά! {name}"
+
+#: voice.py:81
+#, python-brace-format
+msgid ""
+"Uhm ...\n"
+"goodbye\n"
+"{name}"
+msgstr ""
+"Εμμ ...\n"
+"αντίο\n"
+"{name}"
+
+#: voice.py:82
+#, python-brace-format
+msgid ""
+"{name}\n"
+"is gone ..."
+msgstr ""
+"{name}\n"
+"έφυγε ..."
+
+#: voice.py:87
+#, python-brace-format
+msgid ""
+"Whoops ...\n"
+"{name}\n"
+"is gone."
+msgstr ""
+"Ουπς ...\n"
+"{name}\n"
+"έφυγε."
+
+#: voice.py:88
+#, python-brace-format
+msgid ""
+"{name}\n"
+"missed!"
+msgstr ""
+"{name}\n"
+"χάθηκε!"
+
+#: voice.py:89
+msgid "Missed!"
+msgstr "Χάθηκε!"
+
+#: voice.py:94
+msgid ""
+"Nobody wants to\n"
+"play with me ..."
+msgstr ""
+"Κανείς δε θέλει να\n"
+"παίξει μαζί μου ..."
+
+#: voice.py:95
+msgid "I feel so alone ..."
+msgstr "Νιώθω πολλή μοναξία ..."
+
+#: voice.py:96
+msgid "Where's everybody?!"
+msgstr "Μα, πού πήγαν όλοι;!"
+
+#: voice.py:101
+#, python-brace-format
+msgid "Napping for {secs}s ..."
+msgstr "Κοιμάμαι για {secs}s ..."
+
+#: voice.py:102
+msgid "Zzzzz"
+msgstr ""
+
+#: voice.py:103
+#, python-brace-format
+msgid "ZzzZzzz ({secs}s)"
+msgstr ""
+
+#: voice.py:112
+#, python-brace-format
+msgid "Waiting for {secs}s ..."
+msgstr "Περιμένω για {secs}s ..."
+
+#: voice.py:114
+#, python-brace-format
+msgid "Looking around ({secs}s)"
+msgstr "Ψάχνω τριγύρω ({secs}s)"
+
+#: voice.py:121
+#, python-brace-format
+msgid ""
+"Hey\n"
+"{what}\n"
+"let's be friends!"
+msgstr ""
+"Εε\n"
+"{what}\n"
+"ας γίνουμε φίλοι!"
+
+#: voice.py:122
+#, python-brace-format
+msgid ""
+"Associating to\n"
+"{what}"
+msgstr ""
+"Συσχετίζομαι με το\n"
+"{what}"
+
+#: voice.py:123
+#, python-brace-format
+msgid ""
+"Yo\n"
+"{what}!"
+msgstr ""
+"Που'σε ρε τρελέ'\n"
+"{what}!"
+
+#: voice.py:128
+#, python-brace-format
+msgid ""
+"Just decided that\n"
+"{mac}\n"
+"needs no WiFi!"
+msgstr ""
+"Μόλις αποφάσισα ότι η\n"
+"{mac}\n"
+"δε χρείαζεται WiFi!"
+
+#: voice.py:129
+#, python-brace-format
+msgid ""
+"Deauthenticating\n"
+"{mac}"
+msgstr ""
+"Αποπιστοποίηση της\n"
+"{mac}"
+
+#: voice.py:130
+#, python-brace-format
+msgid ""
+"Kickbanning\n"
+"{mac}!"
+msgstr ""
+"Κλωτσομπούνι στη\n"
+"{mac}!"
+
+#: voice.py:135
+#, python-brace-format
+msgid ""
+"Cool, we got {num}\n"
+"new handshake{plural}!"
+msgstr ""
+"Τέλεια δικέ μου, πήραμε {num}\n"
+"νέες handshake{plural}!"
+
+#: voice.py:139
+msgid ""
+"Ops, something\n"
+"went wrong ...\n"
+"Rebooting ..."
+msgstr ""
+"Ουπς, κάτι\n"
+"πήγε λάθος ...\n"
+"Επανεκκινούμαι ..."
+
+#: voice.py:143
+#, python-brace-format
+msgid "Kicked {num} stations\n"
+msgstr "Έριξα {num} σταθμούς\n"
+
+#: voice.py:144
+#, python-brace-format
+msgid "Made {num} new friends\n"
+msgstr "Έκανα {num} νέους φίλους\n"
+
+#: voice.py:145
+#, python-brace-format
+msgid "Got {num} handshakes\n"
+msgstr "Πήρα {num} χειραψίες\n"
+
+#: voice.py:147
+msgid "Met 1 peer"
+msgstr "Γνώρισα 1 συνάδελφο"
+
+#: voice.py:149
+#, python-brace-format
+msgid "Met {num} peers"
+msgstr "Γνώρισα {num} συναδέλφους"
+
+#: voice.py:154
+#, python-brace-format
+msgid ""
+"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
+"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
+"#pwnlog #pwnlife #hacktheplanet #skynet"
+msgstr ""
+"Pwnαρα για {duration} και έριξα {deauthed} πελάτες! Επίσης γνώρισα "
+"{associated} νέους φίλους και καταβρόχθισα {handshakes} χειραψίες! #pwnagotchi "
+"#pwnlog #pwnlife #hacktheplanet #skynet"

From b25092c97b2c1453d9defba3f3d5ffeef70d450f Mon Sep 17 00:00:00 2001
From: James Hooker <g0blin@hackthebox.eu>
Date: Mon, 30 Sep 2019 11:20:35 +0100
Subject: [PATCH 2/2] Include PaPiRus drivers in project

---
 .../scripts/pwnagotchi/ui/display.py          |   6 +-
 .../scripts/pwnagotchi/ui/papirus/__init__.py |   0
 .../scripts/pwnagotchi/ui/papirus/epd.py      | 213 ++++++++++++++++++
 .../scripts/pwnagotchi/ui/papirus/lm75b.py    |  46 ++++
 .../root/pwnagotchi/scripts/requirements.txt  |   1 +
 5 files changed, 263 insertions(+), 3 deletions(-)
 create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/papirus/__init__.py
 create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/papirus/epd.py
 create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/papirus/lm75b.py

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