Compare commits
No commits in common. "master" and "2020.05.24.1" have entirely different histories.
master
...
2020.05.24
311 changed files with 358 additions and 3267 deletions
|
|
@ -1,4 +0,0 @@
|
||||||
.git
|
|
||||||
**/*.hash
|
|
||||||
**/src
|
|
||||||
public/
|
|
||||||
3
.gitattributes
vendored
3
.gitattributes
vendored
|
|
@ -1,5 +1,2 @@
|
||||||
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
||||||
*.jpg filter=lfs diff=lfs merge=lfs -text
|
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||||
*.png filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.xcf filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.webp filter=lfs diff=lfs merge=lfs -text
|
|
||||||
|
|
|
||||||
9
.gitmodules
vendored
9
.gitmodules
vendored
|
|
@ -1,6 +1,3 @@
|
||||||
[submodule "themes/hugo-coder"]
|
[submodule "themes/KeepIt"]
|
||||||
path = themes/hugo-coder
|
path = themes/KeepIt
|
||||||
url = git@github.com:W-Floyd/hugo-coder-iconify.git
|
url = https://github.com/Fastbyte01/KeepIt.git
|
||||||
[submodule "assets/MaterialDesign-SVG"]
|
|
||||||
path = assets/MaterialDesign-SVG
|
|
||||||
url = git@github.com:Templarian/MaterialDesign-SVG.git
|
|
||||||
|
|
|
||||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"cSpell.words": [
|
|
||||||
"Gluster"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
FROM hugomods/hugo:exts as hugo
|
|
||||||
COPY . /src
|
|
||||||
RUN hugo --minify
|
|
||||||
|
|
||||||
FROM nginx:alpine-slim
|
|
||||||
COPY --from=hugo /src/public /usr/share/nginx/html
|
|
||||||
COPY default.conf /etc/nginx/conf.d/default.conf
|
|
||||||
147
config.toml
147
config.toml
|
|
@ -2,89 +2,84 @@ baseURL = "//notmy.space/"
|
||||||
languageCode = "en"
|
languageCode = "en"
|
||||||
defaultContentLanguage = "en"
|
defaultContentLanguage = "en"
|
||||||
title = "William Floyd"
|
title = "William Floyd"
|
||||||
theme = "github.com/W-Floyd/hugo-coder-iconify"
|
theme = "KeepIt"
|
||||||
|
|
||||||
[module]
|
|
||||||
[[module.mounts]]
|
|
||||||
source = 'content'
|
|
||||||
target = 'content'
|
|
||||||
excludeFiles = "**/media/src"
|
|
||||||
[[module.imports]]
|
|
||||||
path = 'github.com/hugomods/icons/vendors/mdi'
|
|
||||||
|
|
||||||
paginate = 20
|
|
||||||
|
|
||||||
pygmentsStyle = "bw"
|
|
||||||
pygmentsCodeFences = true
|
|
||||||
pygmentsCodeFencesGuessSyntax = true
|
|
||||||
|
|
||||||
|
paginate = 12
|
||||||
enableEmoji = true
|
enableEmoji = true
|
||||||
|
googleAnalytics = "UA-63647911-2"
|
||||||
|
enableRobotsTXT = true
|
||||||
|
canonifyURLs = true
|
||||||
|
|
||||||
[permalinks]
|
[sitemap]
|
||||||
posts = "/:year/:month/:filename/"
|
changefreq = "daily"
|
||||||
|
filename = "sitemap.xml"
|
||||||
|
priority = 0.5
|
||||||
|
|
||||||
|
[blackfriday]
|
||||||
|
hrefTargetBlank = true
|
||||||
|
nofollowLinks = true
|
||||||
|
noreferrerLinks = true
|
||||||
|
|
||||||
|
[Permalinks]
|
||||||
|
posts = "/:year/:filename/"
|
||||||
|
|
||||||
|
[menu]
|
||||||
|
[[menu.main]]
|
||||||
|
name = "Blog"
|
||||||
|
url = "/posts/"
|
||||||
|
weight = 1
|
||||||
|
|
||||||
|
[[menu.main]]
|
||||||
|
name = "Projects"
|
||||||
|
url = "/projects/"
|
||||||
|
weight = 1
|
||||||
|
|
||||||
|
[[menu.main]]
|
||||||
|
name = "Categories"
|
||||||
|
url = "/categories/"
|
||||||
|
weight = 3
|
||||||
|
|
||||||
|
[[menu.main]]
|
||||||
|
name = "About"
|
||||||
|
url = "/about"
|
||||||
|
weight = 4
|
||||||
|
|
||||||
[params]
|
[params]
|
||||||
author = "William Floyd"
|
subtitle = "This is not MySpace, it's my space"
|
||||||
description = "William Floyd's personal website"
|
since = 2018
|
||||||
gravatar = "william.png2000@gmail.com"
|
author = "William Floyd"
|
||||||
dateFormat = "January 2, 2006"
|
home_mode = ""
|
||||||
since = 2018
|
|
||||||
colorScheme = "auto"
|
|
||||||
|
|
||||||
# If you want to implement a Content-Security-Policy, add this section
|
[params.gravatar]
|
||||||
scriptsrc = [
|
email = "william.png2000@gmail.com"
|
||||||
"'self'",
|
|
||||||
"'unsafe-inline'",
|
|
||||||
"https://cdn.jsdelivr.net/"
|
|
||||||
]
|
|
||||||
prefetchsrc = ["'self'"]
|
|
||||||
|
|
||||||
[taxonomies]
|
[params.social]
|
||||||
category = "categories"
|
GitHub = "W-Floyd"
|
||||||
series = "series"
|
Linkedin = "william-floyd-906674190"
|
||||||
tag = "tags"
|
Email = "william.png2000@gmail.com"
|
||||||
author = "authors"
|
Steam = "W-Floyd"
|
||||||
|
|
||||||
[[params.social]]
|
|
||||||
name = "Github"
|
|
||||||
icon = "github"
|
|
||||||
weight = 1
|
|
||||||
url = "https://github.com/W-Floyd/"
|
|
||||||
|
|
||||||
[[params.social]]
|
[params.share]
|
||||||
name = "LinkedIn"
|
|
||||||
icon = "linkedin"
|
|
||||||
weight = 2
|
|
||||||
url = "https://www.linkedin.com/in/william-floyd/"
|
|
||||||
|
|
||||||
[[params.social]]
|
[privacy]
|
||||||
name = "Email"
|
[privacy.disqus]
|
||||||
icon = "email"
|
disable = false
|
||||||
weight = 3
|
[privacy.googleAnalytics]
|
||||||
url = "mailto:contact@notmy.space"
|
anonymizeIP = true
|
||||||
|
disable = false
|
||||||
[[params.social]]
|
respectDoNotTrack = true
|
||||||
name = "RSS"
|
useSessionStorage = true
|
||||||
icon = "rss"
|
[privacy.instagram]
|
||||||
weight = 4
|
disable = false
|
||||||
url = "https://notmy.space/posts/index.xml"
|
simple = true
|
||||||
rel = "alternate"
|
[privacy.twitter]
|
||||||
type = "application/rss+xml"
|
disable = false
|
||||||
|
enableDNT = true
|
||||||
[languages.en]
|
simple = true
|
||||||
languageName = ":uk:"
|
[privacy.vimeo]
|
||||||
|
disable = false
|
||||||
[[languages.en.menu.main]]
|
simple = true
|
||||||
name = "About"
|
[privacy.youtube]
|
||||||
weight = 1
|
disable = false
|
||||||
url = "about/"
|
privacyEnhanced = true
|
||||||
|
|
||||||
[[languages.en.menu.main]]
|
|
||||||
name = "Resume"
|
|
||||||
weight = 2
|
|
||||||
url = "https://github.com/W-Floyd/misc-job/releases/download/release/William_Floyd.pdf"
|
|
||||||
|
|
||||||
[[languages.en.menu.main]]
|
|
||||||
name = "Posts"
|
|
||||||
weight = 3
|
|
||||||
url = "posts/"
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
---
|
||||||
title: "About"
|
title: "About"
|
||||||
date: "2023-12-15"
|
date: "2018-06-19"
|
||||||
---
|
---
|
||||||
|
|
||||||
I am William Floyd, this is my blog.
|
I am William Floyd, this is my blog.
|
||||||
|
|
||||||
I currently work as an Associate Project Manager at Engenious Design.
|
I am currently a Mechanical Engineering student at LeTourneau.
|
||||||
|
|
|
||||||
BIN
content/posts/clickbait/media/20190919_143530.webp
(Stored with Git LFS)
BIN
content/posts/clickbait/media/20190919_143530.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/clickbait/media/20190919_213947.webp
(Stored with Git LFS)
BIN
content/posts/clickbait/media/20190919_213947.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/clickbait/media/20190919_224711.webp
(Stored with Git LFS)
BIN
content/posts/clickbait/media/20190919_224711.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/clickbait/media/20191003_000323.webp
(Stored with Git LFS)
BIN
content/posts/clickbait/media/20191003_000323.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/clickbait/media/20191004_160107.webp
(Stored with Git LFS)
BIN
content/posts/clickbait/media/20191004_160107.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/clickbait/media/20191010_152148.webp
(Stored with Git LFS)
BIN
content/posts/clickbait/media/20191010_152148.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/clickbait/media/20191025_194859.webp
(Stored with Git LFS)
BIN
content/posts/clickbait/media/20191025_194859.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/clickbait/media/20191102_144426.webp
(Stored with Git LFS)
BIN
content/posts/clickbait/media/20191102_144426.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/clickbait/media/20191110_014623.webp
(Stored with Git LFS)
BIN
content/posts/clickbait/media/20191110_014623.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/clickbait/media/20191110_072012.webp
(Stored with Git LFS)
BIN
content/posts/clickbait/media/20191110_072012.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/clickbait/media/20191115_090125.webp
(Stored with Git LFS)
BIN
content/posts/clickbait/media/20191115_090125.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/clickbait/media/20191118_032416.webp
(Stored with Git LFS)
BIN
content/posts/clickbait/media/20191118_032416.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/clickbait/media/20191120_224129.webp
(Stored with Git LFS)
BIN
content/posts/clickbait/media/20191120_224129.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/clickbait/media/src/20190919_143530.jpg
(Stored with Git LFS)
BIN
content/posts/clickbait/media/src/20190919_143530.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
0fefa0a42b5ddc8f15e48e77cbadfa2fb8f25db06fc16e8e1afbc17f4e572505
|
|
||||||
BIN
content/posts/clickbait/media/src/20190919_213947.jpg
(Stored with Git LFS)
BIN
content/posts/clickbait/media/src/20190919_213947.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
524f410f6f8d7febe879205b516ff2c2de9c2b2613bb42da8a3103e196763f73
|
|
||||||
BIN
content/posts/clickbait/media/src/20190919_224711.jpg
(Stored with Git LFS)
BIN
content/posts/clickbait/media/src/20190919_224711.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
d0da1a5cca00a3d31b31769b9dc8f3c2ef5e69055d901fa8e6575ffa10d88f4a
|
|
||||||
BIN
content/posts/clickbait/media/src/20191003_000323.jpg
(Stored with Git LFS)
BIN
content/posts/clickbait/media/src/20191003_000323.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
2ad738c50c7640a8bdcdcf5d5feeb8118bbd90343596bcdf50ce00c37e631544
|
|
||||||
BIN
content/posts/clickbait/media/src/20191004_160107.jpg
(Stored with Git LFS)
BIN
content/posts/clickbait/media/src/20191004_160107.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
db26f30ae8b9406206394d6f08146d9a6c664da2f65ea30f7c0f493a2925c464
|
|
||||||
BIN
content/posts/clickbait/media/src/20191010_152148.jpg
(Stored with Git LFS)
BIN
content/posts/clickbait/media/src/20191010_152148.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
55c2322f7bba7c7d29eb3e28ce2269662f4b2016daa9b240fb236e6821b692c0
|
|
||||||
BIN
content/posts/clickbait/media/src/20191025_194859.jpg
(Stored with Git LFS)
BIN
content/posts/clickbait/media/src/20191025_194859.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
5051917955383bd82ec91d5b9082c3f1305782417ae6065e1cd1b80a220514f8
|
|
||||||
BIN
content/posts/clickbait/media/src/20191102_144426.jpg
(Stored with Git LFS)
BIN
content/posts/clickbait/media/src/20191102_144426.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
3c91e3d30fb5b38e543b11f1a21982954078f8dd5d182805f5182a59c6469041
|
|
||||||
BIN
content/posts/clickbait/media/src/20191110_014623.jpg
(Stored with Git LFS)
BIN
content/posts/clickbait/media/src/20191110_014623.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
d5e6e4baba1a9bc8858defe107721f069edf82e1c518326367a3d31f6ee8cedf
|
|
||||||
BIN
content/posts/clickbait/media/src/20191110_072012.jpg
(Stored with Git LFS)
BIN
content/posts/clickbait/media/src/20191110_072012.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
6c9c220b1d695d76e5e3a93832fe14c49b3765ac93e4d971ebe9a981d33f3ce0
|
|
||||||
BIN
content/posts/clickbait/media/src/20191115_090125.jpg
(Stored with Git LFS)
BIN
content/posts/clickbait/media/src/20191115_090125.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
24f6a28a746ea3d7213ee2ed2c40b22b756624331b24b486fd353b4abea576db
|
|
||||||
BIN
content/posts/clickbait/media/src/20191118_032416.jpg
(Stored with Git LFS)
BIN
content/posts/clickbait/media/src/20191118_032416.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
e67916e0e2ea755a40fe7859b7e46fdfc1698c24d5da7a4898b7785dbe8d1754
|
|
||||||
BIN
content/posts/clickbait/media/src/20191120_224129.jpg
(Stored with Git LFS)
BIN
content/posts/clickbait/media/src/20191120_224129.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
ef9c7801402a53ffc20bad84a5e5fb3587bbb2f2738d8d5df295d9e681070897
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
---
|
|
||||||
title: "DIY 'Smart' Coffee Maker"
|
|
||||||
date: "2020-07-05"
|
|
||||||
author: "William Floyd"
|
|
||||||
featured_image: "media/20200702_120123.webp"
|
|
||||||
categories: [
|
|
||||||
"Hardware",
|
|
||||||
"Electronics",
|
|
||||||
"Hacking"
|
|
||||||
]
|
|
||||||
tags: [
|
|
||||||
"Hobby",
|
|
||||||
"Home Assistant",
|
|
||||||
"Home Automation",
|
|
||||||
"ESPHome",
|
|
||||||
"Coffee",
|
|
||||||
"IOT"
|
|
||||||
]
|
|
||||||
---
|
|
||||||
|
|
||||||
I have recently (last couple months) been getting into home automation, specifically using Home Assistant.
|
|
||||||
I also wanted to get a coffee maker that used K-Cups.
|
|
||||||
Thus the goal was born - to get a coffee maker working with Home Assistant, such that I could automate it to fit my morning routine.
|
|
||||||
|
|
||||||
I first searched for new coffee makers, looking to see what the easiest ones would be to hack into and automate.
|
|
||||||
I got so far as adding a sleek new brand name model to my Amazon cart, but thankfully reason and frugality prevailed!
|
|
||||||
Instead, I opted to swing by the local Goodwill the next day, to see what secondhand options might be available.
|
|
||||||
As luck would have it, I found a no-frills, single button coffee maker for the princely sum of $4, and I was on my merry way.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
Later that evening, opening the black box up (thankfully I had the correct triangular screwdriver head), I found very easily hackable innards.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
The control board ([front](media/20200610_205847.webp), [back](media/20200610_205841.webp)) is very simple - two buttons in parallel, an LED, and a couple resistors.
|
|
||||||
I'm not quite sure what the logic is upstream (it un/latches on each press), but the buttons short to ground, and this is something I can work with.
|
|
||||||
The control board has wires for ground, signal, and 5V, in that order.
|
|
||||||
|
|
||||||
The ordeal of hacking into the thing, and my missteps trying to do so, is rather boring really, but it produced this:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
The brains I added was a Wemos D1 Mini (thanks, Aliexpress!), with a 3.3V<->5V converter between to sense when the coffee maker is on, and also turn on a mosfet to short the button to ground.
|
|
||||||
I initially had issues with the coffee maker turning on whenever I plugger the D1 Mini in, but a pulldown resistor solved this.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
I also opted for an external USB plug - this allows me to possibly reflash this without opening the enclosure, and also meant I did not need to splice into the mains line inside, which I was reluctant to do.
|
|
||||||
|
|
||||||
I initially flashed and programmed the coffee maker with Tasmota, but almost immediately opted to use ESPHome instead.
|
|
||||||
I like Tasmota and ESPHome, I was just able to get more fine control of the setup using ESPHome in this instance, especially concerning the button logic.
|
|
||||||
|
|
||||||
The (important) code is as follows:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
binary_sensor:
|
|
||||||
- platform: gpio
|
|
||||||
device_class: power
|
|
||||||
internal: true
|
|
||||||
id: power_on
|
|
||||||
pin: D2
|
|
||||||
|
|
||||||
switch:
|
|
||||||
- platform: gpio
|
|
||||||
pin: D1
|
|
||||||
id: relay
|
|
||||||
restore_mode: ALWAYS_OFF
|
|
||||||
- platform: template
|
|
||||||
name: "Coffee Maker"
|
|
||||||
icon: "mdi:coffee-maker"
|
|
||||||
lambda: |-
|
|
||||||
if (id(power_on).state) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
turn_on_action:
|
|
||||||
- switch.turn_on: relay
|
|
||||||
- delay: 50ms
|
|
||||||
- switch.turn_off: relay
|
|
||||||
turn_off_action:
|
|
||||||
- switch.turn_on: relay
|
|
||||||
- delay: 50ms
|
|
||||||
- switch.turn_off: relay
|
|
||||||
```
|
|
||||||
|
|
||||||
The beauty of the way in which I can detect signals and simulate a button press is that ESPHome senses when I use the physical button, and updates accordingly.
|
|
||||||
I can use the coffee maker absolutely as normal, the 'Smart' part of it is optional.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
The rest of my setup using this coffee maker is somewhat outside the scope of this post, but it includes using Tasker on my phone to run a 'Wakeup' automation on Home Assistant, which will - along with turning on my bed-head lamp, and switching my monitor on before putting it to sleep - check if I turned on a 'Morning Coffee' user input.
|
|
||||||
If I remembered to prep my coffee the night before, and turned on the switch, I will wake up to a hot cup of coffee (or, the sound of a coffee-in-progress).
|
|
||||||
I can also command Alexa and Google Assistant to do my bidding and make me a coffee.
|
|
||||||
At least someone does what I want...
|
|
||||||
|
|
||||||
Truly, we are living in the future.
|
|
||||||
BIN
content/posts/coffee/media/20200610_204856.webp
(Stored with Git LFS)
BIN
content/posts/coffee/media/20200610_204856.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/coffee/media/20200610_205351.webp
(Stored with Git LFS)
BIN
content/posts/coffee/media/20200610_205351.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/coffee/media/20200610_205841.webp
(Stored with Git LFS)
BIN
content/posts/coffee/media/20200610_205841.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/coffee/media/20200610_205847.webp
(Stored with Git LFS)
BIN
content/posts/coffee/media/20200610_205847.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/coffee/media/20200702_120123.webp
(Stored with Git LFS)
BIN
content/posts/coffee/media/20200702_120123.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/coffee/media/20200702_120302.webp
(Stored with Git LFS)
BIN
content/posts/coffee/media/20200702_120302.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/coffee/media/20200702_120309.webp
(Stored with Git LFS)
BIN
content/posts/coffee/media/20200702_120309.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/coffee/media/20200702_120328.webp
(Stored with Git LFS)
BIN
content/posts/coffee/media/20200702_120328.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/coffee/media/20200702_120334.webp
(Stored with Git LFS)
BIN
content/posts/coffee/media/20200702_120334.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/coffee/media/src/20200610_204856.jpg
(Stored with Git LFS)
BIN
content/posts/coffee/media/src/20200610_204856.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
372a99d5662d499adccdd71c8110b8d459612110babf4099bf4d5bdd7370e329
|
|
||||||
BIN
content/posts/coffee/media/src/20200610_205351.jpg
(Stored with Git LFS)
BIN
content/posts/coffee/media/src/20200610_205351.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
d48ea9d5b0cfd9291c110a1beb64be4d7f76f62644986b49a969bc10cce9a7ca
|
|
||||||
BIN
content/posts/coffee/media/src/20200610_205841.jpg
(Stored with Git LFS)
BIN
content/posts/coffee/media/src/20200610_205841.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
f53659511bd33ac8c51a8f1fae74d24551c270e8fc6870553ce4edc04add95f2
|
|
||||||
BIN
content/posts/coffee/media/src/20200610_205847.jpg
(Stored with Git LFS)
BIN
content/posts/coffee/media/src/20200610_205847.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
cf91c706dec4682dbddc5be255fe9e5793c61941c0284c3bb7dae9e0fb592e80
|
|
||||||
BIN
content/posts/coffee/media/src/20200702_120123.jpg
(Stored with Git LFS)
BIN
content/posts/coffee/media/src/20200702_120123.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
526b6f7d067f17a0b593482823cb88024a0ad1417f905e7b61f1077773acd7aa
|
|
||||||
BIN
content/posts/coffee/media/src/20200702_120302.jpg
(Stored with Git LFS)
BIN
content/posts/coffee/media/src/20200702_120302.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
c259c61a1fc9b3554f5e2c3ed4a0318deb92ea013b665a32534f90c4f5b6d897
|
|
||||||
BIN
content/posts/coffee/media/src/20200702_120309.jpg
(Stored with Git LFS)
BIN
content/posts/coffee/media/src/20200702_120309.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
14bfcaec0739089a4bbbf57c78ad3ca93286cf402c9a987fcb63a23bfffddbf5
|
|
||||||
BIN
content/posts/coffee/media/src/20200702_120328.jpg
(Stored with Git LFS)
BIN
content/posts/coffee/media/src/20200702_120328.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
4404536aaf5f70f51fad0e9298ecf1ead5b97e52a3557d482f89d23f7ccba1ce
|
|
||||||
BIN
content/posts/coffee/media/src/20200702_120334.jpg
(Stored with Git LFS)
BIN
content/posts/coffee/media/src/20200702_120334.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
1ee1ad4b87c3a581e97879eb627e6c478972b86a83232fbd66173390de34fb80
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
---
|
|
||||||
title: "Ghetto NAS Part 1"
|
|
||||||
date: "2023-08-29"
|
|
||||||
author: "William Floyd"
|
|
||||||
#featured_image: "media/IMG_20220126_225541.webp"
|
|
||||||
categories: [
|
|
||||||
"Sys Admin",
|
|
||||||
"Hardware",
|
|
||||||
"Software"
|
|
||||||
]
|
|
||||||
tags: [
|
|
||||||
"NAS",
|
|
||||||
"3D Printing",
|
|
||||||
"Gluster",
|
|
||||||
"Homelab"
|
|
||||||
]
|
|
||||||
series: ["Ghetto NAS"]
|
|
||||||
list: never
|
|
||||||
draft: true
|
|
||||||
---
|
|
||||||
|
|
||||||
This is an ongoing project to build a custom NAS on the most minimal budget possible.
|
|
||||||
|
|
||||||
# Use Case
|
|
||||||
|
|
||||||
Storing a large (30TB+) amount of infrequently accessed data that must still be immediately accessible (primarily Jellyfin, Nextcloud), with some level of safety.
|
|
||||||
|
|
||||||
Some details about my use case:
|
|
||||||
* There will be no external network access except via a single local client mounting the drive and sharing via ZeroTier
|
|
||||||
* There will be very few clients total
|
|
||||||
* Most data is replaceable, though inconveniently so (media may be reacquired / restored from backups)
|
|
||||||
* Neither latency nor throughput are very important
|
|
||||||
|
|
||||||
# Bill of Materials
|
|
||||||
|
|
||||||
| Quantity | Item | Per Unit Cost | Notes |
|
|
||||||
|----------|------------------------------|---------------|-----------------------------------------------------------------------------------------------|
|
|
||||||
| 3 | Dell Wyse 3030LT Thin Client | $11 | Ebay - Fairly common, though may run out eventually - other thin clients will no doubt appear |
|
|
||||||
| 3 | HGST 10TB He10 510 | $80 | Amazon / Ebay - Very common, can pick these up any day |
|
|
||||||
| 3 | ORICO 3.5in to USB enclosure | $25 | Amazon - Could use another, this is what I chose, does the job for me |
|
|
||||||
| 5 | Ethernet Cables | $2.5 | Amazon - $12.50 / 5 pack - Or whatever you have lying around |
|
|
||||||
| 1 | 8 Port Ethernet Switch | $13 | Amazon - Or whatever you have lying around |
|
|
||||||
| 0.5kg | PLA | $20 | For the NAS enclosure |
|
|
||||||
|
|
||||||
# Rationale
|
|
||||||
|
|
||||||
In order of importance for my use case: Price > Redundancy > Performance
|
|
||||||
|
|
||||||
## Hardware
|
|
||||||
|
|
||||||
### Thin Client
|
|
||||||
You simply cannot beat a whole working Linux box for $11.
|
|
||||||
With 2GB RAM, 4GB eMMC, 1 GbE, 1 USB 3 port, and a bundled power adapter, it does the bare minimum I need.
|
|
||||||
|
|
||||||
### HDD
|
|
||||||
Similarly, **used** enterprise drives deliver an amazing value.
|
|
||||||
For less than $9/TB or just over $10/TB with the enclosure, these drives are the cheapest possible way to get storage right now.
|
|
||||||
By using external enclosures we can also upgrade to larger drives in future, with minimal effort.
|
|
||||||
No shucking required!
|
|
||||||
|
|
||||||
I buy ones that have a 5 year warranty (spoiler - it's worth having!).
|
|
||||||
|
|
||||||
### Networking
|
|
||||||
1GbE is plenty enough for me, but if in future I need more speed, I can find a network switch with 10GbE uplink and scale horizontally a fair bit.
|
|
||||||
For now, a cheap unmanaged GbE switch will do just fine.
|
|
||||||
|
|
||||||
### UPS
|
|
||||||
Not 100% required, but the peace of mind in having the whole system on a UPS is worth it.
|
|
||||||
|
|
||||||
## Software
|
|
||||||
|
|
||||||
### Gluster
|
|
||||||
|
|
||||||
I am using Gluster to run my NAS cluster.
|
|
||||||
This is in large part due to its very modest hardware requirements, especially memory.
|
|
||||||
I can run my nodes with less than 50% memory utilization, and not fill my limited eMMC storage either.
|
|
||||||
It is very easy to work with, and offers flexible redundancy configurations.
|
|
||||||
|
|
||||||
#### Configuration
|
|
||||||
|
|
||||||
I am using Gluster with a dispersed volume, using the native client on my main server to mount the volume.
|
|
||||||
Dispersed lets me add clusters of bricks fairly easily, which suits my needs well.
|
|
||||||
|
|
||||||
### Netdata
|
|
||||||
|
|
||||||
This lets me know if/when drives get full, lets me know drive temperature from SMART data, and will email me if any hosts go offline.
|
|
||||||
|
|
||||||
# Experiences so far
|
|
||||||
|
|
||||||
I've been too busy to document the whole process, but I currently have a 2 x (2 + 1) array running (if I'd known I'd need 6 drives, I'd have done 1 x (4 + 2), but I didn't know at first).
|
|
||||||
Capacity is 60TB raw, 40TB usable.
|
|
||||||
|
|
||||||
## HDD Failures
|
|
||||||
|
|
||||||
That 5 year warranty I mentioned?
|
|
||||||
I've needed it twice so far - one drive died about 1 month in, and a second died 2 months in.
|
|
||||||
To their credit, the vendor got me a return package label within one business day each time, and refunded me as soon as the return package arrived.
|
|
||||||
For now, I continue to use these drives because the $/TB is so good, but in future I may upgrade to some larger drives in the same way to keep power costs down.
|
|
||||||
|
|
||||||
## Power Draw
|
|
||||||
|
|
||||||
6 x HDDs + 6 x Thin Clients + Network Switch + 12V Power Supply, draws about 40W at the wall under regular load (serving files).
|
|
||||||
|
|
@ -1,150 +0,0 @@
|
||||||
---
|
|
||||||
title: "Ghetto NAS Part 2"
|
|
||||||
date: "2024-02-16"
|
|
||||||
author: "William Floyd"
|
|
||||||
#featured_image: "media/IMG_20220126_225541.webp"
|
|
||||||
categories: [
|
|
||||||
"Sys Admin",
|
|
||||||
"Hardware",
|
|
||||||
"Software"
|
|
||||||
]
|
|
||||||
tags: [
|
|
||||||
"NAS",
|
|
||||||
"3D Printing",
|
|
||||||
"Gluster",
|
|
||||||
"Homelab"
|
|
||||||
]
|
|
||||||
series: ["Ghetto NAS"]
|
|
||||||
list: never
|
|
||||||
draft: true
|
|
||||||
---
|
|
||||||
|
|
||||||
I've been running the Gluster array from [part one](../ghetto-nas-part-01/) of this series for some months now, and am looking to improve my setup as I move to a new location and have new requirements.
|
|
||||||
|
|
||||||
# Existing Hardware
|
|
||||||
|
|
||||||
As a reminder/update, here is my existing hardware setup:
|
|
||||||
|
|
||||||
* Used HP Z440
|
|
||||||
* CPU
|
|
||||||
* Intel Xeon 1650-v4 (6 core, 12 thread, 3.6/4.0GHZ)
|
|
||||||
* Memory
|
|
||||||
* 128GB LRDDR4 @ 2133MT/s
|
|
||||||
* Storage
|
|
||||||
* 1TB NVME boot drive via PCIE adapter
|
|
||||||
* 8TB shucked WD Easystore (bought new)
|
|
||||||
* 14TB shucked WD Easystore (bought new)
|
|
||||||
* GPU
|
|
||||||
* Dell GTX 1080 (for gaming)
|
|
||||||
* Intel Arc A380 (for transcoding)
|
|
||||||
* 6 x Gluster Nodes
|
|
||||||
* Dell Wyse 3030 LT Thin Client
|
|
||||||
* CPU
|
|
||||||
* Intel Celerton N2807 (2 core, 0.5/2.167GHz)
|
|
||||||
* Memory
|
|
||||||
* 2GB Memory
|
|
||||||
* Storage
|
|
||||||
* 4GB MMC boot drive
|
|
||||||
* ORICO 3.5" SATA to USB 3.0 desktop adapter
|
|
||||||
* 10TB HGST He10 (refurbished, 5 year warranty)
|
|
||||||
* Generic 360W 12V power supply for Thin Clients and HDDs
|
|
||||||
* Generic Gigabit ethernet switch for all thin clients and workstation
|
|
||||||
|
|
||||||
# Requirements
|
|
||||||
|
|
||||||
Given my experiences with my existing solution, my new setup must (continue) to be:
|
|
||||||
* Able to support my existing 40TB usable space, scalable up to ~100TB
|
|
||||||
* Easily maintainable
|
|
||||||
* Performant
|
|
||||||
* Mostly quiet
|
|
||||||
* Cost effective
|
|
||||||
* Initial cost
|
|
||||||
* Cost over time (aiming for 5 year lifecycle)
|
|
||||||
* Power efficient
|
|
||||||
* Fewer Gluster nodes
|
|
||||||
* Large disks > many disks
|
|
||||||
* Reliable
|
|
||||||
* ECC Memory
|
|
||||||
* Redundant storage
|
|
||||||
|
|
||||||
This leaves me with the following requirements:
|
|
||||||
* Must support a `n x (4 + 2)` disk arrangement (~67% usable space with 2 disks of redundancy, especially as I plan to use used drives)
|
|
||||||
* Disks must be 10TB or larger
|
|
||||||
* Disks must be cheap
|
|
||||||
* Disks should have reasonable warranty
|
|
||||||
|
|
||||||
Additional observations/experience:
|
|
||||||
* The 4GB storage on the Dell Wyse 3030 LT nodes is difficult to work in. If the storage fills, it can result in a node failing to come online after a restart
|
|
||||||
* Network latency results in slow directory operations via Gluster
|
|
||||||
* The workstation is already well capable of handling this many drives, it makes more sense to connect them directly to the drives as it is their only client
|
|
||||||
|
|
||||||
With this in mind, I want to move away from multiple storage nodes and consolidate into a more unified storage system
|
|
||||||
|
|
||||||
# Options
|
|
||||||
|
|
||||||
## NAS
|
|
||||||
|
|
||||||
### Prebuilt
|
|
||||||
|
|
||||||
Easiest option, but not my ideal as I want to learn, and know my system wholely.
|
|
||||||
Hardware is too expensive, no expandability, so I'm not going to do it.
|
|
||||||
Good more many people's cases though.
|
|
||||||
|
|
||||||
### Custom built
|
|
||||||
|
|
||||||
Solid option, but too expensive - I already have a workstation, I don't want another desktop holding all the drives and not doing anything useful otherwise. More of a sunk cost issue than a failure of this option, I just can't justify redundant hardware like this. Also, power draw would be increased as I'd be adding a system, not replacing.
|
|
||||||
|
|
||||||
If I were to do this, these are some of the options I've looked at:
|
|
||||||
* Mini ITX motherboard
|
|
||||||
* [All in one](https://www.aliexpress.us/item/3256806141617147.html) ([alternative](https://www.aliexpress.us/item/3256806353828287.html)) - $125-$160 depending on spec
|
|
||||||
* 6 SATA ports, PCIE, 4x2.5GbE, NVME
|
|
||||||
* Power efficient (<10W TDP)
|
|
||||||
* No ECC, memory not included
|
|
||||||
* No brand support
|
|
||||||
* [Xeon Kit](https://www.aliexpress.us/item/3256805579918121.html) - ~$135
|
|
||||||
* 6(?) SATA ports, PCIE, 2x2.5GbE, NVME(?)
|
|
||||||
* Powerful, not power efficient (90W TDP)
|
|
||||||
* ECC memory included
|
|
||||||
* No brand support
|
|
||||||
* Cooler not included
|
|
||||||
* More of a replacement to my workstation
|
|
||||||
* [3D printed case](https://modcase.com.au/products/nas)
|
|
||||||
* NAS Case
|
|
||||||
* [Silverstone DS308B](https://www.silverstonetek.com/en/product/info/server-nas/DS380/)
|
|
||||||
* Too expensive ($200+)
|
|
||||||
* [Generic 8 bay ITX enclosure](https://www.amazon.com/KCMconmey-Internal-Compatible-Backplane-Enclosure/dp/B0BXKSS8YY/)
|
|
||||||
* Too expensive ($150)
|
|
||||||
* No brand support
|
|
||||||
* Leaves empty bays if expanding in 6 drive increments
|
|
||||||
|
|
||||||
Overall something I've strongly considered, mostly for space savings, but cost is keeping me away, as it's basically a whole new PC for each new node (unless I'm expanding somehow otherwise, which I could do via the workstation anyway).
|
|
||||||
|
|
||||||
## JBOD
|
|
||||||
|
|
||||||
Requires an external HBA/SATA expander from the workstation.
|
|
||||||
|
|
||||||
### Prebuilt (ex-Enterprise)
|
|
||||||
|
|
||||||
Strong option, moderately easy to set up.
|
|
||||||
Concerns are:
|
|
||||||
* Power draw
|
|
||||||
* Noise
|
|
||||||
* Need for rack mounting
|
|
||||||
* More bays than I need
|
|
||||||
|
|
||||||
If I were to do this (and I may do some day), I would probably get an EMC KTN-STL3, a 15 bay chassis.
|
|
||||||
|
|
||||||
### Custom built (from scratch)
|
|
||||||
|
|
||||||
Too much work, don't want to *need* to design my own PCB for this.
|
|
||||||
|
|
||||||
### Custom built (using ex-Enterprise parts)
|
|
||||||
|
|
||||||
A few options,
|
|
||||||
|
|
||||||
https://www.supermicro.com/manuals/other/BPN-SAS3-815TQ.pdf
|
|
||||||
|
|
||||||
# Physical layout
|
|
||||||
|
|
||||||
I had begun modelling and came close to 3D printing an all in one cluster enclosure for 3 clients and 3 drives that would include a power distribution board, fan controller with temperature sensor, and panel mounted Ethernet ports.
|
|
||||||
This was never finished, and as I look to
|
|
||||||
|
|
@ -1,184 +0,0 @@
|
||||||
---
|
|
||||||
title: "Ghetto NAS Part 1"
|
|
||||||
date: "2023-08-29"
|
|
||||||
author: "William Floyd"
|
|
||||||
#featured_image: "media/IMG_20220126_225541.webp"
|
|
||||||
categories: [
|
|
||||||
"Sys Admin",
|
|
||||||
"Hardware",
|
|
||||||
"Software"
|
|
||||||
]
|
|
||||||
tags: [
|
|
||||||
"NAS",
|
|
||||||
"3D Printing",
|
|
||||||
"Gluster"
|
|
||||||
]
|
|
||||||
series: ["Ghetto NAS"]
|
|
||||||
---
|
|
||||||
|
|
||||||
This is an ongoing project to build a custom NAS on the most minimal budget possible.
|
|
||||||
|
|
||||||
# Use Case
|
|
||||||
|
|
||||||
Storing a large (30TB+) amount of infrequently accessed data that must still be immediately accessible (primarily Jellyfin), with some level of safety.
|
|
||||||
|
|
||||||
Some details about my use case:
|
|
||||||
* There will be no external network access except via a single local client mounting the drive and sharing via ZeroTier
|
|
||||||
* There will be very few clients total
|
|
||||||
* Most data is replaceable, though inconveniently so (media may be reacquired / restored from backups)
|
|
||||||
* Neither latency nor throughput are very important
|
|
||||||
|
|
||||||
# Bill of Materials
|
|
||||||
|
|
||||||
| Quantity | Item | Per Unit Cost | Notes |
|
|
||||||
|----------|------------------------------|---------------|-----------------------------------------------------------------------------------------------|
|
|
||||||
| 3 | Dell Wyse 3030LT Thin Client | $11 | Ebay - Fairly common, though may run out eventually - other thin clients will no doubt appear |
|
|
||||||
| 3 | HGST 10TB He10 510 | $80 | Amazon / Ebay - Very common, can pick these up any day |
|
|
||||||
| 3 | ORICO 3.5in to USB enclosure | $25 | Amazon - Could use another, this is what I chose, does the job for me |
|
|
||||||
| 5 | Ethernet Cables | $2.5 | Amazon - $12.50 / 5 pack - Or whatever you have lying around |
|
|
||||||
| 1 | 8 Port Ethernet Switch | $13 | Amazon - Or whatever you have lying around |
|
|
||||||
| 0.5kg | PLA | $20 | For the NAS enclosure |
|
|
||||||
|
|
||||||
# Rationale
|
|
||||||
|
|
||||||
In order of importance for my use case: Price > Redundancy > Performance
|
|
||||||
|
|
||||||
## Hardware
|
|
||||||
|
|
||||||
### Thin Client
|
|
||||||
You simply cannot beat a whole working Linux box for $11.
|
|
||||||
With 2GB RAM, 4GB eMMC, 1 GbE, 1 USB 3 port, and a bundled power adapter, it does the bare minimum I need.
|
|
||||||
|
|
||||||
### HDD
|
|
||||||
Similarly, **used** enterprise drives deliver an amazing value.
|
|
||||||
For less than $9/TB or just over $10/TB with the enclosure, these drives are the cheapest possible way to get storage right now.
|
|
||||||
By using external enclosures we can also upgrade to larger drives in future, with minimal effort.
|
|
||||||
No shucking required!
|
|
||||||
|
|
||||||
I buy ones that have a 5 year warranty (spoiler - it's worth having!).
|
|
||||||
|
|
||||||
### Networking
|
|
||||||
1GbE is plenty enough for me, but if in future I need more speed, I can find a network switch with 10GbE uplink and scale horizontally a fair bit.
|
|
||||||
For now, a cheap unmanaged GbE switch will do just fine.
|
|
||||||
|
|
||||||
### UPS
|
|
||||||
Not 100% required, but the peace of mind in having the whole system on a UPS is worth it.
|
|
||||||
|
|
||||||
## Software
|
|
||||||
|
|
||||||
### Gluster
|
|
||||||
|
|
||||||
I am using Gluster to run my NAS cluster.
|
|
||||||
This is in large part due to its very modest hardware requirements, especially memory.
|
|
||||||
I can run my nodes with less than 50% memory utilization, and not fill my limited eMMC storage either.
|
|
||||||
It is very easy to work with, and offers flexible redundancy configurations.
|
|
||||||
|
|
||||||
#### Configuration
|
|
||||||
|
|
||||||
I am using Gluster with a dispersed volume, using the native client on my main server to mount the volume.
|
|
||||||
Dispersed lets me add clusters of bricks fairly easily, which suits my needs well.
|
|
||||||
|
|
||||||
### Netdata
|
|
||||||
|
|
||||||
This lets me know if/when drives get full, lets me know drive temperature from SMART data, and will email me if any hosts go offline.
|
|
||||||
|
|
||||||
# Experiences so far
|
|
||||||
|
|
||||||
I've been too busy to document the whole process, but I currently have a 2 x (2 + 1) array running (if I'd known I'd need 6 drives, I'd have done 1 x (4 + 2), but I didn't know at first).
|
|
||||||
Capacity is 60TB raw, 40TB usable.
|
|
||||||
|
|
||||||
## HDD Failures
|
|
||||||
|
|
||||||
That 5 year warranty I mentioned?
|
|
||||||
I've needed it twice so far - one drive died about 1 month in, and a second died 2 months in.
|
|
||||||
To their credit, the vendor got me a return package label within one business day each time, and refunded me as soon as the return package arrived.
|
|
||||||
For now, I continue to use these drives because the $/TB is so good, but in future I may upgrade to some larger drives in the same way to keep power costs down.
|
|
||||||
|
|
||||||
## Power Draw
|
|
||||||
|
|
||||||
6 x HDDs + 6 x Thin Clients + Network Switch + 12V Power Supply, draws about 40W at the wall under regular load (serving files).
|
|
||||||
|
|
||||||
# Topology
|
|
||||||
|
|
||||||
{{<mermaid>}}
|
|
||||||
%%{
|
|
||||||
init: {
|
|
||||||
'theme': 'base',
|
|
||||||
'themeVariables': {
|
|
||||||
'background': '#00000000',
|
|
||||||
'primaryColor': '#00000000',
|
|
||||||
'primaryTextColor': '#888888',
|
|
||||||
'secondaryColor': '#00000000',
|
|
||||||
'primaryBorderColor': '#888888',
|
|
||||||
'secondaryBorderColor': '#888888',
|
|
||||||
'secondaryTextColor': '#888888',
|
|
||||||
'tertiaryColor': '#00000000',
|
|
||||||
'tertiaryBorderColor': '#888888',
|
|
||||||
'tertiaryTextColor': '#888888',
|
|
||||||
'noteBkgColor': '#00000000',
|
|
||||||
'noteTextColor': '#888888',
|
|
||||||
'noteBorderColor': '#888888',
|
|
||||||
'lineColor': '#888888',
|
|
||||||
'textColor': '#888888',
|
|
||||||
'mainBkg': '#00000000',
|
|
||||||
'errorBkgColor': '#00000000',
|
|
||||||
'errorTextColor': '#888888'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}%%
|
|
||||||
graph TB
|
|
||||||
|
|
||||||
subgraph internet["Internet"]
|
|
||||||
me_away["Me when away from home"] & Friends & Family & Fiancé --- caddy
|
|
||||||
subgraph vps["Cloud VPS"]
|
|
||||||
caddy --- vps_zerotier["Zerotier"] & rss
|
|
||||||
subgraph vps_docker["Docker"]
|
|
||||||
caddy["Caddy"]
|
|
||||||
rss["FreshRSS"]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
vps_zerotier ---- zerotier
|
|
||||||
|
|
||||||
subgraph home["Home Network"]
|
|
||||||
|
|
||||||
z440 ---- me_home["Me at home"]
|
|
||||||
|
|
||||||
subgraph z440["Server (HP Z440)"]
|
|
||||||
|
|
||||||
zerotier["Zerotier"] --- jellyfin & arr & ha_zerotier
|
|
||||||
|
|
||||||
subgraph docker[Docker]
|
|
||||||
jellyfin["Jellyfin"]
|
|
||||||
arr["*arr Applications"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph vms["VMs"]
|
|
||||||
subgraph ha["Home Assistant"]
|
|
||||||
ha_zerotier["Zerotier"]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
jellyfin & arr --- gluster["Gluster mount"]
|
|
||||||
|
|
||||||
jellyfin & arr --- disk_internal["Internal Disks"]
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
ha ---- smart_home_devices["Smart Home Devices"]
|
|
||||||
|
|
||||||
gluster --- switch["GbE Network Switch"] --- client1 & client2 & client3 & client4 & client5 & client6
|
|
||||||
|
|
||||||
client1[1.wyse] --"USB"--- disk1[Disk 1]
|
|
||||||
client2[2.wyse] --"USB"--- disk2[Disk 2]
|
|
||||||
client3[3.wyse] --"USB"--- disk3[Disk 3]
|
|
||||||
client4[4.wyse] --"USB"--- disk4[Disk 4]
|
|
||||||
client5[5.wyse] --"USB"--- disk5[Disk 5]
|
|
||||||
client6[6.wyse] --"USB"--- disk6[Disk 6]
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{{</mermaid>}}
|
|
||||||
|
|
@ -1,320 +0,0 @@
|
||||||
---
|
|
||||||
title: "Home Automation at School"
|
|
||||||
date: "2021-04-21"
|
|
||||||
author: "William Floyd"
|
|
||||||
featured_image: "media/20200813_023018.webp"
|
|
||||||
categories: [
|
|
||||||
"Software"
|
|
||||||
]
|
|
||||||
tags: [
|
|
||||||
"School",
|
|
||||||
"Home Assistant",
|
|
||||||
"Home Automation",
|
|
||||||
"ESPHome",
|
|
||||||
"IOT"
|
|
||||||
]
|
|
||||||
---
|
|
||||||
|
|
||||||
I love home automation - I've spent far longer writing automation routines and hacking together my own devices and programs than I have ever saved by doing so, and that's perfectly fine.
|
|
||||||
One unique aspect of my setup, however, is that I cannot control the network I must use - in my Uni dorm, I am not allowed to run my own router, and so all IoT devices must connect to the school wireless network.
|
|
||||||
There abound dozens of Google Home devices, Chromecasts, and so on, all accessible on the same network - but not from the wired connection that my server/desktop uses.
|
|
||||||
|
|
||||||
Here then is my solution: MQTT everything I can.
|
|
||||||
From my lights, so light sensor, to coffee maker to desktop software, I bounce it through a MQTT server hosted on a VPS.
|
|
||||||
I use Home Assistant, so automatic discovery is easy on most things, especially ESPHome.
|
|
||||||
In fact, I disabled direct Home Assistant connectivity entirely on these devices, which works well enough for me to live with.
|
|
||||||
|
|
||||||
# But how to flash?
|
|
||||||
|
|
||||||
Given that my Home Assistant instance isn't even on the same network as the IoT devices, how do I update the firmware?
|
|
||||||
Using my laptop, I can connect to the wireless network that they reside on, and using IP address sensors reported by these devices, flash them directly without needing local discovery.
|
|
||||||
In fact, I can easily automate this for myself using a couple scripts and a minimal number of hard-coded values:
|
|
||||||
|
|
||||||
`hassio.sh`
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
export HASS_SERVER=https://<server_url>:443
|
|
||||||
export HASS_TOKEN='<HA_Token>'
|
|
||||||
|
|
||||||
hass-cli ${@}
|
|
||||||
|
|
||||||
exit
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
`lamps.sh`
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
declare -A aa
|
|
||||||
|
|
||||||
aa["gosund_lb1_1.yaml"]="sensor.desk_lamp_ip_address"
|
|
||||||
aa["gosund_lb1_2.yaml"]="sensor.bed_lamp_ip_address"
|
|
||||||
aa["gosund_lb1_3.yaml"]="sensor.floor_lamp_girlfriend_ip_address"
|
|
||||||
aa["gosund_lb1_4.yaml"]="sensor.desk_lamp_girlfriend_ip_address"
|
|
||||||
aa["gosund_lb1_5.yaml"]="sensor.floor_lamp_ip_address"
|
|
||||||
|
|
||||||
__flash() {
|
|
||||||
|
|
||||||
__config="${1}"
|
|
||||||
__entity_name="${aa[${__config}]}"
|
|
||||||
echo "Getting ${__config} IP..."
|
|
||||||
|
|
||||||
__ip="$(
|
|
||||||
./hassio.sh -o yaml state get \
|
|
||||||
"${__entity_name}" |
|
|
||||||
grep -E '^ *state' | sed -e 's/.* //'
|
|
||||||
)"
|
|
||||||
|
|
||||||
echo "IP: ${__ip}"
|
|
||||||
|
|
||||||
if [ "${__ip}" == 'unavailable' ]; then
|
|
||||||
echo 'Ignoring...'
|
|
||||||
else
|
|
||||||
echo "Flashing..."
|
|
||||||
./esphome.sh "${__config}" run --upload-port="${__ip}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ "${#}" -gt 0 ]; then
|
|
||||||
until [ "${#}" == 0 ]; do
|
|
||||||
__flash "${1}"
|
|
||||||
shift
|
|
||||||
done
|
|
||||||
else
|
|
||||||
for __config in ${!aa[@]}; do
|
|
||||||
__flash "${__config}"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit
|
|
||||||
```
|
|
||||||
|
|
||||||
this allows me to mostly painlessly flash my devices, though truth be told there is little need.
|
|
||||||
|
|
||||||
# Custom software
|
|
||||||
|
|
||||||
I developed for myself a tool in Golang to help tie more of my devices together.
|
|
||||||
It is rather uncreatively/cryptically named `ha-mqtt-iot` - that is, "Home Assistant MQTT Internet of Things".
|
|
||||||
I may rename this some day, but why bother.
|
|
||||||
It is similar to IOTLink (which is Windows only), and HASS Workstation Service - they are great projects, but this one is mine, even if it is poorly written.
|
|
||||||
|
|
||||||
The gist of the software is that most (all?) device types supported by Home Assistant may be implemented using a selection of user defined commands.
|
|
||||||
The most prominent examples in my case are in order to enable/disable dark mode on my desktop.
|
|
||||||
I automate this according to ambient light in my room, to better match the aesthetic I want.
|
|
||||||
Additionally, I can use it to turn my desktop monitor off, without resorting to using a relay outlet, and even change the color temperature of my system.
|
|
||||||
The script I use for this looks like the following:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
__monitor_i2c='dev:/dev/i2c-3'
|
|
||||||
__monitor_dpms='0xd6'
|
|
||||||
__monitor_brightness='0x10'
|
|
||||||
__monitor_standby='4'
|
|
||||||
__monitor_off='5'
|
|
||||||
__monitor_on='1'
|
|
||||||
|
|
||||||
__unknown() {
|
|
||||||
echo "Unknown ${1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
f2i() {
|
|
||||||
awk 'BEGIN{for (i=1; i<ARGC;i++)
|
|
||||||
printf "%.0f\n", ARGV[i]}' "$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
com="${1}"
|
|
||||||
arg="${2}"
|
|
||||||
|
|
||||||
case "${com}" in
|
|
||||||
"command")
|
|
||||||
case "${arg}" in
|
|
||||||
"ON")
|
|
||||||
xset dpms force on
|
|
||||||
;;
|
|
||||||
"OFF")
|
|
||||||
(
|
|
||||||
#xset dpms force off
|
|
||||||
#sleep 0.5s
|
|
||||||
#ddccontrol -r "${__monitor_dpms}" -w "${__monitor_standby}" "${__monitor_i2c}" -f
|
|
||||||
#sleep 2s
|
|
||||||
ddccontrol -r "${__monitor_dpms}" -w "${__monitor_off}" "${__monitor_i2c}" -f
|
|
||||||
) &
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
__unknown "${arg}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
"command-state")
|
|
||||||
echo -n "$(xset q | grep 'Monitor is' | sed -e 's/.* //' | tr '[:lower:]' '[:upper:]')"
|
|
||||||
;;
|
|
||||||
"color-temp")
|
|
||||||
v="$(f2i "$(bc -l <<<"1000000/${arg}")")"
|
|
||||||
./scripts/run-in-user-session.sh gsettings set org.gnome.settings-daemon.plugins.color night-light-temperature "${v}"
|
|
||||||
;;
|
|
||||||
"color-temp-state")
|
|
||||||
v="$(./scripts/run-in-user-session.sh gsettings get org.gnome.settings-daemon.plugins.color night-light-temperature)"
|
|
||||||
echo -n "$(f2i "$(bc -l <<<"1000000/${v/* /}")")"
|
|
||||||
;;
|
|
||||||
"brightness")
|
|
||||||
ddccontrol -r "${__monitor_brightness}" "${__monitor_i2c}" -w "${arg}"
|
|
||||||
;;
|
|
||||||
"brightness-state")
|
|
||||||
echo -n "$(ddccontrol 2>/dev/null -r "${__monitor_brightness}" "${__monitor_i2c}" | tail -n 1 | grep -o '/[0-9]*/100' | sed -e 's|^/||' -e 's|/.*||')"
|
|
||||||
;;
|
|
||||||
|
|
||||||
*)
|
|
||||||
__unknown "root command ${com}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
exit
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the display doesn't respond to being turned back on, so this is somewhat incomplete, but it's good enough for my needs.
|
|
||||||
The corresponding portion of the config for `ha-mqtt-iot` looks like the following:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"lights": [
|
|
||||||
{
|
|
||||||
"info": {
|
|
||||||
"name": "Desktop Monitor",
|
|
||||||
"id": "monitor"
|
|
||||||
},
|
|
||||||
"command": [
|
|
||||||
"./scripts/monitor.sh",
|
|
||||||
"command"
|
|
||||||
],
|
|
||||||
"command_state": [
|
|
||||||
"./scripts/monitor.sh",
|
|
||||||
"command-state"
|
|
||||||
],
|
|
||||||
"command_color_temp": [
|
|
||||||
"./scripts/monitor.sh",
|
|
||||||
"color-temp"
|
|
||||||
],
|
|
||||||
"command_color_temp_state": [
|
|
||||||
"./scripts/monitor.sh",
|
|
||||||
"color-temp-state"
|
|
||||||
],
|
|
||||||
"command_brightness": [
|
|
||||||
"./scripts/monitor.sh",
|
|
||||||
"brightness"
|
|
||||||
],
|
|
||||||
"command_brightness_state": [
|
|
||||||
"./scripts/monitor.sh",
|
|
||||||
"brightness-state"
|
|
||||||
],
|
|
||||||
"brightness_scale": 100,
|
|
||||||
"update_interval": 5
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Pretty simple.
|
|
||||||
This makes custom system sensors trivial.
|
|
||||||
For example, to show my system IP, I use the following:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"sensors": [
|
|
||||||
{
|
|
||||||
"info": {
|
|
||||||
"name": "IP Address Desktop Solus",
|
|
||||||
"id": "ip-address-desktop-solus",
|
|
||||||
"icon": "mdi:ip-network"
|
|
||||||
},
|
|
||||||
"command_state": [
|
|
||||||
"/bin/bash",
|
|
||||||
"-c",
|
|
||||||
"ip -j address show eno1 | jq -r '.[0].addr_info[0].local'"
|
|
||||||
],
|
|
||||||
"update_interval": 10
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Some common use cases are built in as well.
|
|
||||||
Currently, this includes laptop displays (as lights) and batteries (as sensors), as well as Crypto prices (though the CoinGecko Golang library).
|
|
||||||
These are really easy to call.
|
|
||||||
An exhaustive example is quite short:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"builtin": {
|
|
||||||
"prefix": "Name Prefix ",
|
|
||||||
"backlight": {
|
|
||||||
"enable": true,
|
|
||||||
"temperature": false,
|
|
||||||
"range": {
|
|
||||||
"minimum": 0.025,
|
|
||||||
"maximum": 0.95
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"battery": {
|
|
||||||
"enable": true
|
|
||||||
},
|
|
||||||
"crypto": [
|
|
||||||
{
|
|
||||||
"coin_name": "dogecoin",
|
|
||||||
"currency_name": "usd",
|
|
||||||
"update_interval": 1,
|
|
||||||
"icon": "mdi:currency-usd"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This lets me tailor my setup to each machine I'm using, while still enjoying the benefits of Home Assistant MQTT Discovery.
|
|
||||||
The primary limitation at present is the inability to signal to `ha-mqtt-iot` from another process - it can only poll for changes.
|
|
||||||
This will be addressed one day, when it is important for my own needs.
|
|
||||||
|
|
||||||
# How to host?
|
|
||||||
|
|
||||||
But the question is now, how do I access my HomeAssistant instance if it's also hosted at school?
|
|
||||||
I most certainly don't have a public IP, so in comes AutoSSH.
|
|
||||||
I'm not sure which is the best one at this stage, but refer to [this](https://github.com/psallandre/hassio-addons-autossh) repo and check the various forks of the parent project.
|
|
||||||
|
|
||||||
I have configured on my VPS a docker image that accepts reverse SSH tunnelling, authorized only to the key of the HA addon.
|
|
||||||
From my `docker-compose.yml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
homeassistant:
|
|
||||||
image: "docker.io/panubo/sshd"
|
|
||||||
container_name: homeassistant
|
|
||||||
environment:
|
|
||||||
- TCP_FORWARDING=true
|
|
||||||
- GATEWAY_PORTS=true
|
|
||||||
- SSH_ENABLE_ROOT=true
|
|
||||||
- DISABLE_SFTP=true
|
|
||||||
volumes:
|
|
||||||
- "./hassio/authorized_keys:/root/.ssh/authorized_keys:ro"
|
|
||||||
- ./docker-config/hassio/data/:/data
|
|
||||||
- ./docker-config/hassio/keys/:/etc/ssh/keys
|
|
||||||
ports:
|
|
||||||
- "<MY_PORT>:22"
|
|
||||||
restart: unless-stopped
|
|
||||||
hostname: "homeassistant"
|
|
||||||
```
|
|
||||||
|
|
||||||
This is then reverse proxied to using Caddy, to expose the website on a subdomain of a website.
|
|
||||||
From my `Caddyfile`:
|
|
||||||
|
|
||||||
```dockerfile
|
|
||||||
<MY_SUBDOMAIN>.{$MY_DOMAIN} {
|
|
||||||
reverse_proxy homeassistant:8123
|
|
||||||
encode gzip
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Pretty simple, but not without some hiccups now and again - I occasionally have to restart the sshd docker on my VPS if something goes wrong with HomeAssistant.
|
|
||||||
|
|
||||||
# Bonus: Android tie in
|
|
||||||
|
|
||||||
I use Sleep As Android to track my sleeping patterns, and as my alarm clock.
|
|
||||||
Using Tasker, I can run an action when I begin sleep tracking, which (using a HomeAssistant plugin for Tasker) can call a script on my HomeAssistant instance to turn off my lights (only if I'm home, of course).
|
|
||||||
Similarly, it turns on my bedhead light when my alarm goes off in the morning, and could optionally make me coffee...
|
|
||||||
BIN
content/posts/home-automation-at-school/media/20200813_023018.webp
(Stored with Git LFS)
BIN
content/posts/home-automation-at-school/media/20200813_023018.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/home-automation-at-school/media/src/20200813_023018.jpg
(Stored with Git LFS)
BIN
content/posts/home-automation-at-school/media/src/20200813_023018.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
70c3922ade2b73a7a949d1015e738013903342a5860c7dae3525ff8b436e424c
|
|
||||||
BIN
content/posts/midiMixerV1/media/handwired/20190811_004928.webp
(Stored with Git LFS)
BIN
content/posts/midiMixerV1/media/handwired/20190811_004928.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/midiMixerV1/media/handwired/20190811_005806.webp
(Stored with Git LFS)
BIN
content/posts/midiMixerV1/media/handwired/20190811_005806.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/midiMixerV1/media/handwired/20190811_195900.webp
(Stored with Git LFS)
BIN
content/posts/midiMixerV1/media/handwired/20190811_195900.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/midiMixerV1/media/handwired/20190811_200447.webp
(Stored with Git LFS)
BIN
content/posts/midiMixerV1/media/handwired/20190811_200447.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/midiMixerV1/media/handwired/20190812_175132.webp
(Stored with Git LFS)
BIN
content/posts/midiMixerV1/media/handwired/20190812_175132.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/midiMixerV1/media/handwired/20190812_181606.webp
(Stored with Git LFS)
BIN
content/posts/midiMixerV1/media/handwired/20190812_181606.webp
(Stored with Git LFS)
Binary file not shown.
BIN
content/posts/midiMixerV1/media/src/handwired/20190811_004928.jpg
(Stored with Git LFS)
BIN
content/posts/midiMixerV1/media/src/handwired/20190811_004928.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
80bebd4e51d1aaabde9777b00e87e415e73f4b5fe6a0acbf5f675fad2bab3ea9
|
|
||||||
BIN
content/posts/midiMixerV1/media/src/handwired/20190811_005806.jpg
(Stored with Git LFS)
BIN
content/posts/midiMixerV1/media/src/handwired/20190811_005806.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
4e34713687f375c61df8d71d6e49adfb643f3f5df0ba8a919273e8b66b48e6f8
|
|
||||||
BIN
content/posts/midiMixerV1/media/src/handwired/20190811_195900.jpg
(Stored with Git LFS)
BIN
content/posts/midiMixerV1/media/src/handwired/20190811_195900.jpg
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
b1f7de3b630c01de2c09bf0355706ea6801b429651ff73fa8f1d69d979106481
|
|
||||||
BIN
content/posts/midiMixerV1/media/src/handwired/20190811_200447.jpg
(Stored with Git LFS)
BIN
content/posts/midiMixerV1/media/src/handwired/20190811_200447.jpg
(Stored with Git LFS)
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue