diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py
index 751f19e..27a7b30 100644
--- a/pwnagotchi/ui/display.py
+++ b/pwnagotchi/ui/display.py
@@ -61,6 +61,9 @@ class Display(View):
     def is_waveshare213d(self):
         return self._implementation.name == 'waveshare213d'
 
+    def is_spotpear24inch(self):
+        return self._implementation.name == 'spotpear24inch'
+
     def is_waveshare_any(self):
         return self.is_waveshare_v1() or self.is_waveshare_v2()
 
diff --git a/pwnagotchi/ui/hw/__init__.py b/pwnagotchi/ui/hw/__init__.py
index 77fd60e..1bbb602 100644
--- a/pwnagotchi/ui/hw/__init__.py
+++ b/pwnagotchi/ui/hw/__init__.py
@@ -9,7 +9,7 @@ from pwnagotchi.ui.hw.waveshare27inch import Waveshare27inch
 from pwnagotchi.ui.hw.waveshare29inch import Waveshare29inch
 from pwnagotchi.ui.hw.waveshare154inch import Waveshare154inch
 from pwnagotchi.ui.hw.waveshare213d import Waveshare213d
-
+from pwnagotchi.ui.hw.spotpear24inch import Spotpear24inch
 
 def display_for(config):
     # config has been normalized already in utils.load_config
@@ -44,4 +44,7 @@ def display_for(config):
         return Waveshare154inch(config)
 
     elif config['ui']['display']['type'] == 'waveshare213d':
