Skip to content

Turing 5" hardware support (experimental) #253

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

Merged
merged 44 commits into from
Jun 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
c8cdd39
Cleaning port to 5 inch device - with some debug
alexwbaule Jun 1, 2023
ca605dd
simple-program.py to REVC, add example
alexwbaule Jun 1, 2023
b39171f
Working OK, missing images orientation
alexwbaule Jun 1, 2023
636622f
More itens
alexwbaule Jun 2, 2023
fd1d56e
All Orientation is OK, missing FLIP to device
alexwbaule Jun 2, 2023
4cde68b
All good, maybe the orientation is missing
alexwbaule Jun 2, 2023
d7573e9
Add 2 themes to 5 inch
alexwbaule Jun 2, 2023
a6a038f
Add a validation to get the correct theme
alexwbaule Jun 2, 2023
bade313
Update requirements.txt
alexwbaule Jun 2, 2023
50256b9
Removing saving images
alexwbaule Jun 2, 2023
7d18c75
Finished first implementation
alexwbaule Jun 2, 2023
30c207f
debug Hello
alexwbaule Jun 2, 2023
926231a
Theme minor changes
alexwbaule Jun 5, 2023
6eba53f
Removing all logger and debug from rev_c
alexwbaule Jun 5, 2023
0c8a3a1
Resolv conflict
alexwbaule Jun 5, 2023
98209af
Merge branch 'mathoudebine:main' into working/support-5-inches-device
alexwbaule Jun 5, 2023
699ced3
Removed numpy and opencv dependencies
arthurferrai Jun 5, 2023
011319f
Added log to notice extra reset
arthurferrai Jun 5, 2023
95d21ea
resetted eth config to make a clean PR
arthurferrai Jun 5, 2023
701a71e
Merge pull request #252 from alexwbaule/working/support-5-inches-device
mathoudebine Jun 5, 2023
634bbc2
Update README for 5" support
mathoudebine Jun 5, 2023
3095133
Revert config.yaml and requirements.txt to their original values from…
mathoudebine Jun 5, 2023
da0f397
Update config.yaml available values
mathoudebine Jun 5, 2023
7d7f548
Remove DISPLAY_WIDTH and DISPLAY_HEIGHT from config.yaml, it will be …
mathoudebine Jun 5, 2023
e85b0b2
Remove match/case to stay compatible with Python 3.7 to 3.9
mathoudebine Jun 5, 2023
8dfc516
Add Clear method for Rev. C, add copyrights, harmonize Rev. A hello m…
mathoudebine Jun 5, 2023
da6db2c
Rename theme files for consistency with other 3.5 inch themes
mathoudebine Jun 5, 2023
41c167c
Configurator will now display only compatible themes for the selected…
mathoudebine Jun 5, 2023
a568ff1
Get correct background for simple-program
mathoudebine Jun 5, 2023
a120cc8
Them now indicates if it is for 3.5" or 5" displays
mathoudebine Jun 5, 2023
e71307c
Check if theme is compatible with display model
mathoudebine Jun 5, 2023
59ff541
Update screenshot workflows for 5" themes
mathoudebine Jun 6, 2023
26f22b7
Add theme examples for 5inch displays
mathoudebine Jun 6, 2023
4390cb1
Fix theme check
mathoudebine Jun 6, 2023
595dea9
Add new theme NZXT_BLUR for 5" devices
mathoudebine Jun 6, 2023
91230d2
Update NZXT_BLUR theme
mathoudebine Jun 6, 2023
e6c0284
Add new themes for 5" devices
mathoudebine Jun 6, 2023
f5ea594
Update 5inchLandscape6Grid theme
mathoudebine Jun 7, 2023
fdfb10d
Rename theme
mathoudebine Jun 7, 2023
ac031bc
Add 2 new themes DragonBall and Purple Theme for 5inch displays
mathoudebine Jun 7, 2023
3e7433c
Fix screenshot workflows
mathoudebine Jun 7, 2023
2493fb1
Remove spaces in theme names
mathoudebine Jun 7, 2023
c155ac8
Screenshot workflows: run for 10 seconds
mathoudebine Jun 7, 2023
5ae19b1
Check only 1 theme with first & last supported Python versions
mathoudebine Jun 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 1 addition & 2 deletions .github/workflows/simple-program-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ on:
- 'releases/**'
pull_request:


jobs:
simple-program:

Expand All @@ -16,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.7", "3.11"]

steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/simple-program-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.7", "3.11"]

steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/simple-program-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.7", "3.11"]

