Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for 128x64 #10

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
__pycache__
/.vs
25 changes: 17 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
IC2 OLED controller for Raspberry PI
=======================

Python library to enable 128x32 pixel OLED for Raspberry Pi (both 32 and 64bit).
Enables 128x32 or 64 pixel OLED for Raspberry Pi (both 32 and 64bit).

This repository has been broken out to work as a standalone service and will work on a standard Raspberry Pi running Raspian.

Expand All @@ -13,9 +13,11 @@ This repository has been broken out to work as a standalone service and will wor
<br>

## Some Teaser Screenshots.
| Welcome | HA Splash | CPU Stats | RAM Stats | Storage Stats | Network Stats | Exit Screen |
|-----------|-----------|-----------|-----------|---------------|---------------|---------------|
| ![Welcome][welcome-url] | ![Splash][splash-url] | ![CPU Stats][cpu-stats-url] | ![RAM Stats][ram-stats-url] | ![Storage Stats][storage-stats-url] | ![Network Stats][network-stats-url] | ![Exit][exit-url] |
| Welcome | HA Splash | CPU Stats | RAM Stats | Storage Stats | Network Stats | Exit Screen | Stats Screen* |
|-----------|-----------|-----------|-----------|---------------|---------------|---------------|---------------|
| ![Welcome][welcome-url] | ![Splash][splash-url] | ![CPU Stats][cpu-stats-url] | ![RAM Stats][ram-stats-url] | ![Storage Stats][storage-stats-url] | ![Network Stats][network-stats-url] | ![Exit][exit-url] | ![Stats][stats-url] |

*Stats screen designed for 128x64 displays only

## Custom Screen & Static Text Variables
Aswell as the above screens, you can configure a static custom screen which can be fixed or animated.
Expand All @@ -37,8 +39,9 @@ The following variables are supported
| {datetime} | Displays the current datetime based on the defined format specified in the ```DateTime_Format``` config option. |
| {hostname} | Displays the current hostname of the host device |
| {ip} | Displays the host device IP |
| {hassio.info.property} | Fetches a specified property from Home Assistants supervisor API (e.g. http://supervisor/os/info). You can state the namespace and property which will populate with the responding value. This must be fixed with hassio first, followed by the namespace (e.g. os, network etc), then the property e.g. hassio.os.latest_version will call http://supervisor/os/info and display the ```latest_version``` value. |
| {hassio.info.property}* | Fetches a specified property from Home Assistants supervisor API (e.g. http://supervisor/os/info). You can state the namespace and property which will populate with the responding value. This must be fixed with hassio first, followed by the namespace (e.g. os, network etc), then the property e.g. hassio.os.latest_version will call http://supervisor/os/info and display the ```latest_version``` value. |

*Some properties may not be available without setting up the API access in Home Assistant and inputting the supervisor token into configuration. This is not required for the basic screens.
<br>
<br>

Expand Down Expand Up @@ -75,9 +78,13 @@ Home Assistant variant of this build can be accessed from [HomeAssistant_Addons]

Hardware Setup
===============
You can use 0.91 Inch 128X32 I2C module, as long as it is registered on /dev/i2c-1 which is the Rasperry Pi default.
You can use 0.91 Inch 128X32 or 64 I2C module, as long as it is registered on /dev/i2c-1 which is the Rasperry Pi default.

I purchased this [MakerHawk I2C OLED Display Module I2C Screen Module 0.91" 128X32 I2C](https://www.amazon.co.uk/gp/product/B07BDFXFRK/ref=ppx_yo_dt_b_asin_title_o07_s00?ie=UTF8&psc=1)
Testing Hardware
<br>
[MakerHawk I2C OLED Display Module I2C Screen Module 0.91" 128X32 I2C](https://amzn.eu/d/cCNIybv)
<br>
[128x64 Pixels IIC 3.3V 5V White Character Screen Module](https://a.co/d/cTte8OO)

Pin setup:
--------------
Expand Down Expand Up @@ -183,6 +190,7 @@ python3 display.py --config /path/to/options.json

| Name | Type | Requirement | Description | Default |
| ---------------------| ------- | ------------ | -------------------------------------------------------| ------------------- |
| Screen_Size | string | **Required** | The size of the screen you want to display. Currently only 128x32 and 128x64 are supported| `32` |
| i2c_bus | int | **Required** | I2C bus number. /dev/i2c-[bus number] | `1` |
| Temperature_Unit | string | **Required** | Display the CPU temperature in C or F | `C` |
| Rotate | int | **Optional** | Rotates the screen by the number of degrees provided counter clockwise around its centre (e.g. 180 displays the screen upside down). | 0 |
Expand Down Expand Up @@ -285,4 +293,5 @@ MIT license, all text above must be included in any redistribution
[storage-stats-url]: /img/examples/storage.png?raw=true
[network-stats-url]: /img/examples/network.png?raw=true
[splash-url]: /img/examples/splash.png?raw=true
[exit-url]: /img/examples/static_goodbye.png?raw=true
[exit-url]: /img/examples/static_goodbye.png?raw=true
[stats-url]: /img/examples/stats.png?raw=true
11 changes: 7 additions & 4 deletions bin/Config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ class Config:
'storage',
'memory',
'cpu',
'static'
'static',
'stats'
]
HASSIO_DEPENDENT_SCREENS = [
'Splash'
Expand All @@ -33,7 +34,9 @@ class Config:
'scroll_amplitude': 'scroll_amplitude',
'datetime_format': 'datetime_format',
'welcome_screen_text': 'welcome_screen_text',
'rotate': 'rotate'
'rotate': 'rotate',
'supervizor_token': 'supervizor_token',
'screen_size': 'screen_size'
}

logger = logging.getLogger('Config')
Expand Down Expand Up @@ -110,14 +113,14 @@ def _init_display(self):
screenshot = False

rotate = self.get_option_value('rotate')
self.display = Display(busnum, screenshot, rotate)
self.display = Display(busnum, screenshot, rotate, self)

except Exception as e:
raise Exception("Could not create display. Check your i2c bus with 'ls /dev/i2c-*'.")

def _init_utils(self):
if self.is_hassio_supported:
self.utils = HassioUtils
self.utils = HassioUtils()
else:
self.utils = Utils

Expand Down
41 changes: 41 additions & 0 deletions bin/SSD1306.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,47 @@ def image(self, image):
def clear(self):
"""Clear contents of image buffer."""
self._buffer = [0]*(self.width*self._pages)

class SSD1306_128_64(SSD1306Base):
def __init__(self, busnum=1, i2c_address=SSD1306_I2C_ADDRESS):
# Call base class constructor.
super(SSD1306_128_64, self).__init__(128, 64, i2c_address, busnum)

def _initialize(self):
# 128x64 pixel specific initialization.
self.command(SSD1306_DISPLAYOFF) # 0xAE
self.command(SSD1306_SETDISPLAYCLOCKDIV) # 0xD5
self.command(0x80) # the suggested ratio 0x80
self.command(SSD1306_SETMULTIPLEX) # 0xA8
self.command(0x3F)
self.command(SSD1306_SETDISPLAYOFFSET) # 0xD3
self.command(0x0) # no offset
self.command(SSD1306_SETSTARTLINE | 0x0) # line #0
self.command(SSD1306_CHARGEPUMP) # 0x8D
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x10)
else:
self.command(0x14)
self.command(SSD1306_MEMORYMODE) # 0x20
self.command(0x00) # 0x0 act like ks0108
self.command(SSD1306_SEGREMAP | 0x1)
self.command(SSD1306_COMSCANDEC)
self.command(SSD1306_SETCOMPINS) # 0xDA
self.command(0x12)
self.command(SSD1306_SETCONTRAST) # 0x81
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x9F)
else:
self.command(0xCF)
self.command(SSD1306_SETPRECHARGE) # 0xd9
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x22)
else:
self.command(0xF1)
self.command(SSD1306_SETVCOMDETECT) # 0xDB
self.command(0x40)
self.command(SSD1306_DISPLAYALLON_RESUME) # 0xA4
self.command(SSD1306_NORMALDISPLAY) # 0xA6

class SSD1306_128_32(SSD1306Base):
def __init__(self, busnum=1, i2c_address=SSD1306_I2C_ADDRESS):
Expand Down
56 changes: 48 additions & 8 deletions bin/Screens.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@
import time
import logging
import textwrap
from bin.SSD1306 import SSD1306_128_32 as SSD1306
from bin.SSD1306 import SSD1306_128_64, SSD1306_128_32
from bin.Scroller import Scroller
from bin.Utils import Utils
from bin.Utils import Utils, HassioUtils


class Display:
DEFAULT_BUSNUM = 1
SCREENSHOT_PATH = "./img/examples/"

def __init__(self, busnum = None, screenshot = False, rotate = False):
def __init__(self, busnum = None, screenshot = False, rotate = False, config = None):
if not isinstance(busnum, int):
busnum = Display.DEFAULT_BUSNUM

self.display = SSD1306(busnum)
if config and config.get_option_value('screen_size') == '64':
self.display = SSD1306_128_64(busnum)
else:
self.display = SSD1306_128_32(busnum)
self.clear()
self.width = self.display.width
self.height = self.display.height
Expand Down Expand Up @@ -51,7 +55,7 @@ def capture_screenshot(self, name):
self.image.save(path)

class BaseScreen:
font_path = Utils.current_dir + "/fonts/DejaVuSans.ttf"
font_path = Utils.current_dir + "/fonts/PixelOperator.ttf"
font_bold_path = Utils.current_dir + "/fonts/DejaVuSans-Bold.ttf"
fonts = {}

Expand Down Expand Up @@ -242,14 +246,14 @@ def render(self):
Home Assistant screen.
If you're not using Home Assistant OS, disable this screen in the config
'''
os_info = self.utils.hassos_get_info('os/info')
os_info = self.utils.hassos_get_info(self, 'os/info')
os_version = os_info['data']['version']
os_upgrade = os_info['data']['update_available']

if (os_upgrade == True):
os_version = os_version + "*"

core_info = self.utils.hassos_get_info('core/info')
core_info = self.utils.hassos_get_info(self, 'core/info')
core_version = core_info['data']['version']
core_upgrade = os_info['data']['update_available']
if (core_upgrade == True):
Expand Down Expand Up @@ -375,5 +379,41 @@ def render(self):

self.capture_screenshot()

self.display.show()
time.sleep(self.duration)

class StatsScreen(BaseScreen):
def set_temp_unit(self, unit):
unit = str(unit).upper()
if unit in ['C', 'F']:
self.temp_unit = unit

def get_temp(self):
temp = float(Utils.shell_cmd("cat /sys/class/thermal/thermal_zone0/temp")) / 1000.00

if (hasattr(self, 'temp_unit') and self.temp_unit == 'F'):
temp = "%0.2f °F " % (temp * 9.0 / 5.0 + 32)
else:
temp = "%0.2f °C " % (temp)

return temp

def render(self):
self.display.prepare()

ipv4 = self.utils.get_ip()
core_stats = HassioUtils().hassos_get_info('core/stats', self.config)
cpu = core_stats["data"]['cpu_percent']
temp = self.get_temp()
mem = Utils.shell_cmd("free -m | awk 'NR==2{printf \"Mem: %s/%sMB %.2f%%\", $3,$2,$3*100/$2 }'")
storage = Utils.shell_cmd("df -h | awk '$NF==\"/\"{printf \"Disk: %d/%dGB %s\", $3,$2,$5}'")

self.display.draw.text((0, 0), "IP: " + ipv4, font=self.font(16), fill=255)
self.display.draw.text((0, 16), "CPU: " + str(cpu) + "LA", font=self.font(16), fill=255)
self.display.draw.text((80, 16), temp, font=self.font(16), fill=255)
self.display.draw.text((0, 32), mem, font=self.font(16), fill=255)
self.display.draw.text((0, 48), storage, font=self.font(16), fill=255)

self.capture_screenshot()
self.display.show()
time.sleep(self.duration)
9 changes: 7 additions & 2 deletions bin/Utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,18 @@ def slugify(text):

class HassioUtils(Utils):
@staticmethod
def hassos_get_info(type):
def hassos_get_info(type, config = None):
url = 'http://supervisor/{}'.format(type)
Utils.logger.info("Requesting data from '" + url + "'")
cmd = 'curl -sSL -H "Authorization: Bearer $SUPERVISOR_TOKEN" -H "Content-Type: application/json" ' + url
token = '$SUPERVISOR_TOKEN'
if config is not None:
if config.has_option('supervizor_token'):
token = config.get_option_value('supervizor_token')
cmd = 'curl -sSL -H "Authorization: Bearer ' + token +'" -H "content-type: application/json" ' + url
info = Utils.shell_cmd(cmd)
return json.loads(info)


@staticmethod
def get_hostname(opt = ""):
host_info = HassioUtils.hassos_get_info('host/info')
Expand Down
Binary file added fonts/PixelOperator.ttf
Binary file not shown.
Binary file added img/examples/stats.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
82 changes: 44 additions & 38 deletions options.json
Original file line number Diff line number Diff line change
@@ -1,40 +1,46 @@
{
"I2C_bus": 1,
"Temperature_Unit": "C",
"rotate": 0,
"DateTime_Format": "%d/%m/%Y %H:%M:%S",
"Default_Duration": 10,
"graceful_exit_text": "Exited at {datetime}",
"Scroll_Amplitude": 6,

"Show_Welcome_Screen": true,
"Welcome_Screen_Limit": 5,
"Welcome_Screen_Duration": null,
"Welcome_Screen_Text": "Welcome to {hostname}",

"Show_Splash_Screen": false,
"Splash_Screen_Limit": null,
"Splash_Screen_Duration": null,

"Show_Memory_Screen": true,
"Memory_Screen_Limit": null,
"Memory_Screen_Duration": 10,

"Show_Storage_Screen": true,
"Storage_Screen_Limit": null,
"Storage_Screen_Duration": 5,

"Show_Network_Screen": true,
"Network_Screen_Limit": null,
"Network_Screen_Duration": 5,

"Show_CPU_Screen": true,
"CPU_Screen_Limit": null,
"CPU_Screen_Duration": 5,

"Show_Static_Screen": false,
"Static_Screen_Limit": null,
"Static_Screen_Duration": 5,
"Static_Screen_Text": "Hassio verion {hassio.os.version} on {hostname} with IP {ip}",
"static_Screen_Text_NoScroll": false
"I2C_bus": 1,
"Temperature_Unit": "C",
"rotate": 0,
"DateTime_Format": "%d/%m/%Y %H:%M:%S",
"Default_Duration": 10,
"graceful_exit_text": "Exited at {datetime}",
"Scroll_Amplitude": 6,

"Show_Welcome_Screen": true,
"Welcome_Screen_Limit": 5,
"Welcome_Screen_Duration": null,
"Welcome_Screen_Text": "Welcome to {hostname}",

"Show_Splash_Screen": false,
"Splash_Screen_Limit": null,
"Splash_Screen_Duration": null,

"Show_Memory_Screen": true,
"Memory_Screen_Limit": null,
"Memory_Screen_Duration": 10,

"Show_Storage_Screen": true,
"Storage_Screen_Limit": null,
"Storage_Screen_Duration": 5,

"Show_Network_Screen": true,
"Network_Screen_Limit": null,
"Network_Screen_Duration": 5,

"Show_CPU_Screen": true,
"CPU_Screen_Limit": null,
"CPU_Screen_Duration": 5,

"Show_Static_Screen": false,
"Static_Screen_Limit": null,
"Static_Screen_Duration": 5,
"Static_Screen_Text": "Hassio verion {hassio.os.version} on {hostname} with IP {ip}",
"static_Screen_Text_NoScroll": false,

"Show_Stats_Screen": true,
"Stats_Screen_Limit": null,
"Stats_Screen_Duration": 5,

"Supervisor_token": null
}