-        return Waveshare213d(config)
\ No newline at end of file
+        return Waveshare213d(config)
+
+    elif config['ui']['display']['type'] == 'spotpear24inch':
+        return Spotpear24inch(config)
\ No newline at end of file
diff --git a/pwnagotchi/ui/hw/libs/fb/__init__.py b/pwnagotchi/ui/hw/libs/fb/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pwnagotchi/ui/hw/libs/fb/fb.py b/pwnagotchi/ui/hw/libs/fb/fb.py
new file mode 100644
index 0000000..f3c0f72
--- /dev/null
+++ b/pwnagotchi/ui/hw/libs/fb/fb.py
@@ -0,0 +1,144 @@
+FBIOGET_VSCREENINFO=0x4600
+FBIOPUT_VSCREENINFO=0x4601
+FBIOGET_FSCREENINFO=0x4602
+FBIOGETCMAP=0x4604
+FBIOPUTCMAP=0x4605
+FBIOPAN_DISPLAY=0x4606
+
+FBIOGET_CON2FBMAP=0x460F
+FBIOPUT_CON2FBMAP=0x4610
+FBIOBLANK=0x4611
+FBIO_ALLOC=0x4613
+FBIO_FREE=0x4614
+FBIOGET_GLYPH=0x4615
+FBIOGET_HWCINFO=0x4616
+FBIOPUT_MODEINFO=0x4617
+FBIOGET_DISPINFO=0x4618
+
+from mmap import mmap
+from fcntl import ioctl
+import struct
+
+mm = None
+bpp, w, h = 0, 0, 0 # framebuffer bpp and size
+bytepp = 0
+vx, vy, vw, vh = 0, 0, 0, 0 #virtual window offset and size
+vi, fi = None, None
+_fb_cmap = 'IIPPPP' # start, len, r, g, b, a
+RGB = False
+_verbose = False
+msize_kb = 0
+
+def report_fb(i=0, layer=0):
+  with open('/dev/fb'+str(i), 'r+b')as f:
+    vi = ioctl(f, FBIOGET_VSCREENINFO, bytes(160))
+    vi = list(struct.unpack('I'*40, vi))
+    ffm = 'c'*16+'L'+'I'*4+'H'*3+'ILIIHHH'
+    fic = struct.calcsize(ffm)
+    fi = struct.unpack(ffm, ioctl(f, FBIOGET_FSCREENINFO, bytes(fic)))
+
+def ready_fb(_bpp=None, i=0, layer=0, _win=None):
+  global mm, bpp, w, h, vi, fi, RGB, msize_kb, vx, vy, vw, vh, bytepp
+  if mm and bpp == _bpp: return mm, w, h, bpp
+  with open('/dev/fb'+str(i), 'r+b')as f:
+    vi = ioctl(f, FBIOGET_VSCREENINFO, bytes(160))
+    vi = list(struct.unpack('I'*40, vi))
+    bpp = vi[6]
+    bytepp = bpp//8
+    if _bpp:
+      vi[6] = _bpp # 24 bit = BGR 888 mode
+      try:
+        vi = ioctl(f, FBIOPUT_VSCREENINFO, struct.pack('I'*40, *vi)) # fb_var_screeninfo
+        vi = struct.unpack('I'*40,vi)
+        bpp = vi[6]
+        bytepp = bpp//8
+      except:
+        pass
+    
+    if vi[8] == 0 : RGB = True
+    
+    ffm = 'c'*16+'L'+'I'*4+'H'*3+'ILIIHHH'
+    fic = struct.calcsize(ffm)
+    fi = struct.unpack(ffm, ioctl(f, FBIOGET_FSCREENINFO, bytes(fic)))
+    msize = fi[17] # = w*h*bpp//8
+    ll, start = fi[-7:-5]
+    w, h = ll//bytepp, vi[1] # when screen is vertical, width becomes wrong. ll//3 is more accurate at such time.
+    if _win and len(_win)==4: # virtual window settings
+      vx, vy, vw, vh = _win
+      if vw == 'w': vw = w
+      if vh == 'h': vh = h
+      vx, vy, vw, vh = map(int, (vx, vy, vw, vh))
+      if vx>=w: vx = 0
+      if vy>=h: vy = 0
+      if vx>w: vw = w - vx
+      else: vw -= vx
+      if vy>h: vh = h - vy
+      else: vh -= vy
+    else:
+      vx, vy, vw, vh = 0,0,w,h
+    msize_kb = vw*vh*bytepp//1024 # more accurate FB memory size in kb
+
+    mm = mmap(f.fileno(), msize, offset=start)
+    return mm, w, h, bpp#ll//(bpp//8), h
+
+def fill_scr(r,g,b):
+  if bpp == 32:
+    seed = struct.pack('BBBB', b, g, r, 255)
+  elif bpp == 24:
+    seed = struct.pack('BBB', b, g, r)
+  elif bpp == 16:
+    seed = struct.pack('H', r>>3<<11 | g>>2<<5 | b>>3)
+  mm.seek(0)
+  show_img(seed * vw * vh)
+
+def black_scr():
+  fill_scr(0,0,0)
+
+def white_scr():
+  fill_scr(255,255,255)
+
+def mmseekto(x,y):
+  mm.seek((x + y*w) * bytepp)
+
+def dot(x, y, r, g, b):
+  mmseekto(x,y)
+  mm.write(struct.pack('BBB',*((r,g,b) if RGB else (b,g,r))))
+
+def get_pixel(x,y):
+  mmseekto(x,y)
+  return mm.read(bytepp)
+
+def _888_to_565(bt):
+  b = b''
+  for i in range(0, len(bt),3):
+    b += int.to_bytes(bt[i]>>3<<11|bt[i+1]>>2<<5|bt[i+2]>>3, 2, 'little')
+  return b
+
+def numpy_888_565(bt):
+  import numpy as np
+  arr = np.fromstring(bt, dtype=np.uint32)
+  return (((0xF80000 & arr)>>8)|((0xFC00 & arr)>>5)|((0xF8 & arr)>>3)).astype(np.uint16).tostring()
+
+def show_img(img):
+  if not type(img) is bytes:
+    if not RGB:
+      if bpp == 24: # for RPI
+        img = img.tobytes('raw', 'BGR')
+      else:
+        img = img.convert('RGBA').tobytes('raw', 'BGRA')
+        if bpp == 16:
+          img = numpy_888_565(img)
+    else:
+      if bpp == 24:
+        img = img.tobytes()
+      else:
+        img = img.convert('RGBA').tobytes()
+        if bpp == 16:
+          img = numpy_888_565(img)
+  from io import BytesIO
+  b = BytesIO(img)
+  s = vw*bytepp
+  for y in range(vh): # virtual window drawing
+    mmseekto(vx,vy+y)
+    mm.write(b.read(s))
+
diff --git a/pwnagotchi/ui/hw/spotpear24inch.py b/pwnagotchi/ui/hw/spotpear24inch.py
new file mode 100644
index 0000000..e93b071
--- /dev/null
+++ b/pwnagotchi/ui/hw/spotpear24inch.py
@@ -0,0 +1,52 @@
+import logging
+
+import pwnagotchi.ui.fonts as fonts
+from pwnagotchi.ui.hw.base import DisplayImpl
+
+import os,time
+
+class Spotpear24inch(DisplayImpl):
+    def __init__(self, config):
+        super(Spotpear24inch, self).__init__(config, 'spotpear24inch')
+        self._display = None
+
+    def layout(self):
+        fonts.setup(12, 10, 12, 70)
+        self._layout['width'] = 320
+        self._layout['height'] = 240
+        self._layout['face'] = (35, 50)
+        self._layout['name'] = (5, 20)
+        self._layout['channel'] = (0, 0)
+        self._layout['aps'] = (40, 0)
+        self._layout['uptime'] = (240, 0)
+        self._layout['line1'] = [0, 14, 320, 14]
+        self._layout['line2'] = [0, 220, 320, 220]
+        self._layout['friend_face'] = (0, 130)
+        self._layout['friend_name'] = (40, 135)
+        self._layout['shakes'] = (0, 220)
+        self._layout['mode'] = (280, 220)
+        self._layout['status'] = {
+            'pos': (80, 160),
+            'font': fonts.Medium,
+            'max': 20
+        }
+
+        return self._layout
+
+    def refresh(self):
+        time.sleep(0.1)
+
+    def initialize(self):
+        from pwnagotchi.ui.hw.libs.fb import fb
+        self._display = fb
+        logging.info("initializing spotpear 24inch lcd display")
+        self._display.ready_fb(i=1)
+        self._display.black_scr()
+
+    def render(self, canvas):
+        self._display.show_img(canvas.rotate(180))
+        self.refresh()
+
+    def clear(self):
+        self._display.black_scr()
+        self.refresh()
diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py
index 239e5e8..4384dc8 100644
--- a/pwnagotchi/utils.py
+++ b/pwnagotchi/utils.py
@@ -112,6 +112,9 @@ def load_config(args):
     elif config['ui']['display']['type'] in ('ws_213d', 'ws213d', 'waveshare_213d', 'waveshare213d'):
         config['ui']['display']['type'] = 'waveshare213d'
 
+    elif config['ui']['display']['type'] in ('spotpear24inch'):
+        config['ui']['display']['type'] = 'spotpear24inch'
+
     else:
         print("unsupported display type %s" % config['ui']['display']['type'])
         exit(1)