steps:
- uses: actions/checkout@v3
Expand Down
5 changes: 2 additions & 3 deletions .github/workflows/system-monitor-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
theme: [ "3.5inchTheme2", "Cyberpunk", "Cyberpunk-net", "Landscape6Grid", "Terminal", "bash-dark-green", "bash-dark-green-gpu",
"LandscapeMagicBlue", "BigClock", "LandscapeEarth" ]
python-version: ["3.7", "3.11"]
theme: [ "3.5inchTheme2" ]

steps:
- uses: actions/checkout@v3
Expand Down
5 changes: 2 additions & 3 deletions .github/workflows/system-monitor-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
theme: [ "3.5inchTheme2", "Cyberpunk", "Cyberpunk-net", "Landscape6Grid", "Terminal", "bash-dark-green", "bash-dark-green-gpu",
"LandscapeMagicBlue", "BigClock", "LandscapeEarth" ]
python-version: ["3.7", "3.11"]
theme: [ "3.5inchTheme2" ]

steps:
- uses: actions/checkout@v3
Expand Down
5 changes: 2 additions & 3 deletions .github/workflows/system-monitor-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
theme: [ "3.5inchTheme2", "Cyberpunk", "Cyberpunk-net", "Landscape6Grid", "Terminal", "bash-dark-green", "bash-dark-green-gpu",
"LandscapeMagicBlue", "BigClock", "LandscapeEarth" ]
python-version: ["3.7", "3.11"]
theme: [ "3.5inchTheme2" ]

steps:
- uses: actions/checkout@v3
Expand Down
18 changes: 12 additions & 6 deletions .github/workflows/themes-screenshot-on-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- name: Set up Python 3.10
uses: actions/setup-python@v4
with:
python-version: "3.10"
python-version: "3.11"

- name: Install dependencies
run: |
Expand All @@ -22,9 +22,6 @@ jobs:

- name: Configure system monitor for screenshot
run: |
# For tests there is no real HW: use simulated LCD mode
sed -i "/REVISION:/c\ REVISION: SIMU" config.yaml

# Use static data
sed -i "/HW_SENSORS:/c\ HW_SENSORS: STATIC" config.yaml

Expand All @@ -40,9 +37,18 @@ jobs:
echo "Using theme $theme"
sed -i "/THEME:/c\ THEME: $theme" config.yaml

# Run system-monitor for 5 seconds
# For tests there is no real HW: use simulated LCD mode
# Check if theme is for 5"
orientation=$(grep 'DISPLAY_SIZE' $dir/theme.yaml | sed 's/ //g')
if [ "$orientation" == "DISPLAY_SIZE:5\"" ]; then
sed -i "/REVISION:/c\ REVISION: SIMU5" config.yaml
else
sed -i "/REVISION:/c\ REVISION: SIMU" config.yaml
fi

# Run system-monitor for 10 seconds
python3 main.py > output.log 2>&1 &
sleep 5
sleep 10
killall -9 python3

# Rename screen capture
Expand Down
24 changes: 13 additions & 11 deletions .github/workflows/themes-screenshot-on-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: Set up Python 3.10
uses: actions/setup-python@v4
with:
python-version: "3.10"
python-version: "3.11"

- name: Install dependencies
run: |
Expand All @@ -36,9 +36,6 @@ jobs:

- name: Configure system monitor for screenshot
run: |
# For tests there is no real HW: use simulated LCD mode
sed -i "/REVISION:/c\ REVISION: SIMU" config.yaml

# Use static data
sed -i "/HW_SENSORS:/c\ HW_SENSORS: STATIC" config.yaml

Expand All @@ -54,9 +51,18 @@ jobs:
echo "Using theme $theme"
sed -i "/THEME:/c\ THEME: $theme" config.yaml

# Run system-monitor for 5 seconds
# For tests there is no real HW: use simulated LCD mode
# Check if theme is for 5"
orientation=$(grep 'DISPLAY_SIZE' $dir/theme.yaml | sed 's/ //g')
if [ "$orientation" == "DISPLAY_SIZE:5\"" ]; then
sed -i "/REVISION:/c\ REVISION: SIMU5" config.yaml
else
sed -i "/REVISION:/c\ REVISION: SIMU" config.yaml
fi

# Run system-monitor for 10 seconds
python3 main.py > output.log 2>&1 &
sleep 5
sleep 10
killall -9 python3

# Copy screen capture on theme folder
Expand All @@ -73,8 +79,4 @@ jobs:

