Added in progress katchi page
All checks were successful
Build and Publish Docker Image / build (push) Successful in 25s
All checks were successful
Build and Publish Docker Image / build (push) Successful in 25s
This commit is contained in:
@@ -9,6 +9,8 @@ colorScheme = "fire"
|
|||||||
defaultAppearance = "dark" # valid options: light or dark
|
defaultAppearance = "dark" # valid options: light or dark
|
||||||
autoSwitchAppearance = true
|
autoSwitchAppearance = true
|
||||||
|
|
||||||
|
opengraph_io_api_key = "bb9fc05a-0dbc-4748-87f7-b2bb698ae5a9"
|
||||||
|
|
||||||
enableA11y = false
|
enableA11y = false
|
||||||
enableSearch = true
|
enableSearch = true
|
||||||
enableCodeCopy = true
|
enableCodeCopy = true
|
||||||
@@ -86,7 +88,7 @@ forgejoDefaultServer = "https://v11.next.forgejo.org"
|
|||||||
showPagination = true
|
showPagination = true
|
||||||
invertPagination = false
|
invertPagination = false
|
||||||
showReadingTime = true
|
showReadingTime = true
|
||||||
showTableOfContents = false
|
showTableOfContents = true
|
||||||
# showRelatedContent = false
|
# showRelatedContent = false
|
||||||
# relatedContentLimit = 3
|
# relatedContentLimit = 3
|
||||||
showTaxonomies = false # Enable the display of taxonomies for the related article.
|
showTaxonomies = false # Enable the display of taxonomies for the related article.
|
||||||
|
|||||||
BIN
content/posts/katchi/featured.jpg
Normal file
BIN
content/posts/katchi/featured.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.9 MiB |
76
content/posts/katchi/index.md
Normal file
76
content/posts/katchi/index.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
+++
|
||||||
|
date = '2026-05-17T23:57:15-06:00'
|
||||||
|
draft = false
|
||||||
|
title = "Katchi, a dragon's best friend"
|
||||||
|
tags = ['kobold', 'esp32']
|
||||||
|
+++
|
||||||
|
|
||||||
|
## A smart-home for a Dragon
|
||||||
|
|
||||||
|
{{< typeit
|
||||||
|
tag=h3
|
||||||
|
speed=50
|
||||||
|
breakLines=false
|
||||||
|
loop=true
|
||||||
|
>}}
|
||||||
|
"It's the Future..."
|
||||||
|
"Dumb homes are so 2010"
|
||||||
|
"Is all of this really necessary?" — Concerned Friends
|
||||||
|
{{< /typeit >}}
|
||||||
|
|
||||||
|
### Smart-homes
|
||||||
|
|
||||||
|
The present state of smart-home choices is fairly acceptable. You have your major players, Google, Apple, Amazon and
|
||||||
|
their associated services like Google Home or Alexa. These systems are fairly easy to set up; plug in the new device,
|
||||||
|
type in some credentials or type a prompt on your phone, and done. Most of these systems rely on a central hub that
|
||||||
|
orchestrates the entire smart home.
|
||||||
|
|
||||||
|
But all these systems have one fatal annoyance. They all require access to the internet.
|
||||||
|
|
||||||
|
### Internet dependency
|
||||||
|
|
||||||
|
In recent years, it is common to run into issues with major providers. Privacy concerns, outages and the forced
|
||||||
|
obsolescence of existing systems put a lot of pressure on me when building my first smart home. Sure the big players
|
||||||
|
make it easy to set up and use, but for me the non-monetary cost was just too great. Besides the limitations in software,
|
||||||
|
knowing that if I had an internet outage, or god forbid, the provider has an outage, I would be shit out of luck in
|
||||||
|
turning off my lights turned me away from major providers.
|
||||||
|
|
||||||
|
### So what did I use?
|
||||||
|
|
||||||
|
After spending a lot of time frustrated with my options and dealing with the difficulties in automating and doing what
|
||||||
|
I wanted with my smart-home, I went down the rabbit hole of options and found
|
||||||
|
[Home Assistant](https://www.home-assistant.io/).
|
||||||
|

|
||||||
|
|
||||||
|
Unlike the big-name smart-homes, Home Assistant is a self-hosted option that runs on your own hardware and locally
|
||||||
|
connects to supported devices. It supports a wide range of [devices and integrations](https://www.home-assistant.io/integrations/?brands=featured)
|
||||||
|
and is fairly easy to set up.
|
||||||
|
|
||||||
|
I wont expound on it much more here, but I will link to the [getting started](https://www.home-assistant.io/installation/), [documentation](https://www.home-assistant.io/docs/)
|
||||||
|
and [community](https://community.home-assistant.io/) for more information.
|
||||||
|
|
||||||
|
### So what's the problem?
|
||||||
|
|
||||||
|
Of all the amazing options that Home Assistant gives us, it has a fairly significant miss; that being Smart Speaker integration.
|
||||||
|
|
||||||
|
## Home Assistant Smart Speaker
|
||||||
|
|
||||||
|
The options for Home Assistant smart speakers are quite limited, they only offer one official product as of the date of publishing this post.
|
||||||
|
|
||||||
|
{{< externalLink url="https://www.home-assistant.io/voice-pe/" >}}
|
||||||
|
|
||||||
|
While the Home Assistant Voice PE works decently, it is the only off-the-shelf option for Home Assistant which considering
|
||||||
|
all the freedom Home Assistant gives us, feels quite limiting. However, there is a solution.
|
||||||
|
|
||||||
|
### The Solution
|
||||||
|
|
||||||
|
Thankfully we are not constrained by the limitations of existing hardware thanks to microcontrollers, specifically the ESP family of microcontrollers.
|
||||||
|

|
||||||
|
|
||||||
|
Using [ESPHome](https://esphome.io) you can create a whole myriad of smart devices based on the [ESP32 microcontroller](https://www.espressif.com/en/products/socs/esp32). It provides a very diverse
|
||||||
|
family of options that can fit nearly any use-case. Think of it as an alternative to Arduino, where instead of writing C code
|
||||||
|
you can write yaml configuration files that dictate and configure your ESP device.
|
||||||
|
|
||||||
|
Knowing this, I set out to make my own Smart Speaker.
|
||||||
|
|
||||||
|
## Katchi the Kobold Smart Speaker
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.9 MiB |
@@ -1,10 +0,0 @@
|
|||||||
+++
|
|
||||||
date = '2026-05-17T15:26:49-06:00'
|
|
||||||
draft = false
|
|
||||||
title = 'My First Post'
|
|
||||||
tags = ['space']
|
|
||||||
+++
|
|
||||||
|
|
||||||
## A sub-title
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nibh nisl, vulputate eu lacus vitae, maximus molestie libero. Vestibulum laoreet, odio et sollicitudin sollicitudin, quam ligula tempus urna, sed sagittis eros eros ac felis. In tristique tortor vitae lacinia commodo. Mauris venenatis ultrices purus nec fermentum. Nunc sit amet aliquet metus. Morbi nisl felis, gravida ac consequat vitae, blandit eu libero. Curabitur porta est in dui elementum porttitor. Maecenas fermentum, tortor ac feugiat fringilla, orci sem sagittis massa, a congue risus ipsum vel massa. Aliquam sit amet nunc vulputate, facilisis neque in, faucibus nisl.
|
|
||||||
86
layouts/_shortcodes/externalLink.html
Normal file
86
layouts/_shortcodes/externalLink.html
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
{{ $url := .Get "url" }}
|
||||||
|
{{ $title := .Get "title" }}
|
||||||
|
{{ $description := .Get "description" }}
|
||||||
|
{{ $image := .Get "image" }}
|
||||||
|
{{ $favicon := .Get "favicon" }}
|
||||||
|
{{ $provider := .Get "provider" }}
|
||||||
|
{{ $noFetch := .Get "noFetch" | default false }}
|
||||||
|
|
||||||
|
{{ $cardClasses := "flex flex-col md:flex-row relative overflow-hidden rounded-lg border border-neutral-300 dark:border-neutral-600 hover:border-neutral-400 dark:hover:border-neutral-500 transition-colors not-prose" }}
|
||||||
|
|
||||||
|
{{ $parsedURL := urls.Parse $url }}
|
||||||
|
{{ $domain := $parsedURL.Host }}
|
||||||
|
|
||||||
|
{{ $fetchedTitle := "" }}
|
||||||
|
{{ $fetchedDescription := "" }}
|
||||||
|
{{ $fetchedImage := "" }}
|
||||||
|
{{ $fetchedFavicon := "" }}
|
||||||
|
{{ $fetchedProvider := "" }}
|
||||||
|
|
||||||
|
{{ if and (not $noFetch) $url }}
|
||||||
|
{{ with resources.GetRemote $url }}
|
||||||
|
{{ $content := .Content }}
|
||||||
|
|
||||||
|
{{ $ogTitleTags := findRE `<meta[^>]+property=["']og:title["'][^>]*>` $content 1 }}
|
||||||
|
{{ with index $ogTitleTags 0 }}
|
||||||
|
{{ $fetchedTitle = . | replaceRE `.*content=["']([^"']*)["'].*` `$1` }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ $ogDescTags := findRE `<meta[^>]+property=["']og:description["'][^>]*>` $content 1 }}
|
||||||
|
{{ with index $ogDescTags 0 }}
|
||||||
|
{{ $fetchedDescription = . | replaceRE `.*content=["']([^"']*)["'].*` `$1` }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ $ogImageTags := findRE `<meta[^>]+property=["']og:image["'][^>]*>` $content 1 }}
|
||||||
|
{{ with index $ogImageTags 0 }}
|
||||||
|
{{ $fetchedImage = . | replaceRE `.*content=["']([^"']*)["'].*` `$1` }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ $ogSiteNameTags := findRE `<meta[^>]+property=["']og:site_name["'][^>]*>` $content 1 }}
|
||||||
|
{{ with index $ogSiteNameTags 0 }}
|
||||||
|
{{ $fetchedProvider = . | replaceRE `.*content=["']([^"']*)["'].*` `$1` }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ $finalTitle := default $fetchedTitle $title }}
|
||||||
|
{{ $finalDescription := default $fetchedDescription $description }}
|
||||||
|
{{ $finalImage := default $fetchedImage $image }}
|
||||||
|
{{ $finalFavicon := default $fetchedFavicon $favicon }}
|
||||||
|
{{ $finalProvider := default $fetchedProvider $provider }}
|
||||||
|
|
||||||
|
{{ if not $finalProvider }}
|
||||||
|
{{ $finalProvider = $domain }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if not $finalFavicon }}
|
||||||
|
{{ $finalFavicon = printf "https://%s/favicon.ico" $domain }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
<a href="{{ $url }}" target="_blank" rel="noopener noreferrer" class="{{ $cardClasses }}">
|
||||||
|
{{ with $finalImage }}
|
||||||
|
<div class="flex-none md:w-48 lg:w-64">
|
||||||
|
<img src="{{ . }}" alt="{{ $finalTitle }}" class="w-full h-48 md:h-full object-cover" loading="lazy" decoding="async" onerror="this.style.display='none'">
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
<div class="flex flex-col justify-between p-4 min-w-0">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-semibold text-neutral-800 dark:text-neutral-200 line-clamp-2 mb-1">
|
||||||
|
{{ $finalTitle | default $domain }}
|
||||||
|
<span class="text-xs text-neutral-400 dark:text-neutral-500 align-top">↗</span>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{{ with $finalDescription }}
|
||||||
|
<p class="text-sm text-neutral-600 dark:text-neutral-400 line-clamp-2 mb-2">{{ . }}</p>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2 text-xs text-neutral-500 dark:text-neutral-500">
|
||||||
|
{{ with $finalFavicon }}
|
||||||
|
<img src="{{ . }}" alt="" class="w-4 h-4" loading="lazy" onerror="this.style.display='none'">
|
||||||
|
{{ end }}
|
||||||
|
<span class="truncate">{{ $finalProvider }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
Reference in New Issue
Block a user