Compare commits
110 Commits
9417e711a9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
380d8f8e48
|
|||
|
07a297f818
|
|||
|
d7224b038b
|
|||
|
fc62219db7
|
|||
|
2ebd97c345
|
|||
|
270e86bfd0
|
|||
|
7305e3a35b
|
|||
|
aabbd8286f
|
|||
|
37f7d442a1
|
|||
|
3ff805fa39
|
|||
|
1ae62e70ed
|
|||
|
91f4687c07
|
|||
|
dc2df62d04
|
|||
|
b75aac76c2
|
|||
|
5161dced6e
|
|||
|
d9ed144578
|
|||
|
5516f9530b
|
|||
|
621be95870
|
|||
|
b526901546
|
|||
|
b328081b59
|
|||
|
113b859927
|
|||
|
57ff005186
|
|||
|
7ccedb9768
|
|||
|
ef527abef4
|
|||
|
75f4aaebf1
|
|||
|
1396e09227
|
|||
|
cbe8c4a369
|
|||
|
2f88c75655
|
|||
|
0f4b73720c
|
|||
|
b97f41eb70
|
|||
|
6df02e8dff
|
|||
|
57ae6b7e72
|
|||
|
e3ba1759c4
|
|||
|
af70d1d396
|
|||
|
5b474c7190
|
|||
|
d94cd01008
|
|||
|
afb27c512c
|
|||
|
a500c8a572
|
|||
|
c5748d81da
|
|||
|
b38390029f
|
|||
|
b116ea73ec
|
|||
|
920aeef7f3
|
|||
|
9038962f29
|
|||
|
3fed164193
|
|||
|
487e03c0bd
|
|||
|
cf0a7373d4
|
|||
|
e0adee5362
|
|||
|
8f3e624925
|
|||
| e1e551c5cc | |||
| 23d3949421 | |||
| 714dd32ff6 | |||
| 8035fa38dc | |||
| b91cc1adc3 | |||
| 4fe56de990 | |||
| 9ef631b266 | |||
| 8c39f749c7 | |||
| 1361c726d9 | |||
| 1879158b6c | |||
| 7b9968762a | |||
| 250ffeb266 | |||
| de6c1941c5 | |||
| 9bc09a4b98 | |||
| 79377b3653 | |||
| d44bca3f2b | |||
| 660735f0ae | |||
| 6dfd30e175 | |||
| 0e5250d84d | |||
| 556149c583 | |||
| 72e13f53aa | |||
| e9c68abeb9 | |||
| 69e8e89e72 | |||
| 85e74541c2 | |||
| cb66fb6195 | |||
| 8d98cd06fa | |||
| a85627b3b2 | |||
| f046e6edc2 | |||
| a32f055ede | |||
| 0c6509cc17 | |||
| 82b60c086c | |||
| 999869cab6 | |||
| 548cdc8b87 | |||
| 4832b283bb | |||
| 9e83048248 | |||
| f2d684fa7c | |||
| 7980bfb381 | |||
| 20690c48e5 | |||
| ca582333f1 | |||
| dae4063f25 | |||
| 5184c84d50 | |||
| 3f3a03ee05 | |||
| 22c1d635c6 | |||
| 5512c266eb | |||
| de8b827cfb | |||
| 7b93f740ec | |||
| b3e4a45996 | |||
| ab2b033c54 | |||
|
|
c2fa408c1e | ||
|
|
a469444811 | ||
| ed2088d0dc | |||
| 7099e72d6f | |||
| d44773389e | |||
| 03e959c215 | |||
| b45bcd802e | |||
| 38b81fda9a | |||
| 960e91f911 | |||
| 4723ffb13d | |||
| ef9104c796 | |||
| 6e393d90ee | |||
| 9acff25d43 | |||
| 3752f9da61 |
30
.gitea/workflows/caddy.yaml
Normal file
30
.gitea/workflows/caddy.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Podman DDNS Image
|
||||
run-name: Build and Push the Custom Caddy Image with Route53 DNS Certbot
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- active/podman_caddy/**
|
||||
- .gitea/workflows/caddy.yaml
|
||||
schedule:
|
||||
- cron: '@daily'
|
||||
jobs:
|
||||
build-and-push-ddns:
|
||||
runs-on: ubuntu-latest
|
||||
if: gitea.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
- name: Login to Gitea Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: gitea.reeseapps.com
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
- name: Build and push Docker image
|
||||
uses: https://github.com/docker/build-push-action@v5
|
||||
with:
|
||||
context: ${{ gitea.workspace }}/active/podman_caddy
|
||||
file: ${{ gitea.workspace }}/active/podman_caddy/Containerfile
|
||||
push: true
|
||||
tags: "gitea.reeseapps.com/services/caddy:latest,gitea.reeseapps.com/services/caddy:${{gitea.sha}}"
|
||||
no-cache: true
|
||||
30
.gitea/workflows/ddns.yaml
Normal file
30
.gitea/workflows/ddns.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Podman DDNS Image
|
||||
run-name: Build and Push the Podman DDNS Image
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- active/podman_ddns/**
|
||||
- .gitea/workflows/ddns.yaml
|
||||
schedule:
|
||||
- cron: '@daily'
|
||||
jobs:
|
||||
build-and-push-ddns:
|
||||
runs-on: ubuntu-latest
|
||||
if: gitea.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
- name: Login to Gitea Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: gitea.reeseapps.com
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
- name: Build and push Docker image
|
||||
uses: https://github.com/docker/build-push-action@v5
|
||||
with:
|
||||
context: ${{ gitea.workspace }}/active/podman_ddns
|
||||
file: ${{ gitea.workspace }}/active/podman_ddns/Containerfile
|
||||
push: true
|
||||
tags: "gitea.reeseapps.com/services/ddns:latest,gitea.reeseapps.com/services/ddns:${{gitea.sha}}"
|
||||
no-cache: true
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -2,4 +2,12 @@ secrets/
|
||||
venv/
|
||||
tmp/
|
||||
Unsorted/
|
||||
volumes/
|
||||
volumes/
|
||||
__pycache__/
|
||||
.pytest_cache/
|
||||
.venv/
|
||||
.mypy_cache
|
||||
TODO.md
|
||||
eicar.com
|
||||
*.pp
|
||||
*.mod
|
||||
17
.vscode/code_oss_extensions.txt
vendored
Normal file
17
.vscode/code_oss_extensions.txt
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
charliermarsh.ruff
|
||||
eamodio.gitlens
|
||||
franneck94.vscode-python-config
|
||||
franneck94.vscode-python-dev-extension-pack
|
||||
ms-pyright.pyright
|
||||
ms-python.debugpy
|
||||
ms-python.mypy-type-checker
|
||||
ms-python.python
|
||||
ms-python.vscode-python-envs
|
||||
njpwerner.autodocstring
|
||||
njqdev.vscode-python-typehint
|
||||
redhat.vscode-yaml
|
||||
stkb.rewrap
|
||||
streetsidesoftware.code-spell-checker
|
||||
tamasfe.even-better-toml
|
||||
vue.volar
|
||||
yzhang.markdown-all-in-one
|
||||
86
.vscode/settings.json
vendored
Normal file
86
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"[css]": {
|
||||
"editor.suggest.insertMode": "replace",
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"[django-html]": {
|
||||
"editor.insertSpaces": true,
|
||||
"editor.quickSuggestions": {
|
||||
"comments": true,
|
||||
"other": true,
|
||||
"strings": true
|
||||
},
|
||||
"editor.suggest.insertMode": "replace",
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"[dockercompose]": {
|
||||
"breadcrumbs.showConstants": true,
|
||||
"editor.quickSuggestions": {
|
||||
"comments": false,
|
||||
"other": true,
|
||||
"strings": true
|
||||
},
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"[helm]": {
|
||||
"editor.insertSpaces": true,
|
||||
"editor.tabSize": 2,
|
||||
"rewrap.autoWrap.enabled": true,
|
||||
"rewrap.wholeComment": true,
|
||||
"rewrap.wrappingColumn": 73
|
||||
},
|
||||
"[html]": {
|
||||
"editor.insertSpaces": true,
|
||||
"editor.suggest.insertMode": "replace",
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.maxTokenizationLineLength": 2500,
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"[markdown]": {
|
||||
"editor.defaultFormatter": "yzhang.markdown-all-in-one",
|
||||
"editor.quickSuggestions": {
|
||||
"comments": "off",
|
||||
"other": "off",
|
||||
"strings": "off"
|
||||
},
|
||||
"editor.tabSize": 4,
|
||||
"editor.wordWrap": "off"
|
||||
},
|
||||
"[python]": {
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "always"
|
||||
},
|
||||
"editor.defaultFormatter": "charliermarsh.ruff",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnType": true
|
||||
},
|
||||
"[shellscript]": {
|
||||
"editor.tabSize": 2,
|
||||
"files.eol": "\n"
|
||||
},
|
||||
"[terraform]": {
|
||||
"editor.insertSpaces": true,
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.maxTokenizationLineLength": 2500,
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"[vue]": {
|
||||
"editor.insertSpaces": true,
|
||||
"editor.tabSize": 2,
|
||||
"gitlens.codeLens.scopes": [
|
||||
"document"
|
||||
]
|
||||
},
|
||||
"[yaml]": {
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"cSpell.userWords": [
|
||||
"Kubernetes",
|
||||
"clamd",
|
||||
"rtype"
|
||||
],
|
||||
}
|
||||
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
@@ -6,7 +6,7 @@
|
||||
{
|
||||
"label": "Build arch-toolbox",
|
||||
"type": "shell",
|
||||
"command": "./infrastructure/graduated/distoolbox/arch-build.sh",
|
||||
"command": "./active/software_distoolbox/arch-build.sh",
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
|
||||
23
.vscode/vscode.md
vendored
23
.vscode/vscode.md
vendored
@@ -7,7 +7,8 @@
|
||||
- [Fonts](#fonts)
|
||||
- [Navigation](#navigation)
|
||||
- [Extensions](#extensions)
|
||||
- [Continue](#continue)
|
||||
- [Continue](#continue)
|
||||
- [Pylance Type Checking](#pylance-type-checking)
|
||||
|
||||
## Debugpy Snippet
|
||||
|
||||
@@ -117,18 +118,10 @@ To install that list of extensions run:
|
||||
cat vscode_extensions.txt | xargs -L 1 code --install-extension
|
||||
```
|
||||
|
||||
## Continue
|
||||
### Continue
|
||||
|
||||
```json
|
||||
{
|
||||
"models": [
|
||||
{
|
||||
"title": "qwen2.5-coder:32b",
|
||||
"provider": "ollama",
|
||||
"apiBase": "https://ollama.example.com",
|
||||
"apiKey": "...",
|
||||
"model": "qwen2.5-coder:32b"
|
||||
}
|
||||
],
|
||||
...
|
||||
```
|
||||
Continue -> Settings -> Help -> Quickstart
|
||||
|
||||
### Pylance Type Checking
|
||||
|
||||
Settings -> `python.analysis.typeChecking`
|
||||
308
README.md
308
README.md
@@ -1,81 +1,272 @@
|
||||
# Homelab
|
||||
|
||||
A project to store homelab stuff.
|
||||
Welcome to my homelab!
|
||||
|
||||
Just here for the Arch distoolbox?
|
||||
This repo is an in-flux collection of my personal notes, docs, and tutorials of
|
||||
things I find interesting and self-host.
|
||||
|
||||
[Arch Distoolbox](infrastructure/graduated/distoolbox/distoolbox.md)
|
||||
Take a look around!
|
||||
|
||||

|
||||
- "Active" projects (/active) are in use today and generally fall into these
|
||||
categories:
|
||||
- `aws_` is for aws notes
|
||||
- `device_` is for hardware
|
||||
- `kubernetes_` is for helm charts or other kubernetes hosted software
|
||||
- `os_` is for operating system setup guides and notes
|
||||
- `podman_` is for containerized projects
|
||||
- `software_` is for cli tools, projects without a specific way to host them,
|
||||
or other misfits
|
||||
|
||||
All active projects will have a markdown file named after the project. This is
|
||||
for quick access via shortcuts like `ctrl + p` in vscode. For example, I want
|
||||
to check my notes for `virsh` so I would type `ctrl + p` "virsh" to open
|
||||
"virsh.md".
|
||||
|
||||
"Retired" projects (/retired) is a graveyard of things I didn't want to delete.
|
||||
|
||||
"Template" projects (/templates) are quick templates for creating new active
|
||||
projects with sane defaults.
|
||||
|
||||
I keep my GPG and SSH keys in `keys` if you want to add those to your keyring
|
||||
or give me access to your servers.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Homelab](#homelab)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Fun Facts](#fun-facts)
|
||||
- [Keyboard Shortcuts](#keyboard-shortcuts)
|
||||
- [inputrc](#inputrc)
|
||||
- ["find ." shortcuts](#find--shortcuts)
|
||||
- [tmux](#tmux)
|
||||
- [bash](#bash)
|
||||
- [SSH Setup](#ssh-setup)
|
||||
- [Git GPG Commit Signing](#git-gpg-commit-signing)
|
||||
- [Important Dates and Times](#important-dates-and-times)
|
||||
- [Project Lifecycle](#project-lifecycle)
|
||||
- [Supported Projects](#supported-projects)
|
||||
- [Graduation Requirements](#graduation-requirements)
|
||||
- [Project Types](#project-types)
|
||||
- [Active Project Requirements](#active-project-requirements)
|
||||
- [Retirement Requirements](#retirement-requirements)
|
||||
- [Project Structure](#project-structure)
|
||||
- [Creating a Project](#creating-a-project)
|
||||
- [Order of Operations](#order-of-operations)
|
||||
|
||||
## Fun Facts
|
||||
|
||||
### Keyboard Shortcuts
|
||||
|
||||
On linux, <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>u</kbd>, then, while holding
|
||||
<kbd>ctrl</kbd>+<kbd>shift</kbd>, typing <kbd>b</kbd>+<kbd>0</kbd> will type a ° (degree) symbol. Also you
|
||||
can enter any unicode symbol this way.
|
||||
<kbd>ctrl</kbd>+<kbd>shift</kbd>, typing <kbd>b</kbd>+<kbd>0</kbd> will type a
|
||||
° (degree) symbol. Also you can enter any unicode symbol this way.
|
||||
|
||||
In vim: `esc + o` will take you to the end of a file and insert a new line.
|
||||
|
||||
### inputrc
|
||||
|
||||
Add this to your `~/.inputrc` to allow ctrl + backspace to delete whole words.
|
||||
|
||||
```bash
|
||||
"\C-h": backward-kill-word
|
||||
```
|
||||
|
||||
### "find ." shortcuts
|
||||
|
||||
```bash
|
||||
# Change file mode for a bunch of directories
|
||||
find . -type d -exec chmod 755 {} \;
|
||||
```
|
||||
|
||||
### tmux
|
||||
|
||||
- Vertical: ctrl + b + "
|
||||
- Horizontal: ctrl + b + %
|
||||
- Event Horizontal Distribution: ctrl + b + alt + 1
|
||||
- Even Vertical Distribution: ctrl + b + alt + 2
|
||||
- Swap pane order: ctrl + b + : -> swap-pane -t 0
|
||||
|
||||
### bash
|
||||
|
||||
<https://tecadmin.net/bash-special-variables/>
|
||||
|
||||
Here are some handy references for default bash variables
|
||||
|
||||
```text
|
||||
$0 – The name of the script being executed.
|
||||
$1-$9 – The first nine command-line arguments.
|
||||
$# – The number of command-line arguments.
|
||||
$* – All command-line arguments as a single string.
|
||||
$@ – All command-line arguments as an array.
|
||||
$? – The exit status of the last executed command.
|
||||
$$ – The process ID of the current shell.
|
||||
$! – The process ID of the last background command.
|
||||
$- – Shows the current shell options or flags.
|
||||
```
|
||||
|
||||
And here are the meanings of the shell options
|
||||
|
||||
```text
|
||||
h – Remember the location of commands as they are looked up
|
||||
i – Interactive shell
|
||||
m – Job control is enabled
|
||||
B – Brace expansion is enabled
|
||||
H – History substitution is enabled
|
||||
```
|
||||
|
||||
So to check if you are in an interactive shell:
|
||||
|
||||
```bash
|
||||
[ $- == *i* ]] && Some command here
|
||||
```
|
||||
|
||||
## SSH Setup
|
||||
|
||||
Generate a key (password protect it!)
|
||||
|
||||
```bash
|
||||
# Pick one of the below key types
|
||||
# ed25519
|
||||
ssh-keygen -C ssh@ducoterra.net -t ed25519
|
||||
# rsa 4096
|
||||
ssh-keygen -C ssh@ducoterra.net -t rsa -b 4096
|
||||
|
||||
# Inspect a key
|
||||
ssh-keygen -l -f ~/.ssh/id_rsa
|
||||
|
||||
# Change the password
|
||||
ssh-keygen -p -f ~/.ssh/id_rsa
|
||||
```
|
||||
|
||||
In your ~/.ssh/config, add the following line to set the default key
|
||||
|
||||
```conf
|
||||
IdentityFile ~/.foo/identity
|
||||
```
|
||||
|
||||
Then add a host to your local computer
|
||||
|
||||
```bash
|
||||
Host <hostname>
|
||||
Hostname <host.something.com or IP address>
|
||||
User <remote user>
|
||||
Port <remote port>
|
||||
```
|
||||
|
||||
And copy the key to a remote computer
|
||||
|
||||
```bash
|
||||
# Copy the generated key to the server using password auth. Assumes password auth enabled.
|
||||
ssh-copy-id -f -i ~/.ssh/id_ed25519 ${REMOTE_USER}@${REMOTE_HOST}
|
||||
|
||||
# Log into the server with your key
|
||||
ssh -i ${KEY_NAME} ${REMOTE_HOST}
|
||||
# Copy authorized_keys to root
|
||||
sudo mkdir -p /root/.ssh
|
||||
sudo cp ~/.ssh/authorized_keys /root/.ssh/authorized_keys
|
||||
exit
|
||||
|
||||
# login and disable password auth
|
||||
ssh ${REMOTE_HOST}
|
||||
mkdir -p /etc/ssh/sshd_config.d
|
||||
echo "PasswordAuthentication no" > /etc/ssh/sshd_config.d/01-prohibit-password.conf
|
||||
systemctl restart sshd
|
||||
|
||||
# OPTIONAL: Disable sudo password
|
||||
echo '%wheel ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/01-nopasswd-wheel
|
||||
|
||||
exit
|
||||
|
||||
# Test if you can SSH with a password
|
||||
ssh -o PubkeyAuthentication=no ducoterra@${SSH_HOST}.reeselink.com
|
||||
|
||||
# Test that you can log into the server with ssh config
|
||||
ssh $SSH_HOST
|
||||
```
|
||||
|
||||
## Git GPG Commit Signing
|
||||
|
||||
1. Use `gpg --list-key 'git@ducoterra.net'` to find your key
|
||||
2. Use `git config --global user.signingkey 0A46826A...` to set the signing key
|
||||
3. Use `gpg --export -a 'git@ducoterra.net'` to export the key to copy into Gitea/Github/Gitlab
|
||||
|
||||
Now you can sign commits with `git commit -S`.
|
||||
|
||||
Alternatively, you can sign every commit by default with `git config --global commit.gpgsign true`.
|
||||
|
||||
You can verify a commit with `git verify-commit e1e551c`. If the commit is
|
||||
signed you'll see an output. If not, nothing will show.
|
||||
|
||||
## Important Dates and Times
|
||||
|
||||
| Time | Day | Description |
|
||||
| ----- | -------- | ---------------------------------- |
|
||||
| 00:00 | All | Automated builds |
|
||||
| 00:00 | All | NAS Snapshots |
|
||||
| 02:00 | All | Backups |
|
||||
| 04:00 | All | Bare Metal Server Security Updates |
|
||||
| 05:00 | All | VM Server Security Updates |
|
||||
| 05:00 | All | Unifi Protect Firmware Updates |
|
||||
| 06:00 | All | Unifi Network Firmware Updates |
|
||||
| 06:00 | Saturday | Truenas Disk Scrub |
|
||||
|
||||
## Project Lifecycle
|
||||
|
||||
Projects will fall into one of the three following categories:
|
||||
Projects will either be `active` or `retired`.
|
||||
|
||||
1. Incubating
|
||||
2. Graduated
|
||||
3. Retired
|
||||
Active projects are being actively developed. They are in-use, stable, and
|
||||
production ready. Active projects should meet and track the [active project
|
||||
requirements](#active-project-requirements)
|
||||
|
||||
Incubating projects are experimental or prototypal. They're being actively developed and aren't
|
||||
ready for production deployment. These projects may appear and disappear without warning and are not
|
||||
stable. There is no minimum requirement for a project to be in incubation.
|
||||
Retired projects are no longer in use or recommended. They are kept for
|
||||
reference. Retired projects must meet the [retirement
|
||||
requirements](#retirement-requirements)
|
||||
|
||||
Graduated projects are in-use, stable, and production ready. They met the [graduation
|
||||
requirements](#graduation-requirements) and are actively maintained.
|
||||
You'll notice that most of the active projects have scripts or examples that
|
||||
use the `active` path as part of their install process. When moved outside the
|
||||
`active` directory their scripts and examples break. This is intentional. If
|
||||
you want a retired project to work again, bring it back to the active
|
||||
directory.
|
||||
|
||||
Retired projects are no longer in use or recommended. They are kept for reference. Retired projects
|
||||
must meet the [retirement requirements](#retirement-requirements)
|
||||
## Project Types
|
||||
|
||||
## Supported Projects
|
||||
All projects will be prefixed with one of the following categories:
|
||||
|
||||
All projects will fall into one of the following categories:
|
||||
- `device_`
|
||||
- `os_`
|
||||
- `software_`
|
||||
- `podman_`
|
||||
- `docker_`
|
||||
- `kubernetes_`
|
||||
|
||||
- hardware
|
||||
- infrastructure
|
||||
- cloud
|
||||
- systemd
|
||||
- podman
|
||||
- docker
|
||||
- kubernetes
|
||||
Note, some projects will be named with just the prefix. These are projects for
|
||||
configuring the underlying technology. The `podman` project, for example, will
|
||||
tell you how to configure and install podman so it works correctly.
|
||||
|
||||
Hardware will contain projects that relate to specific machines or equipment. 3D printers, Raspberry
|
||||
Pis, and other IOT devices qualify as specialized hardware that needs documentation and
|
||||
configuration. This is not limited to computer equipment. The furnace is an important part of the
|
||||
home lab. the Air Conditioner is integral to the homelab's function. These projects will also be documented.
|
||||
`device_` will prefix projects that relate to specific machines or equipment.
|
||||
3D printers, Raspberry Pis, and other IOT devices qualify as specialized
|
||||
hardware that needs documentation and configuration. This is not limited to
|
||||
computer equipment. The furnace is an important part of the homelab. the Air
|
||||
Conditioner is integral to the homelab's function. These projects will also be
|
||||
documented.
|
||||
|
||||
Infrastructure will contain projects that set up the environments for the remaining listed project
|
||||
types. For example, infrastructure will contain "how to set up a linux box with docker" or "how to
|
||||
set up a k3s cluster for kubernetes".
|
||||
`os_` will contain projects that set up operating systems. These include best
|
||||
practices, backups, updates, default software, etc.
|
||||
|
||||
Cloud projects are for specific cloud providers.
|
||||
`cloud_` projects are for specific cloud providers. This will contain
|
||||
documentation and errata for things like AWS IAM, Route53, etc. Note these will
|
||||
be prefixed with the cloud's name, not the word "cloud". So AWS services will
|
||||
be prefixed with `aws_` and azure would be `azure_`. This should make them more
|
||||
searchable.
|
||||
|
||||
Systemd projects are designed to be installed with ansible and run via systemd on a linux VM or
|
||||
other linux hardware.
|
||||
`software_` projects record configuration for common software agnostic to
|
||||
operating system or linux flavor.
|
||||
|
||||
Podman projects are either designed to be run as quadlets or as podman containers outright.
|
||||
`podman_` projects are either designed to be run as quadlets or as podman
|
||||
containers outright.
|
||||
|
||||
Docker projects are either docker-compose or some form of docker run command.
|
||||
`kubernetes_` projects are helm, kustomize, kubectl, or some other kubernetes
|
||||
compliant deployment.
|
||||
|
||||
Kubernetes projects are helm, kustomize, kubectl, or some other kubernetes compliant deployment.
|
||||
|
||||
## Graduation Requirements
|
||||
## Active Project Requirements
|
||||
|
||||
- [ ] Installation is documented
|
||||
- [ ] Installation configuration examples are provided
|
||||
@@ -91,11 +282,30 @@ Kubernetes projects are helm, kustomize, kubectl, or some other kubernetes compl
|
||||
- [ ] If applicable, a replacement has been identified and documented
|
||||
- [ ] If applicable, backup data locations are documented
|
||||
|
||||
## Project Structure
|
||||
|
||||
All projects will have, at minimum.
|
||||
|
||||
1. A README named `project-name.md`
|
||||
2. A directory called `secrets` which will be gitignored.
|
||||
|
||||
## Creating a Project
|
||||
|
||||
Assuming your project name is `my-project` and it runs on `podman`
|
||||
|
||||
1. Create a new directory called `podman_my-project` under the `active`
|
||||
directory
|
||||
2. Copy the readme template: `cp project_readme_template.md
|
||||
active/podman_my-project/my-project.md`
|
||||
3. Populate `my-project.md` as you work through the install process
|
||||
4. Create a directory called `secrets` in `podman_my-project`. This will be
|
||||
automatically gitignored. Put all secrets here.
|
||||
5. Push the changes when you have a working product
|
||||
|
||||
## Order of Operations
|
||||
|
||||
1. Install cloud projects. These usually have no dependencies and typically provide critical services
|
||||
to other projects (DNS, email notifications, etc.)
|
||||
2. Install infrastructure projects. Usually these only have dependencies on cloud services.
|
||||
3. Install systemd services. These are usually low-level programs that require a dedicated machine
|
||||
and perform semi-critical functions (ipv4 proxy, ddns, etc.).
|
||||
4. Install kubernetes, docker, podman, and other services.
|
||||
1. Configure cloud providers. These usually have no dependencies and typically
|
||||
provide critical services to other projects (DNS, email notifications, etc.)
|
||||
2. Install infrastructure projects. Usually these only have dependencies on
|
||||
cloud services.
|
||||
3. Install systemd, kubernetes, docker, podman, and other services.
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
# AWS Credentials
|
||||
|
||||
Note: this requires the AWS CLI. See [AWS CLI](/cloud/graduated/aws_cli/aws_cli.md)
|
||||
Note: this requires the AWS CLI. See [AWS CLI](/active/aws_cli/aws_cli.md)
|
||||
|
||||
## Credential Generation
|
||||
- [AWS Credentials](#aws-credentials)
|
||||
- [Route53 Credential Generation](#route53-credential-generation)
|
||||
- [AWS Certbot Route53 Policies](#aws-certbot-route53-policies)
|
||||
- [Email Credentials](#email-credentials)
|
||||
|
||||
## Route53 Credential Generation
|
||||
|
||||
```bash
|
||||
export AWS_USERNAME=
|
||||
aws iam create-user --user-name $AWS_USERNAME
|
||||
|
||||
# Allow updating reeseapps
|
||||
aws iam attach-user-policy --user-name $AWS_USERNAME --policy-arn $(cat cloud/graduated/aws_iam/secrets/update-reeseapps-iam-policy-arn)
|
||||
aws iam attach-user-policy --user-name $AWS_USERNAME --policy-arn $(cat active/aws_iam/secrets/update-reeseapps-iam-policy-arn)
|
||||
|
||||
# Allow updating reeselink
|
||||
aws iam attach-user-policy --user-name $AWS_USERNAME --policy-arn $(cat cloud/graduated/aws_iam/secrets/update-reeselink-iam-policy-arn)
|
||||
|
||||
# Allow sending emails
|
||||
aws iam attach-user-policy --user-name $AWS_USERNAME --policy-arn $(cat cloud/graduated/aws_iam/secrets/ses-send-email-policy-arn)
|
||||
aws iam attach-user-policy --user-name $AWS_USERNAME --policy-arn $(cat active/aws_iam/secrets/update-reeselink-iam-policy-arn)
|
||||
|
||||
# Create credentials (run aws configure on the machine that needs these to input them manually)
|
||||
aws iam create-access-key --user-name $AWS_USERNAME
|
||||
@@ -25,7 +27,7 @@ aws iam create-access-key --user-name $AWS_USERNAME
|
||||
|
||||
Example Policy:
|
||||
|
||||
cloud/graduated/aws_iam/secrets/policies/route53_reeselink.json
|
||||
active/aws_iam/secrets/route53_reeselink.json
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -57,11 +59,21 @@ cloud/graduated/aws_iam/secrets/policies/route53_reeselink.json
|
||||
|
||||
```bash
|
||||
# Allow updating route53 records for reeselink.com
|
||||
aws iam create-policy --policy-name update-reeselink --policy-document file://cloud/graduated/aws_iam/secrets/route53_reeselink_policy.json
|
||||
aws iam create-policy --policy-name update-reeselink --policy-document file://active/aws_iam/secrets/route53_reeselink_policy.json
|
||||
|
||||
# Allow updating route53 records for reeseapps.com
|
||||
aws iam create-policy --policy-name update-reeseapps --policy-document file://cloud/graduated/aws_iam/secrets/route53_reeseapps_policy.json
|
||||
|
||||
# Allow sending emails
|
||||
aws iam create-policy --policy-name send-email --policy-document file://cloud/graduated/aws_iam/secrets/ses_allow_send_policy.json
|
||||
aws iam create-policy --policy-name update-reeseapps --policy-document file://active/aws_iam/secrets/route53_reeseapps_policy.json
|
||||
```
|
||||
|
||||
## Email Credentials
|
||||
|
||||
<https://docs.aws.amazon.com/ses/latest/dg/smtp-credentials.html>
|
||||
|
||||
You can technically do this through the CLI, see above link.
|
||||
|
||||
1. Log into the AWS console
|
||||
2. Navigate to SES
|
||||
3. Click "SMTP Settings"
|
||||
4. Click "Create SMTP Credentials"
|
||||
5. Name it "ses-smtp-user.something"
|
||||
6. Copy the username and password
|
||||
@@ -17,14 +17,14 @@ convenience.
|
||||
|
||||
## Reeselink Addresses
|
||||
|
||||
See `example-record-file.json` for example contents of `file://cloud/graduated/aws_route53/secrets/aws/reeselink.json`.
|
||||
See `example-record-file.json` for example contents of `file://active/aws_route53/secrets/aws/reeselink.json`.
|
||||
|
||||
```bash
|
||||
aws route53 change-resource-record-sets --hosted-zone-id $(cat cloud/graduated/aws_route53/secrets/reeselink-zoneid) --change-batch file://cloud/graduated/aws_route53/secrets/reeselink.json
|
||||
aws route53 change-resource-record-sets --hosted-zone-id $(cat active/aws_route53/secrets/reeselink-zoneid) --change-batch file://active/aws_route53/secrets/reeselink.json
|
||||
```
|
||||
|
||||
## Reeseapps Addresses
|
||||
|
||||
```bash
|
||||
aws route53 change-resource-record-sets --hosted-zone-id $(cat cloud/graduated/aws_route53/secrets/reeseapps-zoneid) --change-batch file://cloud/graduated/aws_route53/secrets/reeseapps.json
|
||||
aws route53 change-resource-record-sets --hosted-zone-id $(cat active/aws_route53/secrets/reeseapps-zoneid) --change-batch file://active/aws_route53/secrets/reeseapps.json
|
||||
```
|
||||
3
active/aws_ses/aws_ses.md
Normal file
3
active/aws_ses/aws_ses.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# AWS SES
|
||||
|
||||
AWS Simple Email Service
|
||||
14
active/device_3dserver/3dserver.md
Normal file
14
active/device_3dserver/3dserver.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# 3D Server Hardware
|
||||
|
||||
## Motherboard
|
||||
|
||||
B650 GAMING X AX rev 1.5
|
||||
|
||||
<https://www.gigabyte.com/Motherboard/B650-GAMING-X-AX-rev-15/support#dl>
|
||||
|
||||
- Enable PBO
|
||||
- Enable XMP
|
||||
- Enable SVM
|
||||
- Enable PCIe x4x4x4x4 bifurcation
|
||||
- Enable Power always back on
|
||||
- Fans to full speed
|
||||
@@ -8,6 +8,16 @@ If you want to set up WiFi without logging in follow this:
|
||||
|
||||
<https://wiki.bambulab.com/en/p1/manual/p1-sd-card-network-configuration-guide>
|
||||
|
||||
1. Create a new file on the printer's SD card named `user_wifi.cfg`
|
||||
2. Add the following contents to `user_wifi.cfg`:
|
||||
|
||||
```text
|
||||
ssid:FruitTest08
|
||||
password:wikitest12
|
||||
```
|
||||
|
||||
3. Put the SD card back in the printer. It should connect automatically.
|
||||
|
||||
### Connecting via LAN mode
|
||||
|
||||
- Make sure you open 1990 and 2021 (tcp + udp) in your firewall application.
|
||||
11
active/device_epson_et_2800/epson_et_2800.md
Normal file
11
active/device_epson_et_2800/epson_et_2800.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Epson ET 2800
|
||||
|
||||
## Printer Setup
|
||||
|
||||
1. Download and install the drivers at <https://support.epson.net/linux/Printer/LSB_distribution_pages/en/escpr.php>
|
||||
2. Settings -> Printers -> Add
|
||||
3. Select LPD/LPR Host or Printer
|
||||
4. Enter the address: `lpd://<ip_address>`
|
||||
5. Select Epson, then select Epson ET-2800 Series
|
||||
6. Save
|
||||
7. Print
|
||||
5
active/device_esphome/.gitignore
vendored
Normal file
5
active/device_esphome/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Gitignore settings for ESPHome
|
||||
# This is an example and may include too much for your use-case.
|
||||
# You can modify this file to suit your needs.
|
||||
/.esphome/
|
||||
/secrets.yaml
|
||||
370
active/device_esphome/default-atom-echo.yaml
Normal file
370
active/device_esphome/default-atom-echo.yaml
Normal file
@@ -0,0 +1,370 @@
|
||||
substitutions:
|
||||
name: m5stack-atom-echo
|
||||
friendly_name: M5Stack Atom Echo
|
||||
|
||||
esphome:
|
||||
name: ${name}
|
||||
name_add_mac_suffix: true
|
||||
friendly_name: ${friendly_name}
|
||||
min_version: 2025.5.0
|
||||
|
||||
esp32:
|
||||
board: m5stack-atom
|
||||
cpu_frequency: 240MHz
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
logger:
|
||||
api:
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
id: ota_esphome
|
||||
|
||||
wifi:
|
||||
ap:
|
||||
|
||||
captive_portal:
|
||||
|
||||
button:
|
||||
- platform: factory_reset
|
||||
id: factory_reset_btn
|
||||
name: Factory reset
|
||||
|
||||
i2s_audio:
|
||||
- id: i2s_audio_bus
|
||||
i2s_lrclk_pin: GPIO33
|
||||
i2s_bclk_pin: GPIO19
|
||||
|
||||
microphone:
|
||||
- platform: i2s_audio
|
||||
id: echo_microphone
|
||||
i2s_din_pin: GPIO23
|
||||
adc_type: external
|
||||
pdm: true
|
||||
sample_rate: 16000
|
||||
correct_dc_offset: true
|
||||
|
||||
speaker:
|
||||
- platform: i2s_audio
|
||||
id: echo_speaker
|
||||
i2s_dout_pin: GPIO22
|
||||
dac_type: external
|
||||
bits_per_sample: 16bit
|
||||
sample_rate: 16000
|
||||
channel: stereo # The Echo has poor playback audio quality when using mon audio
|
||||
buffer_duration: 60ms
|
||||
|
||||
media_player:
|
||||
- platform: speaker
|
||||
name: None
|
||||
id: echo_media_player
|
||||
announcement_pipeline:
|
||||
speaker: echo_speaker
|
||||
format: WAV
|
||||
codec_support_enabled: false
|
||||
buffer_size: 6000
|
||||
volume_min: 0.4
|
||||
files:
|
||||
- id: timer_finished_wave_file
|
||||
file: https://github.com/esphome/wake-word-voice-assistants/raw/main/sounds/timer_finished.wav
|
||||
on_announcement:
|
||||
- if:
|
||||
condition:
|
||||
- microphone.is_capturing:
|
||||
then:
|
||||
- script.execute: stop_wake_word
|
||||
- light.turn_on:
|
||||
id: led
|
||||
blue: 100%
|
||||
red: 0%
|
||||
green: 0%
|
||||
brightness: 100%
|
||||
effect: none
|
||||
on_idle:
|
||||
- script.execute: start_wake_word
|
||||
- script.execute: reset_led
|
||||
|
||||
voice_assistant:
|
||||
id: va
|
||||
micro_wake_word:
|
||||
microphone:
|
||||
microphone: echo_microphone
|
||||
channels: 0
|
||||
gain_factor: 4
|
||||
media_player: echo_media_player
|
||||
noise_suppression_level: 2
|
||||
auto_gain: 31dBFS
|
||||
on_listening:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
blue: 100%
|
||||
red: 0%
|
||||
green: 0%
|
||||
effect: "Slow Pulse"
|
||||
on_stt_vad_end:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
blue: 100%
|
||||
red: 0%
|
||||
green: 0%
|
||||
effect: "Fast Pulse"
|
||||
on_tts_start:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
blue: 100%
|
||||
red: 0%
|
||||
green: 0%
|
||||
brightness: 100%
|
||||
effect: none
|
||||
on_end:
|
||||
# Handle the "nevermind" case where there is no announcement
|
||||
- wait_until:
|
||||
condition:
|
||||
- media_player.is_announcing:
|
||||
timeout: 0.5s
|
||||
# Restart only mWW if enabled; streaming wake words automatically restart
|
||||
- if:
|
||||
condition:
|
||||
- lambda: return id(wake_word_engine_location).state == "On device";
|
||||
then:
|
||||
- wait_until:
|
||||
- and:
|
||||
- not:
|
||||
voice_assistant.is_running:
|
||||
- not:
|
||||
speaker.is_playing:
|
||||
- lambda: id(va).set_use_wake_word(false);
|
||||
- micro_wake_word.start:
|
||||
- script.execute: reset_led
|
||||
on_error:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
red: 100%
|
||||
green: 0%
|
||||
blue: 0%
|
||||
brightness: 100%
|
||||
effect: none
|
||||
- delay: 2s
|
||||
- script.execute: reset_led
|
||||
on_client_connected:
|
||||
- delay: 2s # Give the api server time to settle
|
||||
- script.execute: start_wake_word
|
||||
on_client_disconnected:
|
||||
- script.execute: stop_wake_word
|
||||
on_timer_finished:
|
||||
- script.execute: stop_wake_word
|
||||
- wait_until:
|
||||
not:
|
||||
microphone.is_capturing:
|
||||
- switch.turn_on: timer_ringing
|
||||
- light.turn_on:
|
||||
id: led
|
||||
red: 0%
|
||||
green: 100%
|
||||
blue: 0%
|
||||
brightness: 100%
|
||||
effect: "Fast Pulse"
|
||||
- wait_until:
|
||||
- switch.is_off: timer_ringing
|
||||
- light.turn_off: led
|
||||
- switch.turn_off: timer_ringing
|
||||
|
||||
binary_sensor:
|
||||
# button does the following:
|
||||
# short click - stop a timer
|
||||
# if no timer then restart either microwakeword or voice assistant continuous
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO39
|
||||
inverted: true
|
||||
name: Button
|
||||
disabled_by_default: true
|
||||
entity_category: diagnostic
|
||||
id: echo_button
|
||||
on_multi_click:
|
||||
- timing:
|
||||
- ON for at least 50ms
|
||||
- OFF for at least 50ms
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
switch.is_on: timer_ringing
|
||||
then:
|
||||
- switch.turn_off: timer_ringing
|
||||
else:
|
||||
- script.execute: start_wake_word
|
||||
- timing:
|
||||
- ON for at least 10s
|
||||
then:
|
||||
- button.press: factory_reset_btn
|
||||
|
||||
light:
|
||||
- platform: esp32_rmt_led_strip
|
||||
id: led
|
||||
name: None
|
||||
disabled_by_default: true
|
||||
entity_category: config
|
||||
pin: GPIO27
|
||||
default_transition_length: 0s
|
||||
chipset: SK6812
|
||||
num_leds: 1
|
||||
rgb_order: grb
|
||||
effects:
|
||||
- pulse:
|
||||
name: "Slow Pulse"
|
||||
transition_length: 250ms
|
||||
update_interval: 250ms
|
||||
min_brightness: 50%
|
||||
max_brightness: 100%
|
||||
- pulse:
|
||||
name: "Fast Pulse"
|
||||
transition_length: 100ms
|
||||
update_interval: 100ms
|
||||
min_brightness: 50%
|
||||
max_brightness: 100%
|
||||
|
||||
script:
|
||||
- id: reset_led
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
- lambda: return id(wake_word_engine_location).state == "On device";
|
||||
- switch.is_on: use_listen_light
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
red: 100%
|
||||
green: 89%
|
||||
blue: 71%
|
||||
brightness: 60%
|
||||
effect: none
|
||||
else:
|
||||
- if:
|
||||
condition:
|
||||
- lambda: return id(wake_word_engine_location).state != "On device";
|
||||
- switch.is_on: use_listen_light
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
red: 0%
|
||||
green: 100%
|
||||
blue: 100%
|
||||
brightness: 60%
|
||||
effect: none
|
||||
else:
|
||||
- light.turn_off: led
|
||||
- id: start_wake_word
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
and:
|
||||
- not:
|
||||
- voice_assistant.is_running:
|
||||
- lambda: return id(wake_word_engine_location).state == "On device";
|
||||
then:
|
||||
- lambda: id(va).set_use_wake_word(false);
|
||||
- micro_wake_word.start:
|
||||
- if:
|
||||
condition:
|
||||
and:
|
||||
- not:
|
||||
- voice_assistant.is_running:
|
||||
- lambda: return id(wake_word_engine_location).state == "In Home Assistant";
|
||||
then:
|
||||
- lambda: id(va).set_use_wake_word(true);
|
||||
- voice_assistant.start_continuous:
|
||||
- id: stop_wake_word
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return id(wake_word_engine_location).state == "In Home Assistant";
|
||||
then:
|
||||
- lambda: id(va).set_use_wake_word(false);
|
||||
- voice_assistant.stop:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return id(wake_word_engine_location).state == "On device";
|
||||
then:
|
||||
- micro_wake_word.stop:
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
name: Use listen light
|
||||
id: use_listen_light
|
||||
optimistic: true
|
||||
restore_mode: RESTORE_DEFAULT_ON
|
||||
entity_category: config
|
||||
on_turn_on:
|
||||
- script.execute: reset_led
|
||||
on_turn_off:
|
||||
- script.execute: reset_led
|
||||
- platform: template
|
||||
id: timer_ringing
|
||||
optimistic: true
|
||||
restore_mode: ALWAYS_OFF
|
||||
on_turn_off:
|
||||
# Turn off the repeat mode and disable the pause between playlist items
|
||||
- lambda: |-
|
||||
id(echo_media_player)
|
||||
->make_call()
|
||||
.set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_OFF)
|
||||
.set_announcement(true)
|
||||
.perform();
|
||||
id(echo_media_player)->set_playlist_delay_ms(speaker::AudioPipelineType::ANNOUNCEMENT, 0);
|
||||
# Stop playing the alarm
|
||||
- media_player.stop:
|
||||
announcement: true
|
||||
on_turn_on:
|
||||
# Turn on the repeat mode and pause for 1000 ms between playlist items/repeats
|
||||
- lambda: |-
|
||||
id(echo_media_player)
|
||||
->make_call()
|
||||
.set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_ONE)
|
||||
.set_announcement(true)
|
||||
.perform();
|
||||
id(echo_media_player)->set_playlist_delay_ms(speaker::AudioPipelineType::ANNOUNCEMENT, 1000);
|
||||
- media_player.speaker.play_on_device_media_file:
|
||||
media_file: timer_finished_wave_file
|
||||
announcement: true
|
||||
- delay: 15min
|
||||
- switch.turn_off: timer_ringing
|
||||
|
||||
select:
|
||||
- platform: template
|
||||
entity_category: config
|
||||
name: Wake word engine location
|
||||
id: wake_word_engine_location
|
||||
optimistic: true
|
||||
restore_value: true
|
||||
options:
|
||||
- In Home Assistant
|
||||
- On device
|
||||
initial_option: On device
|
||||
on_value:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return x == "In Home Assistant";
|
||||
then:
|
||||
- micro_wake_word.stop:
|
||||
- delay: 500ms
|
||||
- lambda: id(va).set_use_wake_word(true);
|
||||
- voice_assistant.start_continuous:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return x == "On device";
|
||||
then:
|
||||
- lambda: id(va).set_use_wake_word(false);
|
||||
- voice_assistant.stop:
|
||||
- delay: 500ms
|
||||
- micro_wake_word.start:
|
||||
|
||||
micro_wake_word:
|
||||
on_wake_word_detected:
|
||||
- voice_assistant.start:
|
||||
wake_word: !lambda return wake_word;
|
||||
vad:
|
||||
models:
|
||||
- model: okay_nabu
|
||||
- model: hey_mycroft
|
||||
- model: hey_jarvis
|
||||
249
active/device_esphome/esphome.md
Normal file
249
active/device_esphome/esphome.md
Normal file
@@ -0,0 +1,249 @@
|
||||
# ESP32
|
||||
|
||||
- [ESP32](#esp32)
|
||||
- [Install](#install)
|
||||
- [Devices](#devices)
|
||||
- [Lilygo tdongle](#lilygo-tdongle)
|
||||
- [Local Flashing](#local-flashing)
|
||||
- [Adding a New Device](#adding-a-new-device)
|
||||
- [Controlling Home Assistant](#controlling-home-assistant)
|
||||
- [Configuration Sections](#configuration-sections)
|
||||
- [esphome](#esphome)
|
||||
- [esp32](#esp32-1)
|
||||
- [logger](#logger)
|
||||
- [api](#api)
|
||||
- [wifi](#wifi)
|
||||
- [ota](#ota)
|
||||
- [captive portal](#captive-portal)
|
||||
- [button](#button)
|
||||
- [i2s audio](#i2s-audio)
|
||||
- [microphone](#microphone)
|
||||
- [speaker](#speaker)
|
||||
- [media player](#media-player)
|
||||
- [voice assistant](#voice-assistant)
|
||||
- [micro wake word](#micro-wake-word)
|
||||
- [light](#light)
|
||||
- [binary sensor](#binary-sensor)
|
||||
- [lambda](#lambda)
|
||||
- [Display](#display)
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
# Check that you have python 3.11 installed
|
||||
uv python list --only-installed
|
||||
|
||||
# Create the venv (python 3.11 is recommended in the docs)
|
||||
uv venv --python 3.11
|
||||
|
||||
# Install esphome
|
||||
uv pip install esphome wheel pip
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
## Devices
|
||||
|
||||
### Lilygo tdongle
|
||||
|
||||
Display: 80 X 160
|
||||
|
||||
## Local Flashing
|
||||
|
||||
Make sure your permissions are set correctly
|
||||
|
||||
```bash
|
||||
sudo usermod -a -G dialout ducoterra
|
||||
```
|
||||
|
||||
Then "run" your config file
|
||||
|
||||
```bash
|
||||
cd active/device_esp32
|
||||
uv venv
|
||||
uv pip install esphome
|
||||
source .venv/bin/activate
|
||||
|
||||
esphome run m5stack-atom-echo.yaml
|
||||
```
|
||||
|
||||
## Adding a New Device
|
||||
|
||||
1. Create a new yaml configuration file called "my-device-device-type.yaml"
|
||||
|
||||
## Controlling Home Assistant
|
||||
|
||||
<https://esphome.io/components/api/#api-actions>
|
||||
|
||||
## Configuration Sections
|
||||
|
||||
<https://esphome.io/components/>
|
||||
|
||||
### esphome
|
||||
|
||||
### esp32
|
||||
|
||||
<https://esphome.io/components/esp32/#configuration-variables>
|
||||
|
||||
### logger
|
||||
|
||||
<https://esphome.io/components/logger/>
|
||||
|
||||
### api
|
||||
|
||||
<https://esphome.io/components/api/>
|
||||
|
||||
### wifi
|
||||
|
||||
<https://esphome.io/components/wifi/>
|
||||
|
||||
### ota
|
||||
|
||||
<https://esphome.io/components/ota/>
|
||||
|
||||
<https://esphome.io/components/ota/esphome/>
|
||||
|
||||
### captive portal
|
||||
|
||||
<https://esphome.io/components/captive_portal/>
|
||||
|
||||
### button
|
||||
|
||||
<https://esphome.io/components/button/>
|
||||
|
||||
### i2s audio
|
||||
|
||||
<https://esphome.io/components/i2s_audio/>
|
||||
|
||||
### microphone
|
||||
|
||||
<https://esphome.io/components/microphone/>
|
||||
|
||||
<https://esphome.io/components/microphone/i2s_audio/>
|
||||
|
||||
### speaker
|
||||
|
||||
<https://esphome.io/components/speaker/i2s_audio/>
|
||||
|
||||
### media player
|
||||
|
||||
<https://esphome.io/components/media_player/speaker/>
|
||||
|
||||
Sometimes you'll need to convert media files to supported encoders.
|
||||
|
||||
```bash
|
||||
ffmpeg -i input.flac output.wav
|
||||
```
|
||||
|
||||
To play media on other devices from home assistant, put the
|
||||
|
||||
```yaml
|
||||
action: media_player.play_media
|
||||
target:
|
||||
entity_id: media_player.kitchen_google_home
|
||||
data:
|
||||
media_content_type: "audio/wav"
|
||||
media_content_id: "media-source://media_source/local/wake_word_triggered.wav"
|
||||
```
|
||||
|
||||
### voice assistant
|
||||
|
||||
<https://esphome.io/components/voice_assistant/>
|
||||
|
||||
In Home Assistant's configuration.yaml, add the following to listen to
|
||||
audio recordings of your voice request:
|
||||
|
||||
```bash
|
||||
assist_pipeline:
|
||||
debug_recording_dir: /share/assist_pipeline
|
||||
```
|
||||
|
||||
### micro wake word
|
||||
|
||||
<https://esphome.io/components/micro_wake_word/>
|
||||
|
||||
### light
|
||||
|
||||
<https://esphome.io/components/light/#light-effects>
|
||||
|
||||
### binary sensor
|
||||
|
||||
<https://esphome.io/components/binary_sensor/>
|
||||
|
||||
### lambda
|
||||
|
||||
<https://esphome.io/automations/templates/#config-lambda>
|
||||
|
||||
> id(...) is a helper function that makes ESPHome fetch an object with the
|
||||
> supplied ID (which you defined somewhere else, like top_end_stop ) and lets
|
||||
> you call any of ESPHome’s many APIs directly. For example, here we’re
|
||||
> retrieving the current state of the end stop using .state and using it to
|
||||
> construct our cover state.
|
||||
|
||||
### Display
|
||||
|
||||
Display pages
|
||||
|
||||
```yaml
|
||||
display:
|
||||
- platform: st7735
|
||||
spi_id: spi_lcd
|
||||
model: "INITR_MINI160X80"
|
||||
reset_pin: GPIO1
|
||||
cs_pin: GPIO4
|
||||
dc_pin: GPIO2
|
||||
rotation: 270
|
||||
device_width: 82
|
||||
device_height: 161
|
||||
col_start: 0
|
||||
row_start: 0
|
||||
eight_bit_color: true
|
||||
invert_colors: true
|
||||
use_bgr: true
|
||||
auto_clear_enabled: true
|
||||
id: my_display
|
||||
pages:
|
||||
- id: page1
|
||||
lambda: |-
|
||||
it.print(0, 10, id(font_roboto), "Connecting to");
|
||||
it.print(0, 30, id(font_roboto), "Home Assistant...");
|
||||
- id: page2
|
||||
lambda: |-
|
||||
it.print(0, 10, id(font_roboto), "Configuring");
|
||||
it.print(0, 30, id(font_roboto), "sensors...");
|
||||
- id: page3
|
||||
lambda: |-
|
||||
it.print(0, 10, id(font_roboto), "Loading");
|
||||
it.print(0, 30, id(font_roboto), "important");
|
||||
it.print(0, 50, id(font_roboto), "update...");
|
||||
- id: page4
|
||||
lambda: |-
|
||||
it.image(0, 0, id(my_image), COLOR_OFF, COLOR_ON);
|
||||
```
|
||||
|
||||
Switch pages
|
||||
|
||||
```yaml
|
||||
interval:
|
||||
- interval: 5s
|
||||
then:
|
||||
- display.page.show_next: my_display
|
||||
- component.update: my_display
|
||||
```
|
||||
|
||||
Show an image
|
||||
|
||||
```yaml
|
||||
image:
|
||||
- file: "test_tdongle_image.png"
|
||||
type: RGB
|
||||
id: my_image
|
||||
```
|
||||
|
||||
Specify a font
|
||||
|
||||
```yaml
|
||||
font:
|
||||
- file: "gfonts://Roboto"
|
||||
id: font_roboto
|
||||
size: 20
|
||||
```
|
||||
386
active/device_esphome/great-room-atom-echo.yaml
Normal file
386
active/device_esphome/great-room-atom-echo.yaml
Normal file
@@ -0,0 +1,386 @@
|
||||
esphome:
|
||||
name: great-room-atom-echo
|
||||
friendly_name: Great Room Atom Echo
|
||||
|
||||
esp32:
|
||||
board: m5stack-atom
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
level: debug
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
encryption:
|
||||
key: !secret great_room_atom_echo_key
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
domain: .reeselink.com
|
||||
fast_connect: true
|
||||
enable_btm: true
|
||||
on_disconnect:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
blue: 0%
|
||||
red: 100%
|
||||
green: 0%
|
||||
effect: "Slow Pulse"
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "Great-Room-Atom-Echo"
|
||||
password: !secret hotspot_password
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret ota_password
|
||||
|
||||
captive_portal:
|
||||
|
||||
button:
|
||||
- platform: factory_reset
|
||||
id: factory_reset_btn
|
||||
name: Factory reset
|
||||
|
||||
i2s_audio:
|
||||
- id: i2s_audio_bus
|
||||
i2s_lrclk_pin: GPIO33
|
||||
i2s_bclk_pin: GPIO19
|
||||
|
||||
microphone:
|
||||
- platform: i2s_audio
|
||||
id: echo_microphone
|
||||
i2s_din_pin: GPIO23
|
||||
adc_type: external
|
||||
pdm: true
|
||||
sample_rate: 16000
|
||||
correct_dc_offset: true
|
||||
|
||||
speaker:
|
||||
- platform: i2s_audio
|
||||
id: echo_speaker
|
||||
i2s_dout_pin: GPIO22
|
||||
dac_type: external
|
||||
bits_per_sample: 16bit
|
||||
sample_rate: 16000
|
||||
channel: stereo # The Echo has poor playback audio quality when using mon audio
|
||||
buffer_duration: 60ms
|
||||
|
||||
media_player:
|
||||
- platform: speaker
|
||||
name: None
|
||||
id: echo_media_player
|
||||
announcement_pipeline:
|
||||
speaker: echo_speaker
|
||||
format: WAV
|
||||
codec_support_enabled: false
|
||||
buffer_size: 6000
|
||||
volume_min: 1
|
||||
volume_max: 1
|
||||
volume_initial: 1
|
||||
files:
|
||||
- id: timer_finished_wave_file
|
||||
file: https://github.com/esphome/wake-word-voice-assistants/raw/main/sounds/timer_finished.wav
|
||||
on_announcement:
|
||||
- if:
|
||||
condition:
|
||||
- microphone.is_capturing:
|
||||
then:
|
||||
- script.execute: stop_wake_word
|
||||
- light.turn_on:
|
||||
id: led
|
||||
blue: 100%
|
||||
red: 0%
|
||||
green: 0%
|
||||
brightness: 100%
|
||||
effect: none
|
||||
on_idle:
|
||||
- script.execute: start_wake_word
|
||||
- script.execute: reset_led
|
||||
|
||||
voice_assistant:
|
||||
id: va
|
||||
micro_wake_word:
|
||||
microphone:
|
||||
microphone: echo_microphone
|
||||
channels: 0
|
||||
gain_factor: 64
|
||||
media_player: echo_media_player
|
||||
noise_suppression_level: 2
|
||||
auto_gain: 31dBFS
|
||||
on_listening:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
blue: 100%
|
||||
red: 0%
|
||||
green: 0%
|
||||
effect: "Slow Pulse"
|
||||
on_stt_vad_end:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
blue: 100%
|
||||
red: 0%
|
||||
green: 0%
|
||||
effect: "Fast Pulse"
|
||||
on_tts_start:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
blue: 100%
|
||||
red: 0%
|
||||
green: 0%
|
||||
brightness: 100%
|
||||
effect: none
|
||||
on_end:
|
||||
# Handle the "nevermind" case where there is no announcement
|
||||
- wait_until:
|
||||
condition:
|
||||
- media_player.is_announcing:
|
||||
timeout: 0.5s
|
||||
# Restart only mWW if enabled; streaming wake words automatically restart
|
||||
- if:
|
||||
condition:
|
||||
- lambda: return id(wake_word_engine_location).state == "On device";
|
||||
then:
|
||||
- wait_until:
|
||||
- and:
|
||||
- not:
|
||||
voice_assistant.is_running:
|
||||
- not:
|
||||
speaker.is_playing:
|
||||
- lambda: id(va).set_use_wake_word(false);
|
||||
- micro_wake_word.start:
|
||||
- script.execute: reset_led
|
||||
on_error:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
red: 100%
|
||||
green: 0%
|
||||
blue: 0%
|
||||
brightness: 100%
|
||||
effect: none
|
||||
- delay: 2s
|
||||
- script.execute: reset_led
|
||||
on_client_connected:
|
||||
- delay: 2s # Give the api server time to settle
|
||||
- script.execute: start_wake_word
|
||||
on_client_disconnected:
|
||||
- script.execute: stop_wake_word
|
||||
on_timer_finished:
|
||||
- script.execute: stop_wake_word
|
||||
- wait_until:
|
||||
not:
|
||||
microphone.is_capturing:
|
||||
- switch.turn_on: timer_ringing
|
||||
- light.turn_on:
|
||||
id: led
|
||||
red: 0%
|
||||
green: 100%
|
||||
blue: 0%
|
||||
brightness: 100%
|
||||
effect: "Fast Pulse"
|
||||
- wait_until:
|
||||
- switch.is_off: timer_ringing
|
||||
- light.turn_off: led
|
||||
- switch.turn_off: timer_ringing
|
||||
|
||||
binary_sensor:
|
||||
# button does the following:
|
||||
# short click - stop a timer
|
||||
# if no timer then restart either microwakeword or voice assistant continuous
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO39
|
||||
inverted: true
|
||||
name: Button
|
||||
disabled_by_default: true
|
||||
entity_category: diagnostic
|
||||
id: echo_button
|
||||
on_multi_click:
|
||||
- timing:
|
||||
- ON for at least 50ms
|
||||
- OFF for at least 50ms
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
switch.is_on: timer_ringing
|
||||
then:
|
||||
- switch.turn_off: timer_ringing
|
||||
else:
|
||||
- script.execute: start_wake_word
|
||||
- timing:
|
||||
- ON for at least 10s
|
||||
then:
|
||||
- button.press: factory_reset_btn
|
||||
|
||||
light:
|
||||
- platform: esp32_rmt_led_strip
|
||||
id: led
|
||||
name: None
|
||||
disabled_by_default: true
|
||||
entity_category: config
|
||||
pin: GPIO27
|
||||
default_transition_length: 0s
|
||||
chipset: SK6812
|
||||
num_leds: 1
|
||||
rgb_order: grb
|
||||
effects:
|
||||
- pulse:
|
||||
name: "Slow Pulse"
|
||||
transition_length: 250ms
|
||||
update_interval: 250ms
|
||||
min_brightness: 50%
|
||||
max_brightness: 100%
|
||||
- pulse:
|
||||
name: "Fast Pulse"
|
||||
transition_length: 100ms
|
||||
update_interval: 100ms
|
||||
min_brightness: 50%
|
||||
max_brightness: 100%
|
||||
|
||||
script:
|
||||
- id: reset_led
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
- lambda: return id(wake_word_engine_location).state == "On device";
|
||||
- switch.is_on: use_listen_light
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
red: 100%
|
||||
green: 89%
|
||||
blue: 71%
|
||||
brightness: 60%
|
||||
effect: none
|
||||
else:
|
||||
- if:
|
||||
condition:
|
||||
- lambda: return id(wake_word_engine_location).state != "On device";
|
||||
- switch.is_on: use_listen_light
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
red: 0%
|
||||
green: 100%
|
||||
blue: 100%
|
||||
brightness: 60%
|
||||
effect: none
|
||||
else:
|
||||
- light.turn_off: led
|
||||
- id: start_wake_word
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
and:
|
||||
- not:
|
||||
- voice_assistant.is_running:
|
||||
- lambda: return id(wake_word_engine_location).state == "On device";
|
||||
then:
|
||||
- lambda: id(va).set_use_wake_word(false);
|
||||
- micro_wake_word.start:
|
||||
- if:
|
||||
condition:
|
||||
and:
|
||||
- not:
|
||||
- voice_assistant.is_running:
|
||||
- lambda: return id(wake_word_engine_location).state == "In Home Assistant";
|
||||
then:
|
||||
- lambda: id(va).set_use_wake_word(true);
|
||||
- voice_assistant.start_continuous:
|
||||
- id: stop_wake_word
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return id(wake_word_engine_location).state == "In Home Assistant";
|
||||
then:
|
||||
- lambda: id(va).set_use_wake_word(false);
|
||||
- voice_assistant.stop:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return id(wake_word_engine_location).state == "On device";
|
||||
then:
|
||||
- micro_wake_word.stop:
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
name: Use listen light
|
||||
id: use_listen_light
|
||||
optimistic: true
|
||||
restore_mode: RESTORE_DEFAULT_ON
|
||||
entity_category: config
|
||||
on_turn_on:
|
||||
- script.execute: reset_led
|
||||
on_turn_off:
|
||||
- script.execute: reset_led
|
||||
- platform: template
|
||||
id: timer_ringing
|
||||
optimistic: true
|
||||
restore_mode: ALWAYS_OFF
|
||||
on_turn_off:
|
||||
# Turn off the repeat mode and disable the pause between playlist items
|
||||
- lambda: |-
|
||||
id(echo_media_player)
|
||||
->make_call()
|
||||
.set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_OFF)
|
||||
.set_announcement(true)
|
||||
.perform();
|
||||
id(echo_media_player)->set_playlist_delay_ms(speaker::AudioPipelineType::ANNOUNCEMENT, 0);
|
||||
# Stop playing the alarm
|
||||
- media_player.stop:
|
||||
announcement: true
|
||||
on_turn_on:
|
||||
# Turn on the repeat mode and pause for 1000 ms between playlist items/repeats
|
||||
- lambda: |-
|
||||
id(echo_media_player)
|
||||
->make_call()
|
||||
.set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_ONE)
|
||||
.set_announcement(true)
|
||||
.perform();
|
||||
id(echo_media_player)->set_playlist_delay_ms(speaker::AudioPipelineType::ANNOUNCEMENT, 1000);
|
||||
- media_player.speaker.play_on_device_media_file:
|
||||
media_file: timer_finished_wave_file
|
||||
announcement: true
|
||||
- delay: 15min
|
||||
- switch.turn_off: timer_ringing
|
||||
|
||||
select:
|
||||
- platform: template
|
||||
entity_category: config
|
||||
name: Wake word engine location
|
||||
id: wake_word_engine_location
|
||||
optimistic: true
|
||||
restore_value: true
|
||||
options:
|
||||
- In Home Assistant
|
||||
- On device
|
||||
initial_option: On device
|
||||
on_value:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return x == "In Home Assistant";
|
||||
then:
|
||||
- micro_wake_word.stop:
|
||||
- delay: 500ms
|
||||
- lambda: id(va).set_use_wake_word(true);
|
||||
- voice_assistant.start_continuous:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return x == "On device";
|
||||
then:
|
||||
- lambda: id(va).set_use_wake_word(false);
|
||||
- voice_assistant.stop:
|
||||
- delay: 500ms
|
||||
- micro_wake_word.start:
|
||||
|
||||
micro_wake_word:
|
||||
on_wake_word_detected:
|
||||
- voice_assistant.start:
|
||||
wake_word: !lambda return wake_word;
|
||||
vad:
|
||||
models:
|
||||
- model: okay_nabu
|
||||
- model: hey_mycroft
|
||||
- model: hey_jarvis
|
||||
118
active/device_esphome/lilygo-tdongle.yaml
Normal file
118
active/device_esphome/lilygo-tdongle.yaml
Normal file
@@ -0,0 +1,118 @@
|
||||
esphome:
|
||||
name: tdongle
|
||||
friendly_name: tdongle
|
||||
|
||||
esp32:
|
||||
board: esp32-s3-devkitc-1
|
||||
framework:
|
||||
type: esp-idf
|
||||
flash_size: 16MB
|
||||
|
||||
logger:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
encryption:
|
||||
key: !secret lilygo_tdongle_key
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
domain: .reeselink.com
|
||||
fast_connect: true
|
||||
enable_btm: true
|
||||
id: wifithing
|
||||
# on_connect:
|
||||
# - component.update: my_online_image
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret ota_password
|
||||
|
||||
captive_portal:
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin: GPIO0
|
||||
name: Button
|
||||
|
||||
spi:
|
||||
- id: spi_led
|
||||
clk_pin: GPIO39
|
||||
mosi_pin: GPIO40
|
||||
- id: spi_lcd
|
||||
clk_pin: GPIO5
|
||||
mosi_pin: GPIO3
|
||||
|
||||
output:
|
||||
- platform: ledc
|
||||
frequency: 2000
|
||||
pin: GPIO38
|
||||
inverted: True
|
||||
id: backlight_output
|
||||
|
||||
light:
|
||||
- platform: monochromatic
|
||||
output: backlight_output
|
||||
name: "LCD Backlight"
|
||||
id: lcd_backlight
|
||||
restore_mode: ALWAYS_ON
|
||||
# RGB Led, APA102 on GPIO39/GPIO40
|
||||
- platform: spi_led_strip
|
||||
spi_id: spi_led
|
||||
num_leds: 1
|
||||
name: "FastLED SPI Light"
|
||||
data_rate: 1MHz # Adjust as needed, APA102 supports up to 20MHz, 1MHz is a safe starting point
|
||||
|
||||
display:
|
||||
- platform: st7735
|
||||
spi_id: spi_lcd
|
||||
model: "INITR_MINI160X80"
|
||||
reset_pin: GPIO1
|
||||
cs_pin: GPIO4
|
||||
dc_pin: GPIO2
|
||||
rotation: 270
|
||||
device_width: 82
|
||||
device_height: 161
|
||||
col_start: 0
|
||||
row_start: 0
|
||||
eight_bit_color: true
|
||||
invert_colors: true
|
||||
use_bgr: true
|
||||
auto_clear_enabled: true
|
||||
id: my_display
|
||||
pages:
|
||||
- id: page1
|
||||
lambda: |-
|
||||
it.print(0, 10, id(font_roboto), "Connecting to");
|
||||
it.print(0, 30, id(font_roboto), "Home Assistant...");
|
||||
- id: page2
|
||||
lambda: |-
|
||||
it.print(0, 10, id(font_roboto), "Configuring");
|
||||
it.print(0, 30, id(font_roboto), "sensors...");
|
||||
- id: page3
|
||||
lambda: |-
|
||||
it.print(0, 10, id(font_roboto), "Loading");
|
||||
it.print(0, 30, id(font_roboto), "important");
|
||||
it.print(0, 50, id(font_roboto), "update...");
|
||||
- id: page4
|
||||
lambda: |-
|
||||
it.image(0, 0, id(my_image), COLOR_OFF, COLOR_ON);
|
||||
|
||||
image:
|
||||
- file: "test_tdongle_image.png"
|
||||
type: RGB
|
||||
id: my_image
|
||||
|
||||
http_request:
|
||||
|
||||
font:
|
||||
- file: "gfonts://Roboto"
|
||||
id: font_roboto
|
||||
size: 20
|
||||
|
||||
interval:
|
||||
- interval: 5s
|
||||
then:
|
||||
- display.page.show_next: my_display
|
||||
- component.update: my_display
|
||||
387
active/device_esphome/loft-atom-echo.yaml
Normal file
387
active/device_esphome/loft-atom-echo.yaml
Normal file
@@ -0,0 +1,387 @@
|
||||
esphome:
|
||||
name: loft-atom-echo
|
||||
friendly_name: Loft Atom Echo
|
||||
|
||||
esp32:
|
||||
board: m5stack-atom
|
||||
cpu_frequency: 240MHz
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
level: debug
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
encryption:
|
||||
key: !secret loft_atom_echo_key
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
domain: .reeselink.com
|
||||
fast_connect: true
|
||||
enable_btm: true
|
||||
on_disconnect:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
blue: 0%
|
||||
red: 100%
|
||||
green: 0%
|
||||
effect: "Slow Pulse"
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "Loft-Atom-Echo"
|
||||
password: !secret hotspot_password
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret ota_password
|
||||
|
||||
captive_portal:
|
||||
|
||||
button:
|
||||
- platform: factory_reset
|
||||
id: factory_reset_btn
|
||||
name: Factory reset
|
||||
|
||||
i2s_audio:
|
||||
- id: i2s_audio_bus
|
||||
i2s_lrclk_pin: GPIO33
|
||||
i2s_bclk_pin: GPIO19
|
||||
|
||||
microphone:
|
||||
- platform: i2s_audio
|
||||
id: echo_microphone
|
||||
i2s_din_pin: GPIO23
|
||||
adc_type: external
|
||||
pdm: true
|
||||
sample_rate: 16000
|
||||
correct_dc_offset: true
|
||||
|
||||
speaker:
|
||||
- platform: i2s_audio
|
||||
id: echo_speaker
|
||||
i2s_dout_pin: GPIO22
|
||||
dac_type: external
|
||||
bits_per_sample: 16bit
|
||||
sample_rate: 16000
|
||||
channel: stereo # The Echo has poor playback audio quality when using mon audio
|
||||
buffer_duration: 60ms
|
||||
|
||||
media_player:
|
||||
- platform: speaker
|
||||
name: None
|
||||
id: echo_media_player
|
||||
announcement_pipeline:
|
||||
speaker: echo_speaker
|
||||
format: WAV
|
||||
codec_support_enabled: false
|
||||
buffer_size: 6000
|
||||
volume_min: 1
|
||||
volume_max: 1
|
||||
volume_initial: 1
|
||||
files:
|
||||
- id: timer_finished_wave_file
|
||||
file: https://github.com/esphome/wake-word-voice-assistants/raw/main/sounds/timer_finished.wav
|
||||
on_announcement:
|
||||
- if:
|
||||
condition:
|
||||
- microphone.is_capturing:
|
||||
then:
|
||||
- script.execute: stop_wake_word
|
||||
- light.turn_on:
|
||||
id: led
|
||||
blue: 100%
|
||||
red: 0%
|
||||
green: 0%
|
||||
brightness: 100%
|
||||
effect: none
|
||||
on_idle:
|
||||
- script.execute: start_wake_word
|
||||
- script.execute: reset_led
|
||||
|
||||
voice_assistant:
|
||||
id: va
|
||||
micro_wake_word:
|
||||
microphone:
|
||||
microphone: echo_microphone
|
||||
channels: 0
|
||||
gain_factor: 64
|
||||
media_player: echo_media_player
|
||||
noise_suppression_level: 2
|
||||
auto_gain: 31dBFS
|
||||
on_listening:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
blue: 100%
|
||||
red: 0%
|
||||
green: 0%
|
||||
effect: "Slow Pulse"
|
||||
on_stt_vad_end:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
blue: 100%
|
||||
red: 0%
|
||||
green: 0%
|
||||
effect: "Fast Pulse"
|
||||
on_tts_start:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
blue: 100%
|
||||
red: 0%
|
||||
green: 0%
|
||||
brightness: 100%
|
||||
effect: none
|
||||
on_end:
|
||||
# Handle the "nevermind" case where there is no announcement
|
||||
- wait_until:
|
||||
condition:
|
||||
- media_player.is_announcing:
|
||||
timeout: 0.5s
|
||||
# Restart only mWW if enabled; streaming wake words automatically restart
|
||||
- if:
|
||||
condition:
|
||||
- lambda: return id(wake_word_engine_location).state == "On device";
|
||||
then:
|
||||
- wait_until:
|
||||
- and:
|
||||
- not:
|
||||
voice_assistant.is_running:
|
||||
- not:
|
||||
speaker.is_playing:
|
||||
- lambda: id(va).set_use_wake_word(false);
|
||||
- micro_wake_word.start:
|
||||
- script.execute: reset_led
|
||||
on_error:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
red: 100%
|
||||
green: 0%
|
||||
blue: 0%
|
||||
brightness: 100%
|
||||
effect: none
|
||||
- delay: 2s
|
||||
- script.execute: reset_led
|
||||
on_client_connected:
|
||||
- delay: 2s # Give the api server time to settle
|
||||
- script.execute: start_wake_word
|
||||
on_client_disconnected:
|
||||
- script.execute: stop_wake_word
|
||||
on_timer_finished:
|
||||
- script.execute: stop_wake_word
|
||||
- wait_until:
|
||||
not:
|
||||
microphone.is_capturing:
|
||||
- switch.turn_on: timer_ringing
|
||||
- light.turn_on:
|
||||
id: led
|
||||
red: 0%
|
||||
green: 100%
|
||||
blue: 0%
|
||||
brightness: 100%
|
||||
effect: "Fast Pulse"
|
||||
- wait_until:
|
||||
- switch.is_off: timer_ringing
|
||||
- light.turn_off: led
|
||||
- switch.turn_off: timer_ringing
|
||||
|
||||
binary_sensor:
|
||||
# button does the following:
|
||||
# short click - stop a timer
|
||||
# if no timer then restart either microwakeword or voice assistant continuous
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO39
|
||||
inverted: true
|
||||
name: Button
|
||||
disabled_by_default: true
|
||||
entity_category: diagnostic
|
||||
id: echo_button
|
||||
on_multi_click:
|
||||
- timing:
|
||||
- ON for at least 50ms
|
||||
- OFF for at least 50ms
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
switch.is_on: timer_ringing
|
||||
then:
|
||||
- switch.turn_off: timer_ringing
|
||||
else:
|
||||
- script.execute: start_wake_word
|
||||
- timing:
|
||||
- ON for at least 10s
|
||||
then:
|
||||
- button.press: factory_reset_btn
|
||||
|
||||
light:
|
||||
- platform: esp32_rmt_led_strip
|
||||
id: led
|
||||
name: None
|
||||
disabled_by_default: true
|
||||
entity_category: config
|
||||
pin: GPIO27
|
||||
default_transition_length: 0s
|
||||
chipset: SK6812
|
||||
num_leds: 1
|
||||
rgb_order: grb
|
||||
effects:
|
||||
- pulse:
|
||||
name: "Slow Pulse"
|
||||
transition_length: 250ms
|
||||
update_interval: 250ms
|
||||
min_brightness: 50%
|
||||
max_brightness: 100%
|
||||
- pulse:
|
||||
name: "Fast Pulse"
|
||||
transition_length: 100ms
|
||||
update_interval: 100ms
|
||||
min_brightness: 50%
|
||||
max_brightness: 100%
|
||||
|
||||
script:
|
||||
- id: reset_led
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
- lambda: return id(wake_word_engine_location).state == "On device";
|
||||
- switch.is_on: use_listen_light
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
red: 100%
|
||||
green: 89%
|
||||
blue: 71%
|
||||
brightness: 60%
|
||||
effect: none
|
||||
else:
|
||||
- if:
|
||||
condition:
|
||||
- lambda: return id(wake_word_engine_location).state != "On device";
|
||||
- switch.is_on: use_listen_light
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
red: 0%
|
||||
green: 100%
|
||||
blue: 100%
|
||||
brightness: 60%
|
||||
effect: none
|
||||
else:
|
||||
- light.turn_off: led
|
||||
- id: start_wake_word
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
and:
|
||||
- not:
|
||||
- voice_assistant.is_running:
|
||||
- lambda: return id(wake_word_engine_location).state == "On device";
|
||||
then:
|
||||
- lambda: id(va).set_use_wake_word(false);
|
||||
- micro_wake_word.start:
|
||||
- if:
|
||||
condition:
|
||||
and:
|
||||
- not:
|
||||
- voice_assistant.is_running:
|
||||
- lambda: return id(wake_word_engine_location).state == "In Home Assistant";
|
||||
then:
|
||||
- lambda: id(va).set_use_wake_word(true);
|
||||
- voice_assistant.start_continuous:
|
||||
- id: stop_wake_word
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return id(wake_word_engine_location).state == "In Home Assistant";
|
||||
then:
|
||||
- lambda: id(va).set_use_wake_word(false);
|
||||
- voice_assistant.stop:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return id(wake_word_engine_location).state == "On device";
|
||||
then:
|
||||
- micro_wake_word.stop:
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
name: Use listen light
|
||||
id: use_listen_light
|
||||
optimistic: true
|
||||
restore_mode: RESTORE_DEFAULT_ON
|
||||
entity_category: config
|
||||
on_turn_on:
|
||||
- script.execute: reset_led
|
||||
on_turn_off:
|
||||
- script.execute: reset_led
|
||||
- platform: template
|
||||
id: timer_ringing
|
||||
optimistic: true
|
||||
restore_mode: ALWAYS_OFF
|
||||
on_turn_off:
|
||||
# Turn off the repeat mode and disable the pause between playlist items
|
||||
- lambda: |-
|
||||
id(echo_media_player)
|
||||
->make_call()
|
||||
.set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_OFF)
|
||||
.set_announcement(true)
|
||||
.perform();
|
||||
id(echo_media_player)->set_playlist_delay_ms(speaker::AudioPipelineType::ANNOUNCEMENT, 0);
|
||||
# Stop playing the alarm
|
||||
- media_player.stop:
|
||||
announcement: true
|
||||
on_turn_on:
|
||||
# Turn on the repeat mode and pause for 1000 ms between playlist items/repeats
|
||||
- lambda: |-
|
||||
id(echo_media_player)
|
||||
->make_call()
|
||||
.set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_ONE)
|
||||
.set_announcement(true)
|
||||
.perform();
|
||||
id(echo_media_player)->set_playlist_delay_ms(speaker::AudioPipelineType::ANNOUNCEMENT, 1000);
|
||||
- media_player.speaker.play_on_device_media_file:
|
||||
media_file: timer_finished_wave_file
|
||||
announcement: true
|
||||
- delay: 15min
|
||||
- switch.turn_off: timer_ringing
|
||||
|
||||
select:
|
||||
- platform: template
|
||||
entity_category: config
|
||||
name: Wake word engine location
|
||||
id: wake_word_engine_location
|
||||
optimistic: true
|
||||
restore_value: true
|
||||
options:
|
||||
- In Home Assistant
|
||||
- On device
|
||||
initial_option: On device
|
||||
on_value:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return x == "In Home Assistant";
|
||||
then:
|
||||
- micro_wake_word.stop:
|
||||
- delay: 500ms
|
||||
- lambda: id(va).set_use_wake_word(true);
|
||||
- voice_assistant.start_continuous:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return x == "On device";
|
||||
then:
|
||||
- lambda: id(va).set_use_wake_word(false);
|
||||
- voice_assistant.stop:
|
||||
- delay: 500ms
|
||||
- micro_wake_word.start:
|
||||
|
||||
micro_wake_word:
|
||||
on_wake_word_detected:
|
||||
- voice_assistant.start:
|
||||
wake_word: !lambda return wake_word;
|
||||
vad:
|
||||
models:
|
||||
- model: okay_nabu
|
||||
- model: hey_mycroft
|
||||
- model: hey_jarvis
|
||||
BIN
active/device_esphome/test_tdongle_image.png
Normal file
BIN
active/device_esphome/test_tdongle_image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
BIN
active/device_esphome/test_tdongle_image.xcf
Normal file
BIN
active/device_esphome/test_tdongle_image.xcf
Normal file
Binary file not shown.
BIN
active/device_esphome/wake_word_triggered.flac
Normal file
BIN
active/device_esphome/wake_word_triggered.flac
Normal file
Binary file not shown.
BIN
active/device_esphome/wake_word_triggered.wav
Normal file
BIN
active/device_esphome/wake_word_triggered.wav
Normal file
Binary file not shown.
23
active/device_framework_16/framework_16.md
Normal file
23
active/device_framework_16/framework_16.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Framework Laptop 16
|
||||
|
||||
## Keyboard VIA
|
||||
|
||||
Access keyboard configuration at <https://keyboard.frame.work/>
|
||||
|
||||
You might need to add the qmk udev rules for the browser to access your keyboard. Follow below:
|
||||
|
||||
```bash
|
||||
sudo curl -o /etc/udev/rules.d/50-qmk.rules https://raw.githubusercontent.com/qmk/qmk_firmware/refs/heads/master/util/udev/50-qmk.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger
|
||||
```
|
||||
|
||||
## Beta Bios Updates
|
||||
|
||||
```bash
|
||||
# With charger attached
|
||||
sudo fwupdmgr enable-remote lvfs-testing
|
||||
sudo fwupdmgr refresh --force
|
||||
sudo fwupdmgr get-updates
|
||||
sudo fwupdmgr update
|
||||
```
|
||||
BIN
active/device_framework_16/icc_profile.icm
Normal file
BIN
active/device_framework_16/icc_profile.icm
Normal file
Binary file not shown.
710
active/device_home_assistant/home_assistant.md
Normal file
710
active/device_home_assistant/home_assistant.md
Normal file
@@ -0,0 +1,710 @@
|
||||
# Home Assistant
|
||||
|
||||
- [Home Assistant](#home-assistant)
|
||||
- [Certificates](#certificates)
|
||||
- [Setup and Configuration](#setup-and-configuration)
|
||||
- [Schlage Door Lock](#schlage-door-lock)
|
||||
- [Philips Hue Lights](#philips-hue-lights)
|
||||
- [Shelly](#shelly)
|
||||
- [Barometer](#barometer)
|
||||
- [Relative Humidity Calculator](#relative-humidity-calculator)
|
||||
- [Font Colors](#font-colors)
|
||||
- [Light Indicator for Voice Assistant](#light-indicator-for-voice-assistant)
|
||||
- [Blank Button (Spacer)](#blank-button-spacer)
|
||||
- [Roku Remote](#roku-remote)
|
||||
- [Flair Vent Battery](#flair-vent-battery)
|
||||
- [Voice](#voice)
|
||||
- [Changing the Voice of TTS](#changing-the-voice-of-tts)
|
||||
- [Custom Sentences](#custom-sentences)
|
||||
- [Overriding Default Sentences](#overriding-default-sentences)
|
||||
- [Notifications](#notifications)
|
||||
- [Unifi Cameras](#unifi-cameras)
|
||||
- [Multiple Entity Triggers with Custom Names](#multiple-entity-triggers-with-custom-names)
|
||||
- [Philips Hue Switches](#philips-hue-switches)
|
||||
- [Datetimes](#datetimes)
|
||||
- [LG TV Switch](#lg-tv-switch)
|
||||
|
||||
## Certificates
|
||||
|
||||
Note, self signed certs won't work on the hass android app.
|
||||
|
||||
```bash
|
||||
# Generate the key/cert
|
||||
# Note, 36159 days == 99 years
|
||||
openssl req \
|
||||
-sha256 \
|
||||
-addext "subjectAltName = IP:10.2.0.230" \
|
||||
-newkey rsa:4096 \
|
||||
-nodes \
|
||||
-keyout privkey.pem \
|
||||
-x509 \
|
||||
-days 36159 \
|
||||
-out fullchain.pem
|
||||
|
||||
http:
|
||||
server_port: 8123
|
||||
ssl_certificate: /ssl/fullchain.pem
|
||||
ssl_key: /ssl/privkey.pem
|
||||
```
|
||||
|
||||
## Setup and Configuration
|
||||
|
||||
### Schlage Door Lock
|
||||
|
||||
1. Install Z-wave
|
||||
2. Install z-wave JS module
|
||||
3. Add device -> How do you want to add your device -> Legacy Secure
|
||||
4. Disconnect and Reconnect the battery on the lock
|
||||
5. Press and hold the zwave button until the light turns solid red, release and it should flash red
|
||||
1. (OR) Enter programming pin on lock -> 0 (this may take a few attempts, don't click the pair button)
|
||||
|
||||
If the lock ever disconnects you can safely delete it from home assistant and re-interview. It will
|
||||
set back up with the correct entity IDs and automations/dashboards will work just fine.
|
||||
|
||||
### Philips Hue Lights
|
||||
|
||||
1. I configure all philips hue lights through zigbee directly connected to HA
|
||||
|
||||
hue lights support color_temp in mireds, here are some mired-kelvin conversions:
|
||||
|
||||
| Kelvin | Mired |
|
||||
| ------ | ----- |
|
||||
| 6000 | 167 |
|
||||
| 4000 | 250 |
|
||||
| 2600 | 385 |
|
||||
|
||||
### Shelly
|
||||
|
||||
1. Outbound Websocket `wss://homeassistant.reeseapps.com/api/shelly/ws`
|
||||
|
||||
Shelly devices can act as "passive" or "active" bluetooth scanners. Both of these configurations
|
||||
allow home assistant to proxy bluetooth connections through shelly devices, significantly extending
|
||||
the range of your home assistant's bluetooth capabilities. Active scanning uses more power but
|
||||
is quicker to pick up and transmit device information. Note that "gateway mode" is not required,
|
||||
just enable bluetooth and rpc or select "active" from the configuration menu for the shelly
|
||||
device.
|
||||
|
||||
#### Barometer
|
||||
|
||||
<https://www.thoughtco.com/how-to-read-a-barometer-3444043>
|
||||
|
||||
A barometric reading over 30.20 inHg is generally considered high, and high pressure is associated with clear skies and calm weather.
|
||||
|
||||
If the reading is over 30.20 inHg (102268.9 Pa or 1022.689 mb):
|
||||
|
||||
- Rising or steady pressure means continued fair weather.
|
||||
- Slowly falling pressure means fair weather.
|
||||
- Rapidly falling pressure means cloudy and warmer conditions.
|
||||
|
||||
A barometric reading in the range of 29.80 and 30.20 inHg can be considered normal, and normal pressure is associated with steady weather.
|
||||
|
||||
If the reading falls between 29.80 and 30.20 inHg (100914.4–102268.9 Pa or 1022.689–1009.144 mb):
|
||||
|
||||
- Rising or steady pressure means present conditions will continue.
|
||||
- Slowly falling pressure means little change in the weather.
|
||||
- Rapidly falling pressure means that rain is likely, or snow if it is cold enough.
|
||||
|
||||
A barometric reading below 29.80 inHg is generally considered low, and low pressure is associated with warm air and rainstorms.
|
||||
|
||||
If the reading is under 29.80 inHg (100914.4 Pa or 1009.144 mb):
|
||||
|
||||
- Rising or steady pressure indicates clearing and cooler weather.
|
||||
- Slowly falling pressure indicates rain.
|
||||
- Rapidly falling pressure indicates a storm is coming.
|
||||
|
||||
A basic automation would look like
|
||||
|
||||
```yaml
|
||||
It's {{ int(states("sensor.grouse_temp")) }} degrees and {{ states("weather.grouse_weather") }}. The relative humidity is {{ states("sensor.grouse_humidity") }}%. I'm seeing {{ int(states("sensor.grouse_wind_speed")) }}mph wind with gusts up to {{ int(states("sensor.grouse_wind_gust")) }}mph.
|
||||
|
||||
{% set pressure = float(states("sensor.grouse_rel_pressure")) %}
|
||||
The barometer reads {{ pressure }}inHg.
|
||||
{% if pressure > 30.20 %}
|
||||
Fair weather is expected
|
||||
{% elif pressure > 29.80 %}
|
||||
Rain is possible
|
||||
{% else %}
|
||||
Rain is coming.
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
#### Relative Humidity Calculator
|
||||
|
||||
<https://www.wikihow.com/Calculate-Humidity>
|
||||
|
||||
You can calculate the relative humidity of the outdoor air if warmed to indoor temperatures like so:
|
||||
|
||||
```jinja
|
||||
{% set dew_point = state_attr("weather.forecast_home", "dew_point") %}
|
||||
{% set air_temp_f = state_attr("climate.ecobee_thermostat", "current_temperature") %}
|
||||
{% set air_temp = (5/9)*(air_temp_f-32) %}
|
||||
{% set sat_vap_press = 6.11 * 10**((7.5*air_temp) / (237.3+air_temp)) %}
|
||||
{% set act_vap_press = 6.11 * 10**((7.5*dew_point) / (237.3+dew_point)) %}
|
||||
{% set rel_hum = 100*(act_vap_press / sat_vap_press) %}
|
||||
|
||||
{{ dew_point }}
|
||||
{{ air_temp }}
|
||||
{{ sat_vap_press }}
|
||||
{{ act_vap_press }}
|
||||
{{ rel_hum }}
|
||||
```
|
||||
|
||||
### Font Colors
|
||||
|
||||
```html
|
||||
<font color = {{ "green" if state_attr("climate.ecobee_thermostat", "current_humidity") > low_humidity and state_attr("climate.ecobee_thermostat", "current_humidity") < high_humidity else "red" }}>
|
||||
HVAC Humidity: {{ state_attr("climate.ecobee_thermostat", "current_humidity") }}%
|
||||
</font>
|
||||
```
|
||||
|
||||
### Light Indicator for Voice Assistant
|
||||
|
||||
```yaml
|
||||
alias: Flash Lights on Bedroom Voice Assistant Start
|
||||
description: ""
|
||||
triggers:
|
||||
- type: turned_on
|
||||
device_id: d50fa1ae499e88bf37225c7e82ed189b
|
||||
entity_id: 7ab2896ca3a55efd2e0ee9bba91fdf68
|
||||
domain: binary_sensor
|
||||
metadata:
|
||||
secondary: false
|
||||
trigger: device
|
||||
conditions: []
|
||||
actions:
|
||||
- action: scene.create
|
||||
metadata: {}
|
||||
data:
|
||||
scene_id: bedroombeforescene
|
||||
snapshot_entities:
|
||||
- light.main_bedroom_lamps
|
||||
- action: light.turn_on
|
||||
metadata: {}
|
||||
data:
|
||||
transition: 0.25
|
||||
brightness_step_pct: 5
|
||||
target:
|
||||
entity_id: light.main_bedroom_lamps
|
||||
- delay:
|
||||
hours: 0
|
||||
minutes: 0
|
||||
seconds: 0
|
||||
milliseconds: 250
|
||||
- action: scene.turn_on
|
||||
data:
|
||||
entity_id: scene.bedroombeforescene
|
||||
transition: 0.25
|
||||
mode: single
|
||||
```
|
||||
|
||||
### Blank Button (Spacer)
|
||||
|
||||
```yaml
|
||||
- type: button
|
||||
tap_action:
|
||||
action: none
|
||||
show_state: false
|
||||
show_name: false
|
||||
show_icon: false
|
||||
hold_action:
|
||||
action: none
|
||||
```
|
||||
|
||||
### Roku Remote
|
||||
|
||||
```yaml
|
||||
type: vertical-stack
|
||||
cards:
|
||||
- type: entity
|
||||
entity: select.left_living_room_application
|
||||
- square: true
|
||||
type: grid
|
||||
cards:
|
||||
- show_name: true
|
||||
show_icon: true
|
||||
type: button
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: remote.send_command
|
||||
target:
|
||||
entity_id: remote.left_living_room
|
||||
data:
|
||||
command: power
|
||||
entity: remote.left_living_room
|
||||
icon: mdi:power
|
||||
name: power
|
||||
show_state: false
|
||||
hold_action:
|
||||
action: none
|
||||
- show_name: true
|
||||
show_icon: true
|
||||
type: button
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: remote.send_command
|
||||
target:
|
||||
entity_id: remote.left_living_room
|
||||
data:
|
||||
command: volume_down
|
||||
entity: remote.left_living_room
|
||||
icon: mdi:volume-minus
|
||||
name: volume down / hold mute
|
||||
show_state: false
|
||||
hold_action:
|
||||
action: perform-action
|
||||
perform_action: remote.send_command
|
||||
target:
|
||||
entity_id: remote.left_living_room
|
||||
data:
|
||||
command: volume_mute
|
||||
- show_name: true
|
||||
show_icon: true
|
||||
type: button
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: remote.send_command
|
||||
target:
|
||||
entity_id: remote.left_living_room
|
||||
data:
|
||||
command: volume_up
|
||||
entity: remote.left_living_room
|
||||
icon: mdi:volume-plus
|
||||
name: volume up / hold mute
|
||||
show_state: false
|
||||
"hold_action:":
|
||||
action: perform-action
|
||||
perform_action: remote.send_command
|
||||
target:
|
||||
entity_id: remote.left_living_room
|
||||
data:
|
||||
command: volume_mute
|
||||
- show_name: true
|
||||
show_icon: true
|
||||
type: button
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: remote.send_command
|
||||
target:
|
||||
entity_id: remote.left_living_room
|
||||
data:
|
||||
command: back
|
||||
entity: remote.left_living_room
|
||||
icon: mdi:undo
|
||||
name: back
|
||||
show_state: false
|
||||
hold_action:
|
||||
action: none
|
||||
- show_name: true
|
||||
show_icon: true
|
||||
type: button
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: remote.send_command
|
||||
target:
|
||||
entity_id: remote.left_living_room
|
||||
data:
|
||||
command: up
|
||||
entity: remote.left_living_room
|
||||
icon: mdi:arrow-up-bold
|
||||
name: up
|
||||
hold_action:
|
||||
action: none
|
||||
- show_name: true
|
||||
show_icon: true
|
||||
type: button
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: remote.send_command
|
||||
target:
|
||||
entity_id: remote.left_living_room
|
||||
data:
|
||||
command: home
|
||||
entity: remote.left_living_room
|
||||
icon: mdi:home
|
||||
name: home
|
||||
hold_action:
|
||||
action: none
|
||||
- show_name: true
|
||||
show_icon: true
|
||||
type: button
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: remote.send_command
|
||||
target:
|
||||
entity_id: remote.left_living_room
|
||||
data:
|
||||
command: left
|
||||
entity: remote.left_living_room
|
||||
icon: mdi:arrow-left-bold
|
||||
name: left
|
||||
hold_action:
|
||||
action: none
|
||||
- show_name: true
|
||||
show_icon: true
|
||||
type: button
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: remote.send_command
|
||||
target:
|
||||
entity_id: remote.left_living_room
|
||||
data:
|
||||
command: select
|
||||
entity: remote.left_living_room
|
||||
icon: mdi:select-all
|
||||
name: select
|
||||
hold_action:
|
||||
action: none
|
||||
- show_name: true
|
||||
show_icon: true
|
||||
type: button
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: remote.send_command
|
||||
target:
|
||||
entity_id: remote.left_living_room
|
||||
data:
|
||||
command: right
|
||||
entity: remote.left_living_room
|
||||
icon: mdi:arrow-right-bold
|
||||
name: right
|
||||
hold_action:
|
||||
action: none
|
||||
- show_name: true
|
||||
show_icon: true
|
||||
type: button
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: remote.send_command
|
||||
target:
|
||||
entity_id: remote.left_living_room
|
||||
data:
|
||||
command: play
|
||||
entity: remote.left_living_room
|
||||
icon: mdi:play-pause
|
||||
name: play/pause
|
||||
hold_action:
|
||||
action: none
|
||||
- show_name: true
|
||||
show_icon: true
|
||||
type: button
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: remote.send_command
|
||||
target:
|
||||
entity_id: remote.left_living_room
|
||||
data:
|
||||
command: down
|
||||
entity: remote.left_living_room
|
||||
icon: mdi:arrow-down-bold
|
||||
name: down
|
||||
hold_action:
|
||||
action: none
|
||||
- show_name: true
|
||||
show_icon: true
|
||||
type: button
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: remote.send_command
|
||||
target:
|
||||
entity_id: remote.left_living_room
|
||||
data:
|
||||
command: info
|
||||
entity: remote.left_living_room
|
||||
icon: mdi:wrench
|
||||
name: settings
|
||||
hold_action:
|
||||
action: none
|
||||
title: Left Living Room TV
|
||||
```
|
||||
|
||||
### Flair Vent Battery
|
||||
|
||||
Flair vents report low battery at 2.4v. 3v is nominal/full.
|
||||
|
||||
```yaml
|
||||
{% set volt_min=2.4 %}
|
||||
{% set volt_max=3.0 %}
|
||||
{% set volt_diff_max=0.6 %}
|
||||
|
||||
{{ (min(float(states("sensor.main_bedroom_29bf_voltage")) - volt_min, volt_diff_max) / volt_diff_max) * 100 }}
|
||||
```
|
||||
|
||||
## Voice
|
||||
|
||||
### Changing the Voice of TTS
|
||||
|
||||
Select a media player -> play TTS -> select voice -> copy voice ID.
|
||||
|
||||
```yaml
|
||||
options:
|
||||
voice: DavisNeural||chat
|
||||
```
|
||||
|
||||
### Custom Sentences
|
||||
|
||||
<https://developers.home-assistant.io/docs/voice/intent-recognition/template-sentence-syntax/#sentence-templates-syntax>
|
||||
|
||||
### Overriding Default Sentences
|
||||
|
||||
1. Identify if your sentence conflicts with [Home Assistant's default
|
||||
sentences](https://github.com/OHF-Voice/intents/tree/main/sentences/en)
|
||||
2. Create a new file at `/config/custom_sentences/en/overrides.yaml`
|
||||
3. As an example, to override the `HassGetWeather` sentence:
|
||||
1. Copy the contents of `weather_HassGetWeather.yaml` into `overrides.yaml`
|
||||
2. Rename `HassGetWeather` to `HassGetWeather_Custom`
|
||||
3. Delete the required context `weather`
|
||||
4. Now in `configuration.yaml`, under a section called `intent_script`, add the following
|
||||
|
||||
```yaml
|
||||
HassGetWeather_Custom:
|
||||
speech:
|
||||
text: >-
|
||||
It's {{ int(states("sensor.backyard_weather_station_temp")) }} degrees
|
||||
with {{ states("sensor.backyard_weather_station_humidity") }}% humidity.
|
||||
I'm seeing {{ int(states("sensor.backyard_weather_station_wind_speed"))
|
||||
}}mph wind. It's rained {{
|
||||
int(states("sensor.backyard_weather_station_hourly_rain_rate")) }} inches
|
||||
in the last hour.
|
||||
```
|
||||
|
||||
5. Restart Home Assistant
|
||||
6. Navigate to Settings -> Voice Assistants -> Click the 3 dots next to
|
||||
your voice assistant -> Debug -> Click the icon in the top right -> Run
|
||||
text pipeline -> "What's the weather"
|
||||
|
||||
## Notifications
|
||||
|
||||
Notification Information:
|
||||
|
||||
<https://www.home-assistant.io/docs/automation/templating/>
|
||||
|
||||
```yaml
|
||||
Triggered by {{ trigger.entity_id }}, Date: {{ now().strftime('%Y-%m-%d') }}, Time: {{ now().strftime('%H:%M') }}
|
||||
```
|
||||
|
||||
## Unifi Cameras
|
||||
|
||||
Create image/video previews of events with the following automation:
|
||||
|
||||
```yaml
|
||||
alias: Vehicle Driveway Notification
|
||||
description: Sends a notification with video upon motion detection.
|
||||
triggers:
|
||||
- entity_id:
|
||||
- binary_sensor.driveway_camera_vehicle_detected
|
||||
trigger: state
|
||||
from: "on"
|
||||
to: "off"
|
||||
actions:
|
||||
- data:
|
||||
message: Vehicle detected on Driveway Camera
|
||||
data:
|
||||
image: >-
|
||||
/api/unifiprotect/thumbnail/{{ config_entry_id(trigger.entity_id)
|
||||
}}/{{ trigger.from_state.attributes.event_id }}
|
||||
video: >-
|
||||
/api/unifiprotect/video/{{ config_entry_id(trigger.entity_id) }}/{{
|
||||
trigger.from_state.attributes.event_id }}
|
||||
action: notify.notify
|
||||
mode: single
|
||||
max_exceeded: silent
|
||||
```
|
||||
|
||||
## Multiple Entity Triggers with Custom Names
|
||||
|
||||
You can set an "id" for a trigger that can be used as a human readable name.
|
||||
|
||||
```yaml
|
||||
alias: Notify when a Door Opened
|
||||
description: ""
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id:
|
||||
- binary_sensor.my_front_door
|
||||
from: "off"
|
||||
to: "on"
|
||||
id: Front Door
|
||||
- trigger: state
|
||||
entity_id:
|
||||
- binary_sensor.my_back_door
|
||||
from: "off"
|
||||
to: "on"
|
||||
id: Back Door
|
||||
- trigger: state
|
||||
entity_id:
|
||||
- binary_sensor.super_secret_door
|
||||
from: "off"
|
||||
to: "on"
|
||||
id: Trap Door
|
||||
conditions: []
|
||||
actions:
|
||||
- action: notify.notify
|
||||
metadata: {}
|
||||
data:
|
||||
message: "{{ trigger.id }} Opened"
|
||||
mode: single
|
||||
```
|
||||
|
||||
## Philips Hue Switches
|
||||
|
||||
Philips Hue Switches don't expose entities, but rather trigger "zha_event" events.
|
||||
|
||||
To see events fired by these devices: Developer tools -> Events -> Listen to events `zha_event`
|
||||
|
||||
You can use this in automations like so:
|
||||
|
||||
```yaml
|
||||
alias: Some Switch
|
||||
description: ""
|
||||
triggers:
|
||||
- device_id: bb54b111ec77fb7d5356bb600789098f
|
||||
domain: zha
|
||||
type: remote_button_short_press
|
||||
subtype: turn_on
|
||||
trigger: device
|
||||
id: "on"
|
||||
- device_id: bb54b111ec77fb7d5356bb600789098f
|
||||
domain: zha
|
||||
type: remote_button_long_press
|
||||
subtype: turn_on
|
||||
trigger: device
|
||||
id: on-con
|
||||
conditions: []
|
||||
actions:
|
||||
- action: scene.turn_on
|
||||
metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
entity_id: scene.some_scene
|
||||
mode: single
|
||||
```
|
||||
|
||||
## Datetimes
|
||||
|
||||
Stolen from Reddit
|
||||
|
||||
```yaml
|
||||
## Set placeholder templates for reference in this template
|
||||
## 'dt' substitutes 'now()'
|
||||
## eg. if currently 5 March 2024 at 09:08:07 (AM)
|
||||
eg_now = {% set eg_now = "2024-03-05 09:08:07.123456+00:00" %}{{ eg_now }}
|
||||
dt = {% set dt = eg_now | as_datetime %}{{ dt }}
|
||||
ts = {% set ts = eg_now | as_timestamp %}{{ ts }}
|
||||
|
||||
## Basic Time & Date Functions
|
||||
time_now: {{ now() }}
|
||||
time_local: {{ now() | as_local }}
|
||||
time_timestamp: {{ now() | as_timestamp }}
|
||||
|
||||
## Time Conversions
|
||||
seconds_per_min : {% set spm = 60 | int %}{{ spm }}
|
||||
seconds_per_hour: {% set sph = ( spm * 60 ) | int %}{{ sph }}
|
||||
seconds_per_day : {% set spd = 86400 | int %}{{ spd }}
|
||||
seconds_per_week: {% set spw = ( spd * 7 ) | int %}{{ spw }}
|
||||
minutes_per_day : {% set mpd = ( spd / 60 ) | int %}{{ mpd }}
|
||||
minutes_per_week: {% set mpw = ( mpd * 7 ) | int %}{{ mpw }}
|
||||
hours_per_week : {% set hpw = ( 24 * 7 ) | int %}{{ hpw }}
|
||||
|
||||
## Time Calculations
|
||||
## with DATETIME use timedelta:
|
||||
* CURRENT TIME : {{ dt }}
|
||||
+ 1 YEAR : {{ dt + timedelta(days=365) }}
|
||||
- 1 DAY (24H) : {{ dt - timedelta(days=1) }}
|
||||
+ 3 DAYS (72H) : {{ dt + timedelta(days=3) }}
|
||||
- 3 HOURS : {{ dt - timedelta(hours=3) }}
|
||||
+ 1 HR 26 MIN : {{ dt + timedelta(hours=1, minutes=26) }}
|
||||
+ 1D 2H 3M 4S : {{ dt + timedelta(days=1, hours=2, minutes=3, seconds=4) }}
|
||||
|
||||
## with TIMESTAMP use maths and then convert:
|
||||
## Referencing earlier calculations for ease
|
||||
* TIMESTAMP : {{ ts }}
|
||||
* CURRENT TIME : {{ ts | as_datetime }}
|
||||
+ 1 YEAR : {{ ( ts + (spd * 365) ) | as_datetime }}
|
||||
- 1 DAY (24H) : {{ ( ts - spd ) | as_datetime }}
|
||||
+ 3 DAYS (72H) : {{ ( ts + (spd * 3) ) | as_datetime }}
|
||||
- 3 HOURS : {{ ( ts - (sph * 3) ) | as_datetime }}
|
||||
+ 1 HR 26 MIN : {{ ( ts + sph + (spm * 26) ) | as_datetime }}
|
||||
+ 1D 2H 3M 4S : {{ ( ts + spd + (sph * 2) + (spm * 3) + 4 ) | as_datetime }}
|
||||
|
||||
## Adjusting Time & Date For Calculations
|
||||
Start Of Today: {% set start_today = dt.replace(hour=0, minute=0, second=0, microsecond=0) %}{{ start_today }}
|
||||
End Of Today : {% set start_tomorrow = start_today + timedelta(days=1) %}{{ start_tomorrow }}
|
||||
|
||||
## Use Relative Time For DATETIME in the PAST
|
||||
relative_time: {{ relative_time( start_today ) }} ago
|
||||
|
||||
## For time in the FUTURE you can use:
|
||||
{% set current_time = dt %}{% set future_time = as_local(dt) %}{% set time_distance = future_time - current_time %}
|
||||
relative_future: In {{ relative_time(current_time - time_distance) }}
|
||||
|
||||
## Use Time Templates combined with History Stats Sensor:
|
||||
sensor:
|
||||
- platform: history_stats
|
||||
name: Lamp ON today
|
||||
entity_id: light.my_lamp
|
||||
state: "on"
|
||||
```
|
||||
|
||||
Stolen from <https://www.fabriziomusacchio.com/blog/2021-08-15-strftime_Cheat_Sheet/>
|
||||
|
||||
| Format | Example | Description |
|
||||
| ------ | ------------------------ | -------------------------------------------------------------------------------------------------- |
|
||||
| %c | Thu Jan 28 12:32:01 2014 | locale’s appropriate date and time representation |
|
||||
| %D | 23/05/12 | formats the date |
|
||||
| %F | 2002-01-30 | date in ISO 8601 format YYYY-MM-DD |
|
||||
| %x | 02/10/11 | locale’s appropriate date representation |
|
||||
| %X | 14:22:01 | locale’s appropriate time representation |
|
||||
| %r | 3:44:12 AM | 12-hour time |
|
||||
| %R | 15:21 | 24-hour time HH:MM |
|
||||
| %T | 15:21:59 | time in ISO 8601 format HH:MM:SS |
|
||||
| %A | Monday | full weekday name |
|
||||
| %a | Mon | abbreviated weekday name |
|
||||
| %w | 0-6 | day of the week with Sunday as 0 |
|
||||
| %d | 01-31 | day of the month (with a leading zero) |
|
||||
| %e | 1-31 | day of the month (without a leading zero) |
|
||||
| %B | April | full month name |
|
||||
| %b | Apr | abbreviated month name |
|
||||
| %m | 01-12 | month of the year (with a leading zero) |
|
||||
| %-m | 1-12 | month of the year (without a leading zero) |
|
||||
| %Y | 2003 | year |
|
||||
| %y | 00-99 | year without a century (last two digits, with a leading zero) |
|
||||
| %-y | 0-99 | year without a century (last two digits, without a leading zero) |
|
||||
| %H | 00-23 | hour of the day, 24-hour time (with a leading zero) |
|
||||
| %k | 0-23 | hour of the day, 24-hour time (without a leading zero) |
|
||||
| %I | 01-11 | hour of the day, 12-hour time (with a leading zero) |
|
||||
| %-I | 1-11 | hour of the day, 12-hour time (without a leading zero) |
|
||||
| %P | am, pm | am or pm designation |
|
||||
| %p | AM, PM | AM or PM designation |
|
||||
| %M | 00-59 | minute of the hour (with a leading zero) |
|
||||
| %-M | 0-59 | minute of the hour (without a leading zero) |
|
||||
| %S | 00-60 | second of the minute (with a leading zero) |
|
||||
| %-S | 0-60 | second of the minute (without a leading zero) |
|
||||
| %f | 000000-999999 | microsecond of the second (with a leading zero) |
|
||||
| %Z | UTC | timezone name or abbreviation |
|
||||
| %z | +0000 | UTC offset in the form +HHMM or -HHMM |
|
||||
| %s | | amount of seconds since 1970-01-01 00:00:00 UTC |
|
||||
| %% | | % sign |
|
||||
| %j | 001-366 | day of the year (with a leading zeroes) |
|
||||
| %U | 00-53 | week number with the first Sunday as the first day of week one |
|
||||
| %W | 00-53 | week number of the current year, starting with the first Monday as the first day of the first week |
|
||||
| %V | 01-53 | week number in ISO 8601 format |
|
||||
|
||||
## LG TV Switch
|
||||
|
||||
```yaml
|
||||
- platform: wake_on_lan
|
||||
mac: b4:b2:91:8e:ce:20
|
||||
name: loft_lg_tv_wol
|
||||
turn_off:
|
||||
service: media_player.turn_off
|
||||
target:
|
||||
device_id: "{{device_id('media_player.loft_lg_tv')}}"
|
||||
|
||||
- platform: wake_on_lan
|
||||
mac: 60:8d:26:2c:4d:45
|
||||
name: living_room_lg_tv_wol
|
||||
turn_off:
|
||||
service: media_player.turn_off
|
||||
target:
|
||||
device_id: "{{device_id('media_player.living_room_lg_tv')}}"
|
||||
```
|
||||
88
active/device_shelly/shelly.md
Normal file
88
active/device_shelly/shelly.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Shelly Devices
|
||||
|
||||
- [Shelly Devices](#shelly-devices)
|
||||
- [1PM Mini Gen4](#1pm-mini-gen4)
|
||||
- [Setup 1PM Mini Gen4](#setup-1pm-mini-gen4)
|
||||
- [Install 1PM Mini Gen4](#install-1pm-mini-gen4)
|
||||
- [Shelly Plug US](#shelly-plug-us)
|
||||
- [Shelly BLU Motion](#shelly-blu-motion)
|
||||
- [Shelly BLU Door/Window](#shelly-blu-doorwindow)
|
||||
- [Reset](#reset)
|
||||
- [Shelly Flood](#shelly-flood)
|
||||
|
||||
## 1PM Mini Gen4
|
||||
|
||||
### Setup 1PM Mini Gen4
|
||||
|
||||
1. Cut 1 white and 3 black pieces of 14 gauge wire to 3" long.
|
||||
2. Strip 1/4" from one side of each wire.
|
||||
3. Strip 1/2" from the other side of each wire.
|
||||
4. Connect the 1/4" side to the shelly. Tighten the screws until you can't turn them.
|
||||
5. Push line and neutral into a standard outlet. The wider receptacle is neutral.
|
||||
6. Press and hold the button for 10 seconds to factory reset. Light will flash on/off every 1/4 second.
|
||||
7. Press and hold the button for 5 seconds to turn on AP mode. Light will flash on/off every 1/2 second.
|
||||
8. Connect to shelly network.
|
||||
9. Navigate to <http://192.168.33.1>.
|
||||
10. Connect to wifi. The light should turn solid.
|
||||
11. Update firmware.
|
||||
12. Set a password for AP mode.
|
||||
13. Turn off the AP.
|
||||
14. In Unifi: Name the device, give it a fixed IP, set the icon.
|
||||
15. Navigate to the Shelly website via its IP address.
|
||||
16. Set a password for http access: Settings -> Authentication.
|
||||
17. Name the device: Settings -> Device name.
|
||||
18. Set Restore last known state of output/relay: Home -> Output -> Input/Output Settings.
|
||||
19. Enable Zigbee: Zigbee -> Enable.
|
||||
20. Connect Shelly to Home Assistant via Zigbee.
|
||||
21. Change switch type: Click on switch control -> Settings -> Show as.
|
||||
|
||||
### Install 1PM Mini Gen4
|
||||
|
||||
1. Cut 1 3" white wire for neutral bridge.
|
||||
2. Cut 2 3" black wires for line bridge and light switch input.
|
||||
3. Prepare 4 14 gauge wire connectors.
|
||||
|
||||
## Shelly Plug US
|
||||
|
||||
1. Connect to WiFi
|
||||
2. Set password for AP
|
||||
3. Disable AP
|
||||
4. Set password for device authentication
|
||||
5. Set Restore last known state of output/relay
|
||||
6. Set Device Name
|
||||
7. Enable Bluetooth Gateway
|
||||
8. Update Firmware
|
||||
|
||||
## Shelly BLU Motion
|
||||
|
||||
1. Download and install the Shelly Debug app
|
||||
2. Follow the instructions in the app to connect the device
|
||||
3. Update the firmware
|
||||
4. Enable encryption (generate a 6 digit code)
|
||||
5. "Read" from the device and copy the encryption key for home assistant
|
||||
|
||||
## Shelly BLU Door/Window
|
||||
|
||||
1. Download and install the Shelly Debug app
|
||||
2. Follow the instructions in the app to connect the device
|
||||
3. Update the firmware
|
||||
4. Create a new "login" in Bitwarden called "Shelly BLU DW " + name of device
|
||||
1. Password will be the encryption key
|
||||
2. Website should be the MAC address of the Shelly
|
||||
5. Generate a 6 digit code, send it to your phone then throw it away
|
||||
6. In the Shelly Debug app, enable encryption using the 6 digit code
|
||||
7. Copy the encryption and store in the password field
|
||||
8. Add to Home Assistant
|
||||
9. Unpair from Phone
|
||||
|
||||
### Reset
|
||||
|
||||
Resetting is super finnicky. You'll need to plug it in, press and hold the power button until the
|
||||
red light flashes quickly (not slowly, that's a reboot). You'll probably have to do it multiple
|
||||
times because they seem to reboot halfway through the reset process.
|
||||
|
||||
## Shelly Flood
|
||||
|
||||
1. In the web interface, ensure "CoIoT" is enabled and pointing to `<home assistant ip>:5683`.
|
||||
Allow 5683/udp from shelly flood to home assistant. If you don't do this Shelly Flood will
|
||||
not report its status correctly!
|
||||
0
kubernetes/graduated/iperf3/values.yaml → active/device_truenas/truenas.md
Executable file → Normal file
0
kubernetes/graduated/iperf3/values.yaml → active/device_truenas/truenas.md
Executable file → Normal file
99
active/device_yubikey/yubikey.md
Normal file
99
active/device_yubikey/yubikey.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Yubikey
|
||||
|
||||
- [Yubikey](#yubikey)
|
||||
- [Configuration](#configuration)
|
||||
- [Software](#software)
|
||||
- [GPG](#gpg)
|
||||
- [Saving GPG key to card](#saving-gpg-key-to-card)
|
||||
- [Using the GPG key on a Yubikey](#using-the-gpg-key-on-a-yubikey)
|
||||
- [Factory Reset](#factory-reset)
|
||||
|
||||
## Configuration
|
||||
|
||||
1. You will likely need the [udev
|
||||
rules](https://support.yubico.com/hc/en-us/articles/360013708900-Using-Your-YubiKey-with-Linux)
|
||||
to use the AppImage configuration tool on linux even if your udev version is above 244.
|
||||
|
||||
## Software
|
||||
|
||||
The [Yubikey Manager](https://www.yubico.com/support/download/yubikey-manager/) is deprecated.
|
||||
|
||||
Use the [Yubikey Authenticator](https://www.yubico.com/products/yubico-authenticator/) for GUI.
|
||||
|
||||
## GPG
|
||||
|
||||
### Saving GPG key to card
|
||||
|
||||
<https://support.yubico.com/hc/en-us/articles/360013790259-Using-Your-YubiKey-with-OpenPGP>
|
||||
|
||||
On Fedora you'll need to add the following polkit rules to access your smart card.
|
||||
|
||||
```bash
|
||||
export MY_USER=ducoterra
|
||||
echo <<EOF > /etc/polkit-1/rules.d/10-pcsc-custom.rules
|
||||
polkit.addRule(function(action, subject) {
|
||||
if (action.id == "org.debian.pcsc-lite.access_pcsc" &&
|
||||
subject.user == "${MY_USER}") {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
});
|
||||
|
||||
polkit.addRule(function(action, subject) {
|
||||
if (action.id == "org.debian.pcsc-lite.access_card" &&
|
||||
action.lookup("reader") == 'Yubico YubiKey OTP+FIDO+CCID 00 00' &&
|
||||
subject.user == "${MY_USER}") {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
});
|
||||
EOF
|
||||
```
|
||||
|
||||
Now you can add your key to your card.
|
||||
|
||||
```bash
|
||||
gpg --edit-key 1234ABC
|
||||
|
||||
# Save both the signature and authentication keys
|
||||
> keytocard
|
||||
|
||||
# Do not save or your key will be deleted locally
|
||||
> quit
|
||||
```
|
||||
|
||||
Check the keys on the yubikey with
|
||||
|
||||
```bash
|
||||
gpg --card-status
|
||||
```
|
||||
|
||||
Once your keys have been loaded, change the pin.
|
||||
|
||||
```bash
|
||||
gpg --change-pin
|
||||
```
|
||||
|
||||
### Using the GPG key on a Yubikey
|
||||
|
||||
<https://github.com/drduh/YubiKey-Guide?tab=readme-ov-file#notes>
|
||||
|
||||
```bash
|
||||
export GPG_EMAIL='myemail@example.com'
|
||||
|
||||
# Import the public key. Without this the key won't show up.
|
||||
gpg --auto-key-locate hkps://keys.openpgp.org --locate-keys ${GPG_EMAIL}
|
||||
|
||||
# Trust the key
|
||||
gpg --quick-set-ownertrust ${GPG_EMAIL} full
|
||||
|
||||
# Yubikey should now show up
|
||||
gpg --list-secret-keys
|
||||
```
|
||||
|
||||
### Factory Reset
|
||||
|
||||
```bash
|
||||
gpg --edit-card
|
||||
|
||||
> admin
|
||||
> factory-reset
|
||||
```
|
||||
13
active/kubernetes/kubernetes.md
Normal file
13
active/kubernetes/kubernetes.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Kubernetes
|
||||
|
||||
## CLI Tools
|
||||
|
||||
kubectl: <https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/>
|
||||
|
||||
helm: <https://helm.sh/docs/intro/install/>
|
||||
|
||||
## Install a Kubernetes Server
|
||||
|
||||
For k3s, see [k3s](/active/systemd_k3s/k3s.md)
|
||||
|
||||
For k0s, see [k0s](/active/systemd_k0s/k0s.md)
|
||||
@@ -12,7 +12,7 @@ helm repo add bitwarden https://charts.bitwarden.com/
|
||||
helm repo update
|
||||
|
||||
kubectl create namespace bitwarden
|
||||
helm show values bitwarden/self-host > kubernetes/incubating/bitwarden/values.yaml
|
||||
helm show values bitwarden/self-host > active/kubernetes_bitwarden/values.yaml
|
||||
|
||||
# Installation ID: https://bitwarden.com/host/
|
||||
# Optional argument for Have I Been Pwned: --from-literal=globalSettings__hibpApiKey="REPLACE" \
|
||||
@@ -25,5 +25,5 @@ kubectl create secret generic custom-secret -n bitwarden \
|
||||
--from-file=globalSettings__yubico__key=./secrets/bitwarden/yubico_secret \
|
||||
--from-file=SA_PASSWORD=./secrets/bitwarden/sa_password
|
||||
|
||||
helm upgrade bitwarden bitwarden/self-host --install --namespace bitwarden --values kubernetes/incubating/bitwarden/values.yaml
|
||||
helm upgrade bitwarden bitwarden/self-host --install --namespace bitwarden --values active/kubernetes_bitwarden/values.yaml
|
||||
```
|
||||
11
active/kubernetes_gitea/gitea-demo-values.yaml
Normal file
11
active/kubernetes_gitea/gitea-demo-values.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
service:
|
||||
http:
|
||||
type: LoadBalancer
|
||||
externalTrafficPolicy: Cluster
|
||||
annotations:
|
||||
metallb.io/allow-shared-ip: gitea
|
||||
ssh:
|
||||
type: LoadBalancer
|
||||
externalTrafficPolicy: Cluster
|
||||
annotations:
|
||||
metallb.io/allow-shared-ip: gitea
|
||||
@@ -21,14 +21,10 @@ ingress:
|
||||
persistence:
|
||||
enabled: true
|
||||
create: true
|
||||
storageClass: zfs-iscsi-enc0
|
||||
claimName: data-gitea-staging-0
|
||||
annotations:
|
||||
"helm.sh/resource-policy": keep
|
||||
|
||||
global:
|
||||
storageClass: zfs-iscsi-enc1
|
||||
|
||||
postgresql:
|
||||
enabled: true
|
||||
image:
|
||||
@@ -36,7 +32,6 @@ postgresql:
|
||||
primary:
|
||||
persistence:
|
||||
enabled: true
|
||||
storageClass: zfs-iscsi-enc1
|
||||
annotations:
|
||||
"helm.sh/resource-policy": keep
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Gitea
|
||||
|
||||
- [Gitea](#gitea)
|
||||
- [Demo](#demo)
|
||||
- [Staging](#staging)
|
||||
- [Install](#install)
|
||||
- [Backup and Restore](#backup-and-restore)
|
||||
@@ -14,6 +15,17 @@ they decide to change things. This is the first chart (besides ingress-nginx) wh
|
||||
we need to pay attention to the MetalLB annotation. This has been set in the values.yaml
|
||||
file.
|
||||
|
||||
## Demo
|
||||
|
||||
```bash
|
||||
helm upgrade --install \
|
||||
gitea \
|
||||
gitea-charts/gitea \
|
||||
--values active/kubernetes_gitea/gitea-demo-values.yaml \
|
||||
--namespace gitea \
|
||||
--create-namespace
|
||||
```
|
||||
|
||||
## Staging
|
||||
|
||||
There is a `gitea-staging.yaml` file with staging values. This should be installed in
|
||||
@@ -40,7 +52,7 @@ helm repo update
|
||||
helm upgrade --install \
|
||||
gitea \
|
||||
gitea-charts/gitea \
|
||||
--values kubernetes/graduated/gitea/gitea-values.yaml \
|
||||
--values active/kubernetes_gitea/gitea-values.yaml \
|
||||
--namespace gitea \
|
||||
--create-namespace
|
||||
```
|
||||
@@ -8,7 +8,7 @@ helm repo update
|
||||
helm upgrade --install my-grafana grafana/grafana \
|
||||
--namespace monitoring \
|
||||
--create-namespace \
|
||||
--values kubernetes/incubating/grafana/values.yaml
|
||||
--values active/kubernetes_grafana/values.yaml
|
||||
|
||||
kubectl get secret --namespace monitoring my-grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo
|
||||
```
|
||||
@@ -25,5 +25,5 @@ helm upgrade --install \
|
||||
kube-prometheus-stack \
|
||||
prometheus-community/kube-prometheus-stack \
|
||||
--namespace kube-system \
|
||||
--values kubernetes/incubating/grafana/helm-prom-stack-values.yaml
|
||||
--values active/kubernetes_grafana/helm-prom-stack-values.yaml
|
||||
```
|
||||
@@ -7,7 +7,7 @@ This creates a basic iperf3 server.
|
||||
```bash
|
||||
helm upgrade --install \
|
||||
iperf3 \
|
||||
./kubernetes/graduated/iperf3 \
|
||||
./active/kubernetes_iperf3/iperf3 \
|
||||
--namespace iperf3 \
|
||||
--create-namespace
|
||||
```
|
||||
```
|
||||
0
yq → active/kubernetes_iperf3/values.yaml
Normal file → Executable file
0
yq → active/kubernetes_iperf3/values.yaml
Normal file → Executable file
@@ -5,7 +5,7 @@
|
||||
```bash
|
||||
helm upgrade --install \
|
||||
jellyfin \
|
||||
./kubernetes/graduated/jellyfin \
|
||||
./active/kubernetes_jellyfin \
|
||||
--namespace jellyfin \
|
||||
--create-namespace
|
||||
```
|
||||
@@ -10,15 +10,14 @@
|
||||
|
||||
```bash
|
||||
# Download the updated template from github
|
||||
kubectl kustomize "github.com/rancher/local-path-provisioner/deploy?ref=v0.0.31" > kubernetes/graduated/local-path-provisioner/local-path-storage.yaml
|
||||
kubectl kustomize "github.com/rancher/local-path-provisioner/deploy?ref=v0.0.32" > active/kubernetes_local-path-provisioner/local-path-storage.yaml
|
||||
|
||||
# Apply customizations (ssd/hdd storage, read write many support)
|
||||
kubectl kustomize kubernetes/graduated/local-path-provisioner | kubectl apply -f -
|
||||
|
||||
# Create test pod
|
||||
kubectl apply -f systemd/graduated/k3s/tests/local-storage-test.yaml
|
||||
kubectl get pod -n default
|
||||
# Exec in and test - storage will be mounted at /storage
|
||||
kubectl exec -it -n default <local-storage-test> -- bash
|
||||
kubectl delete -f systemd/graduated/k3s/tests/local-storage-test.yaml
|
||||
kubectl kustomize active/kubernetes_local-path-provisioner | kubectl apply -f -
|
||||
```
|
||||
|
||||
Mark the class as default
|
||||
|
||||
```bash
|
||||
kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
|
||||
```
|
||||
@@ -176,7 +176,7 @@ spec:
|
||||
fieldPath: metadata.namespace
|
||||
- name: CONFIG_MOUNT_PATH
|
||||
value: /etc/config/
|
||||
image: rancher/local-path-provisioner:v0.0.31
|
||||
image: rancher/local-path-provisioner:v0.0.32
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: local-path-provisioner
|
||||
volumeMounts:
|
||||
@@ -5,12 +5,21 @@ below installs nimcraft. For each installation you'll want to create your own va
|
||||
with a new port. The server-downloader is called "minecraft_get_server" and is available on
|
||||
[Github](https://github.com/ducoterra/minecraft_get_server).
|
||||
|
||||
After installing, you can run admin commands (like whitelisting players) by
|
||||
attaching to the container:
|
||||
|
||||
```bash
|
||||
kubectl attach -it <pod>
|
||||
|
||||
> /whitelist add ducoterra
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
helm upgrade --install \
|
||||
testcraft \
|
||||
./kubernetes/graduated/minecraft \
|
||||
./active/kubernetes_minecraft \
|
||||
--namespace minecraft \
|
||||
--create-namespace
|
||||
```
|
||||
@@ -20,7 +29,7 @@ helm upgrade --install \
|
||||
```bash
|
||||
helm upgrade --install \
|
||||
nimcraft \
|
||||
./kubernetes/graduated/minecraft \
|
||||
./active/kubernetes_minecraft \
|
||||
--namespace minecraft \
|
||||
--create-namespace
|
||||
```
|
||||
@@ -30,7 +39,7 @@ helm upgrade --install \
|
||||
```bash
|
||||
helm upgrade --install \
|
||||
courtniecraft \
|
||||
./kubernetes/graduated/minecraft \
|
||||
./active/kubernetes_minecraft \
|
||||
--namespace minecraft \
|
||||
--create-namespace
|
||||
```
|
||||
@@ -40,7 +49,7 @@ helm upgrade --install \
|
||||
```bash
|
||||
helm upgrade --install \
|
||||
camcraft1 \
|
||||
./kubernetes/graduated/minecraft \
|
||||
./active/kubernetes_minecraft \
|
||||
--namespace minecraft \
|
||||
--create-namespace
|
||||
```
|
||||
@@ -56,10 +56,10 @@ spec:
|
||||
value: "1"
|
||||
resources:
|
||||
requests:
|
||||
memory: {{ div .Values.max_ram 2 }}Gi
|
||||
memory: "{{ div .Values.max_ram 2 }}Gi"
|
||||
cpu: 1m
|
||||
limits:
|
||||
memory: {{ add 1 .Values.max_ram }}Gi
|
||||
memory: "{{ add 1 .Values.max_ram }}Gi"
|
||||
cpu: {{ .Values.max_cpu | quote }}
|
||||
volumes:
|
||||
- name: data
|
||||
@@ -5,7 +5,6 @@ metadata:
|
||||
annotations:
|
||||
"helm.sh/resource-policy": keep
|
||||
spec:
|
||||
storageClassName: ssd
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
@@ -2,11 +2,7 @@ apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ .Release.Name }}
|
||||
annotations:
|
||||
metallb.universe.tf/address-pool: "external"
|
||||
external-dns.alpha.kubernetes.io/hostname: {{ .Release.Name }}.reeseapps.com
|
||||
spec:
|
||||
ipFamilies: ["IPv6"]
|
||||
externalTrafficPolicy: Cluster
|
||||
selector:
|
||||
app: {{ .Release.Name }}
|
||||
@@ -7,7 +7,7 @@ Snapdrop is a file sharing app that allows airdrop-like functionality over the w
|
||||
```bash
|
||||
helm upgrade --install \
|
||||
snapdrop \
|
||||
./kubernetes/graduated/snapdrop \
|
||||
./active/kubernetes_snapdrop \
|
||||
--namespace snapdrop \
|
||||
--create-namespace
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user