- name: Run if changes have been detected
if: steps.auto-commit-action.outputs.changes_detected == 'true'
run: echo "Changes!"

- name: Run if no changes have been detected
if: steps.auto-commit-action.outputs.changes_detected == 'false'
run: echo "No Changes!"
run: echo "Theme previews have changed!"
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,21 @@ This project is an open-source alternative software, NOT the USBMonitor.exe / Ex
![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black) ![Windows](https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logo=windows&logoColor=white) ![macOS](https://img.shields.io/badge/mac%20os-000000?style=for-the-badge&logo=apple&logoColor=white) ![Raspberry Pi](https://img.shields.io/badge/Raspberry%20Pi-A22846?style=for-the-badge&logo=Raspberry%20Pi&logoColor=white) ![Python](https://img.shields.io/badge/python-3670A0?style=for-the-badge&logo=python&logoColor=ffdd54) [![Licence](https://img.shields.io/github/license/mathoudebine/turing-smart-screen-python?style=for-the-badge)](./LICENSE)


A Python system monitor program and a library for 3.5" IPS USB-C (UART) displays.
A Python system monitor program and a library for **3.5" & 5" IPS USB-C (UART) displays.**

Supported operating systems : macOS, Windows, Linux (incl. Raspberry Pi), basically all OS that support Python 3.x
Supported operating systems : macOS, Windows, Linux (incl. Raspberry Pi), basically all OS that support Python 3.7+

Supported smart screens models:
| **Turing Smart Screen 3.5"** | **XuanFang 3.5"** |
|--------|----------|
| <img src="res/docs/turing.webp" height="300" /> | <img src="res/docs/xuanfang.webp" height="300" /> |
| also improperly called "revision A" by the resellers | revision B & flagship (with backplate & RGB LEDs) |

| **Turing Smart Screen 3.5"** | **XuanFang 3.5"** | **Turing Smart Screen 5"** |
|------------------------------------------------------|---------------------------------------------------|-----------------------------------------------------|
| <img src="res/docs/turing.webp" height="300" /> | <img src="res/docs/xuanfang.webp" height="300" /> | <img src="res/docs/turing5inch.png" height="300" /> |
| also improperly called "revision A" by the resellers | revision B & flagship (with backplate & RGB LEDs) | experimental support (no video or storage for now) |

### [> What is my smart screen model?](https://github.com/mathoudebine/turing-smart-screen-python/wiki/Hardware-revisions)

**Please note the Turing and the XuanFang screens are 2 different products** designed and produced by different companies, despite having a similar appearance. The communication protocol is also different.
This project support both products, including backplate RGB LEDs for available models!
**Please note the Turing and the XuanFang screens are different products** designed and produced by different companies, despite having a similar appearance. The communication protocol is also different.
This project support products from both manufacturers, including backplate RGB LEDs for available models!

If you haven't received your screen yet but want to start developing your theme now, you can use the [**"simulated LCD" mode!**](https://github.com/mathoudebine/turing-smart-screen-python/wiki/Simulated-display)

Expand All @@ -49,15 +50,15 @@ Some themes are already included for a quick start!

* Fully functional multi-OS code base (operates out of the box, tested on Windows, Linux & MacOS).
* Display configuration using GUI configuration wizard or `config.yaml` file: no Python code to edit.
* Support for all [3.5" smart screen models (Turing and XuanFang)](https://github.com/mathoudebine/turing-smart-screen-python/wiki/Hardware-revisions). Backplate RGB LEDs are also supported for available models!
* Support for [3.5" & 5" smart screen models (Turing and XuanFang)](https://github.com/mathoudebine/turing-smart-screen-python/wiki/Hardware-revisions). Backplate RGB LEDs are also supported for available models!
* Support [multiple hardware sensors and metrics (CPU/GPU usage, temperatures, memory, disks, etc)](https://github.com/mathoudebine/turing-smart-screen-python/wiki/System-monitor-:-themes#stats-entry) with configurable refresh intervals.
* Allow [creation of themes (see `res/themes`) with `theme.yaml` files using theme editor](https://github.com/mathoudebine/turing-smart-screen-python/wiki/System-monitor-:-themes) to be [shared with the community!](https://github.com/mathoudebine/turing-smart-screen-python/discussions/categories/themes)
* Easy to expand: additional code that pulls specific information can be written in a modular way without impacting existing code.
* Auto detect comm port. No longer need to hard set it, or if it changes on you then the config is wrong.
* Tray icon with Exit option, useful when the program is running in background

Screenshots from the latest version using included themes (click on the thumbnails to see a bigger preview):
<img src="res/docs/Theme3.5Inch.jpg" height="300" /> <img src="res/docs/ThemeTerminal.jpg" height="300" /> <img src="res/docs/ThemeCyberpunk.png" height="300" /> <img src="res/docs/ThemeBashDarkGreenGpu.png" height="300" /> <img src="res/docs/ThemeLandscape6Grid.jpg" width="300" /> <img src="res/docs/ThemeLandscapeMagicBlue.png" width="300" />
<img src="res/themes/3.5inchTheme2/preview.png" height="300" /> <img src="res/themes/Terminal/preview.png" height="300" /> <img src="res/themes/Cyberpunk-net/preview.png" height="300" /> <img src="res/themes/bash-dark-green-gpu/preview.png" height="300" /> <img src="res/themes/Landscape6Grid/preview.png" width="300" /> <img src="res/themes/LandscapeMagicBlue/preview.png" width="300" /> <img src="res/themes/LandscapeEarth/preview.png" width="300" />

### [> Themes creation/edition (using theme editor)](https://github.com/mathoudebine/turing-smart-screen-python/wiki/System-monitor-:-themes)
### [> Themes shared by the community:](https://github.com/mathoudebine/turing-smart-screen-python/discussions/categories/themes)
Expand Down
21 changes: 3 additions & 18 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,7 @@ config:
COM_PORT: "AUTO"

# Theme to use (located in res/themes)
# Available themes:
# - 3.5inchTheme2
# - Terminal
# - Landscape6Grid
# - LandscapeMagicBlue
# - Cyberpunk
# - Cyberpunk-net
# - bash-dark-green
# - bash-dark-green-gpu
# - BigClock
# Use the name of the folder as value
THEME: 3.5inchTheme2

# Hardware sensors reading
Expand All @@ -36,7 +27,8 @@ config:
WLO: "" # Wi-Fi Card

display:
# Display revision: A or B (for "flagship" version, use B) or SIMU for simulated LCD (image written in screencap.png)
# Display revision: A for Turing 3.5", B for Xuanfang 3.5" (inc. flagship), C for Turing 5"
# Use SIMU for 3.5" simulated LCD (image written in screencap.png) or SIMU5 for 5" simulated LCD
# To identify your revision: https://github.com/mathoudebine/turing-smart-screen-python/wiki/Hardware-revisions
REVISION: A

Expand All @@ -49,10 +41,3 @@ display:
# Set to true to reverse display orientation (landscape <-> reverse landscape, portrait <-> reverse portrait)
# Note: Display basic orientation (portrait or landscape) is defined by the theme you have selected
DISPLAY_REVERSE: false

# Display resolution in portrait orientation
# Do not use this setting to rotate display! The selected theme handles landscape/portrait orientation
DISPLAY_WIDTH: 320 # Do not change unless you have a good reason
DISPLAY_HEIGHT: 480 # Do not change unless you have a good reason


37 changes: 26 additions & 11 deletions configure.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# turing-smart-screen-python - a Python system monitor and library for 3.5" USB-C displays like Turing Smart Screen or XuanFang
# turing-smart-screen-python - a Python system monitor and library for USB-C displays like Turing Smart Screen or XuanFang
# https://github.com/mathoudebine/turing-smart-screen-python/

# Copyright (C) 2021-2023 Matthieu Houdebine (mathoudebine)
Expand Down Expand Up @@ -58,22 +58,29 @@
os._exit(0)

# Maps between config.yaml values and GUI description
revision_map = {'A': "Turing / rev. A", 'B': "XuanFang / rev. B / flagship", 'SIMU': "Simulated screen"}
revision_map = {'A': "Turing 3.5\" / rev. A", 'B': "XuanFang / rev. B / flagship", 'C': "Turing 5\"",
'SIMU': "Simulated 3.5\" screen", 'SIMU5': "Simulated 5\" screen"}
hw_lib_map = {"AUTO": "Automatic", "LHM": "LibreHardwareMonitor (admin.)", "PYTHON": "Python libraries",
"STUB": "Fake random data", "STATIC": "Fake static data"}
reverse_map = {False: "classic", True: "reverse"}
revision_size = {'A': '3.5"', 'B': '3.5"', 'C': '5"', 'SIMU': '3.5"', 'SIMU5': '5"'}


def get_themes():
def get_themes(revision: str):
themes = []
directory = 'res/themes/'
for filename in os.listdir('res/themes'):
f = os.path.join(directory, filename)
# checking if it is a file
if os.path.isdir(f):
theme = os.path.join(f, 'theme.yaml')
dir = os.path.join(directory, filename)
# checking if it is a directory
if os.path.isdir(dir):
# Check if a theme.yaml file exists
theme = os.path.join(dir, 'theme.yaml')
if os.path.isfile(theme):
themes.append(filename)
# Get display size from theme.yaml
with open(theme, "rt", encoding='utf8') as stream:
theme_data, ind, bsi = ruamel.yaml.util.load_yaml_guess_indent(stream)
if theme_data['display'].get("DISPLAY_SIZE", '3.5"') == revision_size[revision]:
themes.append(filename)
return sorted(themes, key=str.casefold)


Expand Down Expand Up @@ -112,7 +119,7 @@ def __init__(self):

self.theme_label = ttk.Label(self.window, text='Theme')
self.theme_label.place(x=320, y=35)
self.theme_cb = ttk.Combobox(self.window, values=get_themes(), state='readonly')
self.theme_cb = ttk.Combobox(self.window, state='readonly')
self.theme_cb.place(x=500, y=30, width=210)
self.theme_cb.bind('<<ComboboxSelected>>', self.on_theme_change)

Expand Down Expand Up @@ -283,7 +290,7 @@ def on_theme_change(self, e=None):
self.load_theme_preview()

def on_theme_editor_click(self):
subprocess.Popen(os.path.join(os.getcwd(), "theme-editor.py") + " " + self.theme_cb.get(), shell=True)
subprocess.Popen(os.path.join(os.getcwd(), "theme-editor.py") + " \"" + self.theme_cb.get() + "\"", shell=True)

def on_save_click(self):
self.save_config_values()
Expand All @@ -299,7 +306,8 @@ def on_brightness_change(self, e=None):

def on_model_change(self, e=None):
self.show_hide_brightness_warning()
if [k for k, v in revision_map.items() if v == self.model_cb.get()][0] == "SIMU":
revision = [k for k, v in revision_map.items() if v == self.model_cb.get()][0]
if revision == "SIMU" or revision == "SIMU5":
self.com_cb.configure(state="disabled", foreground="#C0C0C0")
self.orient_cb.configure(state="disabled", foreground="#C0C0C0")
self.brightness_slider.configure(state="disabled")
Expand All @@ -310,6 +318,13 @@ def on_model_change(self, e=None):
self.brightness_slider.configure(state="normal")
self.brightness_val_label.configure(foreground="#000")

themes = get_themes(revision)
self.theme_cb.config(values=themes)

if not self.theme_cb.get() in themes:
# The selected theme does not exist anymore / is not allowed for this screen model : select 1st theme avail.
self.theme_cb.set(themes[0])

def on_hwlib_change(self, e=None):
hwlib = [k for k, v in hw_lib_map.items() if v == self.hwlib_cb.get()][0]
if hwlib == "STUB" or hwlib == "STATIC":
Expand Down
17 changes: 16 additions & 1 deletion library/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# turing-smart-screen-python - a Python system monitor and library for 3.5" USB-C displays like Turing Smart Screen or XuanFang
# turing-smart-screen-python - a Python system monitor and library for USB-C displays like Turing Smart Screen or XuanFang
# https://github.com/mathoudebine/turing-smart-screen-python/

# Copyright (C) 2021-2023 Matthieu Houdebine (mathoudebine)
Expand Down Expand Up @@ -38,6 +38,9 @@ def load_yaml(configfile):
THEME_DEFAULT = load_yaml("res/themes/default.yaml")
THEME_DATA = None

# Matching between hardware revision and display size in inches
revision_size = {'A': '3.5"', 'B': '3.5"', 'C': '5"', 'SIMU': '3.5"', 'SIMU5': '5"'}


def copy_default(default, theme):
"""recursively supply default values into a dict of dicts of dicts ...."""
Expand Down Expand Up @@ -65,6 +68,18 @@ def load_theme():
copy_default(THEME_DEFAULT, THEME_DATA)


def check_theme_compatible():
global THEME_DATA
# Check if theme is compatible with hardware revision
if revision_size[CONFIG_DATA["display"]["REVISION"]] != THEME_DATA['display'].get("DISPLAY_SIZE", '3.5"'):
logger.error("The selected theme " + CONFIG_DATA['config'][
'THEME'] + " is not compatible with your display revision " + CONFIG_DATA["display"]["REVISION"])
try:
sys.exit(0)
except:
os._exit(0)


# Load theme on import
load_theme()

Expand Down
Loading