commit 48f46f57942870f339d0266c2dd7e4ee31051f0d Author: ckaczor Date: Thu Nov 21 23:06:39 2024 +0000 deploy: 888370bd88942df31d73f66ca3db8cac505e976f diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..9b18a98 --- /dev/null +++ b/404.html @@ -0,0 +1,393 @@ + + + + + + Chris Kaczor + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

Content not found.

+

Go home.

+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/about/index.html b/about/index.html new file mode 100644 index 0000000..219bab3 --- /dev/null +++ b/about/index.html @@ -0,0 +1,384 @@ + + + + + + Chris Kaczor + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

About

+

I don't really know what to write here - I'd much rather write about other things, but I figure I need to introduce myself.

+

My name is Chris Kaczor, and I'm a software architect/engineer who likes to solve (and hopefully not cause) problems, determine how things should work, and figure out why the hell they aren't working.

+ +
+
+ + + + + + + \ No newline at end of file diff --git a/blog/back-again/index.html b/blog/back-again/index.html new file mode 100644 index 0000000..741250b --- /dev/null +++ b/blog/back-again/index.html @@ -0,0 +1,581 @@ + + + + + + Back Again + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

Back Again

+ + + +

When I started this blog I had recently been laid off and figured it was as good a time as any to give some visibility to the code I had been creating over the years rather than hoarding it like a jealous dragon. I intended to keep up with it but then I started a new job and the writing sort of fell by the wayside. I kept working on projects when I could but never really got the chance to write about them. I'm hoping to be a bit more consistent this time around but only time will tell.

+

I've been mostly working on completely recreating my home monitoring project with both new hardware and software - I'll write more about that in upcoming posts.

+

For the last week or so I've been doing Advent of Code 2019 so that I can follow along with my son. He got a good head start due to a few snow days at the beginning of the month but I've gotten ahead of him a bit as the challenges have gotten harder. We just spent some time getting his Intcode computer for day 9 working properly and I nearly swore off the entire contest while doing the second part of day 12 - the coding for that part wasn't really a problem but it took me a bit to figure out the concept for doing it without brute force. I think it would have been more fair if the challenge better explained how to solve the problem and left the coding up to the participant but that's probably just me being whiny. =) We've been posting our code in our GitHub repos in case anyone wants a peek:

+ +

I've been working in C# and he has been doing it in Python so it has been interesting to compare the two languages. I've been trying to get him to debug with breakpoints in Visual Studio Code rather than going old-school with print statements. It is definitely a process but we're getting there!

+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/blog/cpu-usage-status-window/images/ProcessCpuUsageStatusWindow.png b/blog/cpu-usage-status-window/images/ProcessCpuUsageStatusWindow.png new file mode 100644 index 0000000..7189ddc Binary files /dev/null and b/blog/cpu-usage-status-window/images/ProcessCpuUsageStatusWindow.png differ diff --git a/blog/cpu-usage-status-window/index.html b/blog/cpu-usage-status-window/index.html new file mode 100644 index 0000000..37573db --- /dev/null +++ b/blog/cpu-usage-status-window/index.html @@ -0,0 +1,580 @@ + + + + + + CPU Usage Status Window + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

CPU Usage Status Window

+ + + +

Now that I've written about my FloatingStatusWindow library I can start to talk about the projects that use it. First up is the ProcessCpuUsageStatusWindow project.

+

+

It is basically a mini task manager showing the top X processes by CPU usage and the total CPU usage of the system. The values update automatically every 2 seconds.

+

CPU usage is calculated by taking a snapshot of "% processor time" for the "Process" performance counter category every 2 seconds and having the counter sample calculate the percent usage for each process based on the previous snapshot.

+

This ended needing a lot more calculation than I had hoped although I don't remember all the details as to why - one of the perils of blogging so long after the code was written. From what I remember what I first tried was built into .NET and was easier to code but used a lot more processor time than I was comfortable with. Perhaps that has since been fixed in a later framework version - someday I'll have to try to recreate what I was doing.

+

One caveat - the code doesn't always work quite right. I have seen a few times where the calculation comes out way over 100% but I haven't been able to reliably reproduce it. It seems to be when either something hangs Windows or there's exceptionally high disk usage. Either way - it works well enough for getting a quick look at CPU usage so I haven't spent much time on it.

+

This was also the first project where I tried using Squirrel for installation and updates. I had used both WiX (with custom update detection and installation) and ClickOnce in other projects and I think I'll probably go with Squirrel for most things going forward.

+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/blog/etsy-shop-widget/images/etsy-shop-widget-settings.png b/blog/etsy-shop-widget/images/etsy-shop-widget-settings.png new file mode 100644 index 0000000..01bedce Binary files /dev/null and b/blog/etsy-shop-widget/images/etsy-shop-widget-settings.png differ diff --git a/blog/etsy-shop-widget/images/etsy-shop-widget-widget.png b/blog/etsy-shop-widget/images/etsy-shop-widget-widget.png new file mode 100644 index 0000000..f5bb004 Binary files /dev/null and b/blog/etsy-shop-widget/images/etsy-shop-widget-widget.png differ diff --git a/blog/etsy-shop-widget/index.html b/blog/etsy-shop-widget/index.html new file mode 100644 index 0000000..16f5bb4 --- /dev/null +++ b/blog/etsy-shop-widget/index.html @@ -0,0 +1,630 @@ + + + + + + Etsy Shop Widget + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

Etsy Shop Widget

+ + + +

My wife and a friend run The Crafty Coop (edit: now closed) - an event planning business that also sells handmade party decorations/favors online and at craft shows. One of the ways I help out is as the designated "IT guy" by handling the web/email hosting and other technical stuff.

+

When we were initially setting up the website using WordPress we looked for a widget that would generate a listing of their Etsy shop items but we weren't really happy with the ones we found - they either required that the Etsy shop be broken up into sections or they didn't quite display the way we wanted. I decided to try to write my own instead of tweaking one of the existing ones and Etsy Shop Widget was born.

+

+

I decided early on that I wanted to avoid generating the HTML in PHP as much as possible. I knew I'd have use some PHP for the WordPress admin settings but I wanted to use a front-end framework for the widget itself. I was already using Vue.js in other projects so when I found a template project for creating a WordPress plugin that used Vue.js I was good to go.

+

The Etsy API requires an API key and is rate limited so I wanted to cache the results rather than fetch them each time the page loaded. I added standard WordPress settings for the API key, the shop name, and the cache time and put them all in a settings section.

+

+

Right now this limits the widget to only one shop per WordPress installation but that's all we need right now. In the future I'll move the shop name to an attribute of the widget instead.

+

On the back end I created a custom WordPress action that uses the WordPress transients API to store the cached data from Etsy. Basically if get_transient returns some data then that data is simply returned to the caller, otherwise wp_remote_request is used to make the call to the Etsy API and the returned data is stored using set_transient with the appropriate cache duration.

+
function ESW_Listings_request()
+{
+    $listings = get_transient('etsy_shop_widget_listings');
+
+    if ($listings === false) {
+        $options = get_option('ESW_settings');
+
+        $response = wp_remote_request('https://openapi.etsy.com/v2/shops/' . $options['ESW_Etsy_Shop_Name'] . '/listings/active?includes=MainImage&api_key=' . $options['ESW_Etsy_API_Key'] . '');
+
+        $listings = $response['body'];
+
+        set_transient('etsy_shop_widget_listings', $listings, $options['ESW_Cache_Time'] * 60);
+    }
+
+    echo $listings;
+
+    die();
+}
+
+add_action('admin_post_esw_listings', 'ESW_Listings_request');
+add_action('admin_post_nopriv_esw_listings', 'ESW_Listings_request');
+

The rest of the PHP side is pretty straightforward - there's an "[etsy-shop-widget]" shortcode that generates an empty div with an ID of "etsy-shop-widget" that is used as the root of the Vue.js application and some code that links the styles and scripts generated by webpack when building the Vue.js code.

+

The front-end code is currently pretty basic as well. The Vue.js application just makes an AJAX request to the WordPress custom action, stores the resulting data in a component, and then uses the data to render a list of the item names, pictures, and prices.

+
@Component
+export default class App extends Vue {
+	listings: Array | null = null;
+
+	async mounted() {
+		const response = await Axios.get(window['esw_wp'].siteurl + '/wp-admin/admin-post.php?action=esw_listings');
+
+		this.listings = response.data.results.sort((a, b) => a.last_modified_tsz - b.last_modified_tsz);
+	}
+}
+
<template>
+	<div class="esw-listing-container">
+		<div class="esw-listing-item" v-for="listing in listings" v-bind:key="listing.listing_id">
+			<a :href="listing.url" target="_blank">
+				<img class="esw-listing-item-image" :src="listing.MainImage.url_170x135" />
+			</a>
+
+			<a :href="listing.url" target="_blank">
+				<div class="esw-listing-item-title" v-html="listing.title"></div>
+			</a>
+
+			<div class="esw-listing-item-price">
+				
+				
+			</div>
+		</div>
+	</div>
+</template>
+

At some point I'd like the make the listing a little fancier - maybe with a single image and previous/next buttons rather than a simple scrolling list but we're happy with the way it is working for now.

+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/blog/feed-center/images/Feed-Center-300x188.png b/blog/feed-center/images/Feed-Center-300x188.png new file mode 100644 index 0000000..1a2bded Binary files /dev/null and b/blog/feed-center/images/Feed-Center-300x188.png differ diff --git a/blog/feed-center/images/Feed-Center.png b/blog/feed-center/images/Feed-Center.png new file mode 100644 index 0000000..78880f7 Binary files /dev/null and b/blog/feed-center/images/Feed-Center.png differ diff --git a/blog/feed-center/images/Options-Feed.png b/blog/feed-center/images/Options-Feed.png new file mode 100644 index 0000000..87e8949 Binary files /dev/null and b/blog/feed-center/images/Options-Feed.png differ diff --git a/blog/feed-center/index.html b/blog/feed-center/index.html new file mode 100644 index 0000000..30dc23e --- /dev/null +++ b/blog/feed-center/index.html @@ -0,0 +1,581 @@ + + + + + + Feed Center + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

Feed Center

+ + + +

One of my most complete projects is Feed Center - an RSS feed reader designed to blend in with the Windows desktop.

+

+

Years ago when push technology was the hot new thing I got my online news from applications like PointCast and MSNBC News Alert - but they were eventually discontinued as web sites moved to other delivery mechanisms like RSS feeds. I tried to find an RSS reader but never really found one that I liked. I didn't want some Outlook lookalike that I had to specifically open and read through. I wanted an app that would blend in with my desktop as much as possible and be something that I could glance at every so often for the latest news. At some point I started fiddling with creating my own and Feed Center is the result.

+

In theory Feed Center supports all of the common RSS versions and Atom with some special handling for common errors. The fact that I'm parsing the feeds with an XML parser makes it a little harder to handle the edge cases but it works well enough so far.

+

Feeds are added in a "default" category but they can optionally be organized into any number of other categories. The top of the main window has a selector for the current category - I'm not sure I like the way it looks but I haven't come up with anything better yet.

+

+

The arrows at the top of the window (as well as mouse buttons 4 and 5 if you have them) will scroll through the feeds in the current category in alphabetical order. The current feed will also scroll automatically every minute unless the mouse is hovering over the window - it would be annoying to have the feed change when you're getting ready to read one.

+

Double clicking an article will open the web page for the article. By default the system default browser will be used but there's an option to choose another browser instead. There's also buttons to open all of the articles for the current feed and to mark all of the articles as having been read.

+

The code base has had some major changes over the years - originally the UI was done using WinForms and I used XML files as storage. After a few corrupt XML files from power failures I switched to using SQLite and then to SQL Server Compact. At some point I rewrote the UI using WPF so I could get rid of a bunch of custom painting for the feed list.

+

The project has a full installer created with WiX that uses a modified bootstrapper to automatically relaunch the application after an upgrade.

+

Other than the code on GitHub I haven't made anything public yet but I plan to use AppVeyor for build and deployment at some point - probably with the installer served as a GitHub release.

+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/blog/floating-status-window/images/Window-Appearance.png b/blog/floating-status-window/images/Window-Appearance.png new file mode 100644 index 0000000..8b77aef Binary files /dev/null and b/blog/floating-status-window/images/Window-Appearance.png differ diff --git a/blog/floating-status-window/images/Windows-Locked.png b/blog/floating-status-window/images/Windows-Locked.png new file mode 100644 index 0000000..d06d052 Binary files /dev/null and b/blog/floating-status-window/images/Windows-Locked.png differ diff --git a/blog/floating-status-window/images/Windows-Unlocked.png b/blog/floating-status-window/images/Windows-Unlocked.png new file mode 100644 index 0000000..2e52d9d Binary files /dev/null and b/blog/floating-status-window/images/Windows-Unlocked.png differ diff --git a/blog/floating-status-window/index.html b/blog/floating-status-window/index.html new file mode 100644 index 0000000..f0cbfce --- /dev/null +++ b/blog/floating-status-window/index.html @@ -0,0 +1,584 @@ + + + + + + Floating Status Window + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

Floating Status Window

+ + + +

Something that shows up in a lot of my projects is my FloatingStatusWindow library. It allows the creation of something akin to a desktop widget that displays simple text and blends in with the Windows desktop. This is what several of them look like in the corner of my secondary monitor:

+

+

Each one is a separate project that uses the core library to create and manage the window. The code for a few of these is on GitHub and I'm working to add more.

+

The windows are locked by default to prevent accidentally moving them but they can be unlocked in order to move or resize them. When moving or resizing the windows will snap to each other and the sides of the screen. This is what the example above looks like unlocked:

+

+

Basic appearance settings are built into the library itself along with a dialog to change them:

+

+

Each individual project is responsible for the text, the text layout, using a non-default text color, and keeping the text up to date. Some projects will update using a timer while others will wait for some sort of event and update as needed.

+

I had tried a number of applications that could do custom widgets but none of them quite worked the way I wanted. I read an article about transparent WPF windows and decided to create something myself.

+

Originally it was implemented as a single application that could load a number of plugins but that ended up being a bit of a pain - when working on a new window I had to close and restart everything and if a plugin crashed it'd take down all of the rest. I decided to convert it into a library that could detect other instances of the window so they worked together but were implemented as separate binaries.

+

There will be more about the library later when I write about the various applications that use it.

+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/blog/hello-world/index.html b/blog/hello-world/index.html new file mode 100644 index 0000000..0b51540 --- /dev/null +++ b/blog/hello-world/index.html @@ -0,0 +1,573 @@ + + + + + + Hello world! + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

Hello world!

+ + + +

Being a programmer at heart it seems appropriate to stick with the classic "hello world" for starting off!

+

I've been programming in one way or another since my dad bought a TRS-80 Model I many years ago. My first real project was a BASIC program that would convert text to pig Latin and it all took off from there. Now that I think about it there's even a chance that I still have that original code - I should go look for it!

+

Recently I was adding a new project to my Bitbucket account and realized I had a lot of code in there that might be useful for other people and it was a shame to keep it locked up. I had been hording it as if it was something secret but it really didn't need to be. I decided that I would made as much as possible public on GitHub and write a little bit about each project. Some of it is certainly a bit crusty but there's a chance some of it might be useful. I've only managed to move a little so far so expect more to show up as I make progress.

+

We also have a small collection of critters (hermit crabs, African clawed frogs, a bunch of freshwater tropical fish, two cats, and a dog) and are planning to add some new ones (axolotls and White's tree frogs) in the near future - so plan on seeing some posts about them as well.

+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/blog/laundry-monitor-hardware/images/Chip-225x300.jpg b/blog/laundry-monitor-hardware/images/Chip-225x300.jpg new file mode 100644 index 0000000..87abccd Binary files /dev/null and b/blog/laundry-monitor-hardware/images/Chip-225x300.jpg differ diff --git a/blog/laundry-monitor-hardware/images/Chip.jpg b/blog/laundry-monitor-hardware/images/Chip.jpg new file mode 100644 index 0000000..ed4b889 Binary files /dev/null and b/blog/laundry-monitor-hardware/images/Chip.jpg differ diff --git a/blog/laundry-monitor-hardware/images/Dryer-225x300.jpg b/blog/laundry-monitor-hardware/images/Dryer-225x300.jpg new file mode 100644 index 0000000..5c6c94a Binary files /dev/null and b/blog/laundry-monitor-hardware/images/Dryer-225x300.jpg differ diff --git a/blog/laundry-monitor-hardware/images/Dryer.jpg b/blog/laundry-monitor-hardware/images/Dryer.jpg new file mode 100644 index 0000000..5a79c1e Binary files /dev/null and b/blog/laundry-monitor-hardware/images/Dryer.jpg differ diff --git a/blog/laundry-monitor-hardware/images/Washer-225x300.jpg b/blog/laundry-monitor-hardware/images/Washer-225x300.jpg new file mode 100644 index 0000000..8d21bc7 Binary files /dev/null and b/blog/laundry-monitor-hardware/images/Washer-225x300.jpg differ diff --git a/blog/laundry-monitor-hardware/images/Washer.jpg b/blog/laundry-monitor-hardware/images/Washer.jpg new file mode 100644 index 0000000..e742655 Binary files /dev/null and b/blog/laundry-monitor-hardware/images/Washer.jpg differ diff --git a/blog/laundry-monitor-hardware/index.html b/blog/laundry-monitor-hardware/index.html new file mode 100644 index 0000000..f7f02d3 --- /dev/null +++ b/blog/laundry-monitor-hardware/index.html @@ -0,0 +1,582 @@ + + + + + + Laundry Monitor - Hardware + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

Laundry Monitor - Hardware

+ + + +

The next project up is LaundryMonitor which uses the most hardware of anything I've done so far.

+

I'd always wanted something that would let us tell if the washing machine or dryer was running but I never knew exactly the right way to detect it. After we got our solar panels I was doing research on how to track power usage and came across an article about current sensors and had the idea that I could hook up a current sensor to each appliance and watch the output voltage to know if the appliance was running. While trying to find a decent sensor I stumbled across a current switch which was more or less the same idea but had everything in one package - when the current was over a certain threshold the switch would close and that could be easily detected.

+

My first thought was to use a PowerState Tail but only a 120V version was available - there was nothing like it I could use for the 240V of the dryer. Eventually I settled on a Dwyer Miniature Current Switch and I'd use one each for both the washer and dryer just to be consistent.

+

Next up was to figure out how to monitor the switch. I was going to use a Phidget I/O Board attached to a central server and run wires across the house from the washer and dryer to the I/O board but the idea of running all the wires wasn't too appealing. I was about to try some sort of XBee setup when I found the C.H.I.P. Kickstarter and it seemed like it was worth a try - the C.H.I.P. was cheap, had digital I/O, had WiFi, and I could use anything that'd run on Linux to make it all work. The full board is probably a bit of overkill for just monitoring two switches but at $9 a device it really didn't bother me that much.

+

Hooking up the dryer was relatively easy - I opened the control panel, used the schematic to find the wire that powers the motor, disconnected the wire, ran the wire through the current switch, and reconnected the wire. I mounted the sensor on the back of the dryer for easy access in case I needed to adjust it but the motor draws enough current that the switch was able to detect it easily.

+

+

The washer was pretty much the same but I decided to monitor the main power line so I could detect if the washer was running at any point in the cycle. The trouble was that at certain times (like when the washer was filling) the current draw was way too low for the switch to detect even with the sensitivity set as low as possible. I solved this by looping the wire through the sensor several times to amplify the current to the point where the switch could pick it up.

+

+

From here on out things were simple - I just had to run some smaller wires from the switch to the digital I/O ports on the C.H.I.P. and then start working on the software. I ended up sticking the C.H.I.P. a few feet away by the power outlet so I didn't need too long a USB cable to power it.

+

+

So far this has been running for a little over two years and the hardware continues to work well. I'm planning to do a similar build to monitor two sump pumps - the only change is that I'm planning to use the PowerState Tail since they're both 120V with standard plugs so it'll be a lot easier to hook up.

+

I'll cover the software side of the project in my next post - right now the monitor is a standalone service that does all the work but since I'm going to be adding more devices I'm thinking that I need to break things up a bit.

+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/blog/laundry-monitor-software/images/HomeStatusWindow.png b/blog/laundry-monitor-software/images/HomeStatusWindow.png new file mode 100644 index 0000000..d3e26ca Binary files /dev/null and b/blog/laundry-monitor-software/images/HomeStatusWindow.png differ diff --git a/blog/laundry-monitor-software/images/LaundryBot-238x300.png b/blog/laundry-monitor-software/images/LaundryBot-238x300.png new file mode 100644 index 0000000..d95cc3a Binary files /dev/null and b/blog/laundry-monitor-software/images/LaundryBot-238x300.png differ diff --git a/blog/laundry-monitor-software/images/LaundryBot.png b/blog/laundry-monitor-software/images/LaundryBot.png new file mode 100644 index 0000000..6df382e Binary files /dev/null and b/blog/laundry-monitor-software/images/LaundryBot.png differ diff --git a/blog/laundry-monitor-software/index.html b/blog/laundry-monitor-software/index.html new file mode 100644 index 0000000..64cc015 --- /dev/null +++ b/blog/laundry-monitor-software/index.html @@ -0,0 +1,578 @@ + + + + + + Laundry Monitor – Software + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

Laundry Monitor – Software

+ + + +

The software side of LaundryMonitor is currently pretty straightforward. The C.H.I.P. runs a Node.js application that keeps track of a GPIO pin for each current switch - when the switch is closed the appliance is on and when the switch is open the appliance is off. The application exposes a websocket that applications like HomeStatusWindow can watch and messages are sent to a family Telegram channel.

+

+

+

I use the onoff library to poll the GPIO pins every 500 milliseconds and wait for a pin to be steady for 10 seconds before registering the new state. Originally I tried to use an interrupt but during testing I found that the onoff library could get stuck if switch events came in too fast. I decided that I didn't need real-time updates so a polling mechanism would be enough. I also originally didn't have the 10 second wait time but I found that the washer had a few periods where it would stop completely between wash phases and it made the Telegram alerts somewhat spammy. It looks like there have been a lot of updates to onoff since I first created the application so I plan to see if the interrupt issue has been fixed and if I can use the new debounce support rather than my own custom code.

+

Since I'm planning to monitor more devices in the future I'm thinking about making the application that runs on each C.H.I.P. generic and moving the alert and websocket implementation to a central "home monitor" service. The monitor application on each C.H.I.P. would be the same (other than configuration settings) and just report the status to the central service which would be in charge of everything else.

+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/blog/system-temperature-status-window/images/SystemTemperatureStatusWindow.png b/blog/system-temperature-status-window/images/SystemTemperatureStatusWindow.png new file mode 100644 index 0000000..b143008 Binary files /dev/null and b/blog/system-temperature-status-window/images/SystemTemperatureStatusWindow.png differ diff --git a/blog/system-temperature-status-window/index.html b/blog/system-temperature-status-window/index.html new file mode 100644 index 0000000..cc666ff --- /dev/null +++ b/blog/system-temperature-status-window/index.html @@ -0,0 +1,580 @@ + + + + + + System Temperature Status Window + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

System Temperature Status Window

+ + + +

Next up in the "floating status window" category is SystemTemperatureStatusWindow. This one does exactly what it says on the tin - it shows various system temperature values.

+

+

The sensor data is read using the Open Hardware Monitor library which is pretty straightforward - the one catch is that it requires running as an administrator and getting a UAC prompt each time the application starts got a bit annoying after a while.

+

I first tried to solve this by splitting the application into two parts - a Windows service that ran with administrator privileges to read the sensors and a normal application to handle the display. While this got past the annoying UAC prompts (after the service was installed) I found that Windows doesn't let you get GPU information from inside a Windows service.

+

After doing some more research I came across a workaround of using the Windows Task Scheduler to run the service as an administrator. I just turned the service into a regular application and set up the Task Scheduler to run it on system startup - problem solved.

+

Later on I added some command line switches to the service application that'd do the installation automatically. All I had to do was run the service once with a command line of "-install" and everything would get setup properly with just one UAC prompt.

+

I haven't gotten around to adding an installer for this one yet but I definitely have plans to do so. I expect some minor complications setting up the service in Squirrel due to the need for administrator access but I think it'll end up being doable.

+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/blog/weather-station/images/Weather-New-Chart.png b/blog/weather-station/images/Weather-New-Chart.png new file mode 100644 index 0000000..d24cbea Binary files /dev/null and b/blog/weather-station/images/Weather-New-Chart.png differ diff --git a/blog/weather-station/images/Weather-New-Dashboard.png b/blog/weather-station/images/Weather-New-Dashboard.png new file mode 100644 index 0000000..01d4150 Binary files /dev/null and b/blog/weather-station/images/Weather-New-Dashboard.png differ diff --git a/blog/weather-station/images/Weather-Old-Chart.png b/blog/weather-station/images/Weather-Old-Chart.png new file mode 100644 index 0000000..5c6d553 Binary files /dev/null and b/blog/weather-station/images/Weather-Old-Chart.png differ diff --git a/blog/weather-station/images/Weather-Old-Current.png b/blog/weather-station/images/Weather-Old-Current.png new file mode 100644 index 0000000..713590b Binary files /dev/null and b/blog/weather-station/images/Weather-Old-Current.png differ diff --git a/blog/weather-station/index.html b/blog/weather-station/index.html new file mode 100644 index 0000000..eb55604 --- /dev/null +++ b/blog/weather-station/index.html @@ -0,0 +1,586 @@ + + + + + + Weather Station + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

Weather Station

+ + + +

One of the oldest projects I'm still working on today is my weather station. I had always wanted a weather station but it seemed boring to just install something on the roof, stick an LCD display on the wall somewhere, and then forget about it - I wanted something I could build, expand on, and write my own software for.

+

At some point I ran across a weather station from AAG Electronica - it was relatively cheap, had a bunch of sensors, and ran on the Dallas Semiconductor (now Maxim Integrated) 1-Wire network. It was pretty much everything that I was looking for so I bought one, mounted it on the chimney, and set about writing an application for it.

+

At the time I was starting to learn C# and decided this would make a perfect learning project. There were a few examples for using the 1-Wire hardware (mostly in Java from what I remember) but I was able to access the driver functions from C# and started to get things working. At the lowest level there was a lot of bit manipulation but once that was going I was able to start building the application up from there.

+

The code and application has changed quite a bit over the years. Originally it was split into two libraries - one for the 1-Wire network and one for weather devices like temperature and pressure - and a WinForms application that displayed the current readings and graphs of the last 24 hours of readings.

+

Eventually I wanted to view the readings remotely so the application was refactored into a service that ran on the server with the hardware connection and a viewer that could run anywhere. It used .NET remoting (the recommended technique at the time) to connect the two pieces. This worked okay for a while and I created a few different applications to view the data - more about them in later posts.

+

Later when I got a smartphone it made sense to create something that would let me view things from my phone. I started down the path of creating an Android application but then decided that it'd be better to create a web application that could be used everywhere. I added a SignalR interface to the service and created a basic AngularJS 1.x web application as the viewer.

+

+

+

The AngularJS 1.x application worked well for a while but started to show its age so I decided to re-write it with Vue.js and that's where things are now. The new web application also integrates with some of my more recent projects like the laundry monitor that I'll write more about soon.

+

+

+

At this point the WeatherService project is responsible for reading the sensors, writing the readings to the database, and providing historical data for charting. It still uses my original OneWireAPI library to access the 1-Wire network.

+

Unfortunately AAG seems to have gone out of business so it is probably impossible to create the same setup or for me to get replacement parts. I've been looking at some off-the-shelf weather stations that I can read in a similar way but I haven't found anything I like so far. I'll probably be forced to find something soon since I'm starting to have some problems that look like hardware issues.

+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/blog/where-did-the-time-go/index.html b/blog/where-did-the-time-go/index.html new file mode 100644 index 0000000..5a20e55 --- /dev/null +++ b/blog/where-did-the-time-go/index.html @@ -0,0 +1,577 @@ + + + + + + Where did the time go? + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

Where did the time go?

+ + + +

Whoa - where the hell did all the time go?! I knew in the back of my mind that I was being a huge slacker by not updating in a while, but almost 5 years? That's crazy! My initial reaction was that the timestamp of my last post had gotten screwed up somehow, but the details of what I wrote proves that the time is right.

+

I'll fill in more details in upcoming posts but some highlights of the last 5 years:

+
    +
  • Continued working on my software and hardware projects
  • +
  • Starting playing around with 3D printing
  • +
  • Took a fairly intensive master's degree program in software engineering
  • +
  • Cataract surgery for my right eye (my left was April 2018 which I never wrote about)
  • +
  • Made it through all the craziness of 2020 and beyond
  • +
+

I'm sure there are other things, but they're not coming to mind right now - I'll bring them up if I think of them later.

+

If you've been here before you might notice that this site looks completely different now. I had been using WordPress because I wanted something easy - I had a feeling that if I picked something that needed some coding I'd end up getting lost in all of that rather that writing, but with dispute between WordPress and WP Engine (and some stuff done by Mullenweg that I think is pretty shady) I decided it was time to bail on it completely. The site has switched to Eleventy, and I've done some minimal tweaking to make it look the way I want for now. I'm sure I'll end up making some adjustments as I go, but I need to just make the switch and go with it.

+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/blog/work-indicator/index.html b/blog/work-indicator/index.html new file mode 100644 index 0000000..1a66083 --- /dev/null +++ b/blog/work-indicator/index.html @@ -0,0 +1,576 @@ + + + + + + Work Indicator + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

Work Indicator

+ + + +

I'm going to take a break from status windows for a little bit to cover my WorkIndicator project.

+

I've been working remotely for almost 14 years now and my family found it hard to tell when I was working or on the phone. It often looks the same whether I'm working or not - I'm sitting at my computer, sometimes with my headset on. At some point I came across a blog post by Scott Hanselman that described hooking up a status light to Lync and I was inspired to create something similar.

+

The first task was to find something I could use as a status light - after some research I settled on a USB HID Green/Yellow/Red Visual Indicator from Delcom. I liked that it had a stoplight design and it came with a C# sample - perfect!

+

My main workflow is to connect to my work system using Microsoft Remote Desktop so the easiest way to detect if I am working or not is to look for the remote desktop window. I created a class that uses SetWinEventHook to get ObjectCreate, ObjectDestroy, and ObjectNameChanged events and watch for a window with the title of my remote desktop window. If the window was found I'd set the indicator lights to yellow, otherwise I'd set them to green. Lately I've been using a VM as well so I updated the code to be able to look for multiple window titles - if any of them are detected the light is set to yellow.

+

Originally there was also Skype integration - I was able to use the Skype API to detect if I was on a call and if the call was muted. If I was on a muted call the right light would be on, and if I wasn't muted the red light would blink. This made it easy for the kids to tell how quiet they needed to be when coming into the room. Unfortunately Skype no longer supports this API (and I'm not using Skype anymore anyways) so I removed this code from the project.

+

The application also has a tray icon with a menu so I can manually set my status - for example, I can select "talking" and the red light will blink. This is my workaround until I come up with something that'll work for my current phone setup.

+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/css/index.css b/css/index.css new file mode 100644 index 0000000..046ebf5 --- /dev/null +++ b/css/index.css @@ -0,0 +1,314 @@ +/* Defaults */ +:root { + --font-family: Roboto, sans-serif; + --font-family-monospace: Consolas, Menlo, Monaco, Andale Mono WT, + Andale Mono, Lucida Console, Lucida Sans Typewriter, DejaVu Sans Mono, + Bitstream Vera Sans Mono, Liberation Mono, Nimbus Mono L, Courier New, + Courier, monospace; +} + +/* Theme colors */ +:root { + --color-gray-20: #e0e0e0; + --color-gray-50: #c0c0c0; + --color-gray-90: #333; + + --background-color: #fff; + + --text-color: var(--color-gray-90); + --text-color-link: #082840; + --text-color-link-active: #5f2b48; + --text-color-link-visited: #17050f; + + --syntax-tab-size: 2; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-gray-20: #e0e0e0; + --color-gray-50: #c0c0c0; + --color-gray-90: #dad8d8; + + /* --text-color is assigned to --color-gray-_ above */ + --text-color-link: #1493fb; + --text-color-link-active: #6969f7; + --text-color-link-visited: #a6a6f8; + + --background-color: #15202b; + } +} + +/* Global stylesheet */ +* { + box-sizing: border-box; +} + +@view-transition { + navigation: auto; +} + +html, +body { + padding: 0; + margin: 0 auto; + font-family: var(--font-family); + color: var(--text-color); + background-color: var(--background-color); +} +html { + overflow-y: scroll; +} +body { + max-width: 60em; +} + +/* https://www.a11yproject.com/posts/how-to-hide-content/ */ +.visually-hidden { + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; +} + +p:last-child { + margin-bottom: 0; +} +p { + line-height: 1.5; +} + +li { + line-height: 1.5; +} + +a[href] { + color: var(--text-color-link); +} +a[href]:visited { + color: var(--text-color-link-visited); +} +a[href]:hover, +a[href]:active { + color: var(--text-color-link-active); +} + +main { + padding: 1rem; +} +footer { + padding-bottom: 1rem; + padding-left: 1rem; + padding-right: 1rem; + font-size: 0.75rem; +} +main :first-child { + margin-top: 0; +} + +header { + border-bottom: 1px dashed var(--color-gray-20); +} +header:after { + content: ''; + display: table; + clear: both; +} + +.links-nextprev { + display: flex; + justify-content: space-between; + gap: 0.5em 1em; + list-style: ''; + border-top: 1px dashed var(--color-gray-20); + padding: 1em 0; +} +.links-nextprev > * { + flex-grow: 1; +} +.links-nextprev-next { + text-align: right; +} + +table { + margin: 1em 0; +} +table td, +table th { + padding-right: 1em; +} + +pre, +code { + font-family: var(--font-family-monospace); +} +pre:not([class*='language-']) { + margin: 0.5em 0; + line-height: 1.375; /* 22px /16 */ + -moz-tab-size: var(--syntax-tab-size); + -o-tab-size: var(--syntax-tab-size); + tab-size: var(--syntax-tab-size); + -webkit-hyphens: none; + -ms-hyphens: none; + hyphens: none; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + overflow-x: auto; +} +code { + word-break: break-all; +} + +/* Header */ +header { + display: flex; + gap: 1em 0.5em; + flex-wrap: wrap; + align-items: center; + padding: 1em; +} +.home-link { + font-size: 1em; /* 16px /16 */ + font-weight: 700; + margin-right: 2em; +} +.home-link:link:not(:hover) { + text-decoration: none; +} + +/* Nav */ +.nav { + display: flex; + padding: 0; + margin: 0; + list-style: none; +} +.nav-item { + display: inline-block; + margin-right: 1em; +} +.nav-item a[href]:not(:hover) { + text-decoration: none; +} +.nav a[href][aria-current='page'] { + font-weight: bold; +} + +/* Posts list */ +.postlist { + list-style: none; + padding: 0; +} +.postlist-item { + display: flex; + flex-wrap: wrap; + align-items: baseline; + margin-bottom: 2em; +} +.postlist-item:last-child { + margin-bottom: 0em; +} +.postlist-item:before { + display: inline-block; + pointer-events: none; + line-height: 100%; + text-align: right; +} +.postlist-date, +.postlist-item:before { + font-size: 0.8125em; /* 13px /16 */ + color: var(--color-gray-90); +} +.postlist-date { + word-spacing: -0.5px; +} +.postlist-link { + font-size: 1.1875em; /* 19px /16 */ + font-weight: 700; + flex-basis: calc(100% - 1.5rem); + padding-right: 0.5em; + text-decoration: none; +} + +.postlist-read-more { + text-decoration: none; + font-size: 0.8125em; + flex-basis: 100%; +} + +.postlist-read-more:hover { + text-decoration: underline; +} + +.postlist-link:hover { + text-decoration: underline; + text-underline-position: from-font; + text-underline-offset: 0; + text-decoration-thickness: 1px; +} +.postlist-item-active .postlist-link { + font-weight: bold; +} + +.postlist-byline { + flex-basis: 100%; +} + +.postlist-tags { + display: inline-flex; + align-items: center; + justify-content: center; + list-style-type: none; + gap: 0.5em; + font-size: 0.8125em; + padding-inline-start: 10px; +} + +.postlist-tag { + text-decoration: none; +} + +.postlist-tag:hover { + text-decoration: underline; +} + +/* Tags */ +.post-tag { + display: inline-flex; + align-items: center; + justify-content: center; + text-transform: capitalize; +} +.postlist-item > .post-tag { + align-self: center; +} + +/* Tags list */ +.post-metadata { + display: inline-flex; + flex-wrap: wrap; + gap: 0.5em; + list-style: none; + padding: 0; + margin: 0; +} +.post-metadata time { + margin-right: 1em; +} + +img { + max-width: 100%; + display: block; + margin: 0 auto; +} +img[height] { + height: auto; +} +img[width][height] { + height: auto; +} diff --git a/css/prism-diff.css b/css/prism-diff.css new file mode 100644 index 0000000..cf22581 --- /dev/null +++ b/css/prism-diff.css @@ -0,0 +1,45 @@ +/* + * New diff- syntax + */ + +pre[class*='language-diff-'] { + --eleventy-code-padding: 1.25em; + padding-left: var(--eleventy-code-padding); + padding-right: var(--eleventy-code-padding); +} +.token.deleted { + background-color: hsl(0, 51%, 37%); + color: inherit; +} +.token.inserted { + background-color: hsl(126, 31%, 39%); + color: inherit; +} + +/* Make the + and - characters unselectable for copy/paste */ +.token.prefix.unchanged, +.token.prefix.inserted, +.token.prefix.deleted { + -webkit-user-select: none; + user-select: none; + display: inline-flex; + align-items: center; + justify-content: center; + padding-top: 2px; + padding-bottom: 2px; +} +.token.prefix.inserted, +.token.prefix.deleted { + width: var(--eleventy-code-padding); + background-color: rgba(0, 0, 0, 0.2); +} + +/* Optional: full-width background color */ +.token.inserted:not(.prefix), +.token.deleted:not(.prefix) { + display: block; + margin-left: calc(-1 * var(--eleventy-code-padding)); + margin-right: calc(-1 * var(--eleventy-code-padding)); + text-decoration: none; /* override del, ins, mark defaults */ + color: inherit; /* override del, ins, mark defaults */ +} diff --git a/dist/rJ3_G-2ArF.js b/dist/rJ3_G-2ArF.js new file mode 100644 index 0000000..aa9a58e --- /dev/null +++ b/dist/rJ3_G-2ArF.js @@ -0,0 +1,219 @@ +// Thank you to https://github.com/daviddarnes/heading-anchors +// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/ + +let globalInstanceIndex = 0; + +class HeadingAnchors extends HTMLElement { + static register(tagName) { + if ("customElements" in window) { + customElements.define(tagName || "heading-anchors", HeadingAnchors); + } + } + + static attributes = { + exclude: "data-ha-exclude", + prefix: "prefix", + content: "content", + } + + static classes = { + anchor: "ha", + placeholder: "ha-placeholder", + srOnly: "ha-visualhide", + } + + static defaultSelector = "h2,h3,h4,h5,h6"; + + static css = ` +.${HeadingAnchors.classes.srOnly} { + clip: rect(0 0 0 0); + height: 1px; + overflow: hidden; + position: absolute; + width: 1px; +} +.${HeadingAnchors.classes.anchor} { + position: absolute; + left: var(--ha_offsetx); + top: var(--ha_offsety); + text-decoration: none; + opacity: 0; +} +.${HeadingAnchors.classes.placeholder} { + opacity: .3; +} +.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) { + opacity: 1; +} +.${HeadingAnchors.classes.anchor}, +.${HeadingAnchors.classes.placeholder} { + padding: 0 .25em; + + /* Disable selection of visually hidden label */ + -webkit-user-select: none; + user-select: none; +} + +@supports (anchor-name: none) { + .${HeadingAnchors.classes.anchor} { + position: absolute; + left: anchor(left); + top: anchor(top); + } +}`; + + get supports() { + return "replaceSync" in CSSStyleSheet.prototype; + } + + get supportsAnchorPosition() { + return CSS.supports("anchor-name: none"); + } + + constructor() { + super(); + + if(!this.supports) { + return; + } + + let sheet = new CSSStyleSheet(); + sheet.replaceSync(HeadingAnchors.css); + document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet]; + + this.headingStyles = {}; + this.instanceIndex = globalInstanceIndex++; + } + + connectedCallback() { + if (!this.supports) { + return; + } + + this.headings.forEach((heading, index) => { + if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) { + let anchor = this.getAnchorElement(heading); + let placeholder = this.getPlaceholderElement(); + + // Prefers anchor position approach for better accessibility + // https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/ + if(this.supportsAnchorPosition) { + let anchorName = `--ha_${this.instanceIndex}_${index}`; + placeholder.style.setProperty("anchor-name", anchorName); + anchor.style.positionAnchor = anchorName; + } + + heading.appendChild(placeholder) + heading.after(anchor); + } + }); + } + + // Polyfill-only + positionAnchorFromPlaceholder(placeholder) { + if(!placeholder) { + return; + } + + let heading = placeholder.closest("h1,h2,h3,h4,h5,h6"); + if(!heading.nextElementSibling) { + return; + } + + // TODO next element could be more defensive + this.positionAnchor(heading.nextElementSibling); + } + + // Polyfill-only + positionAnchor(anchor) { + if(!anchor || !anchor.previousElementSibling) { + return; + } + + // TODO previous element could be more defensive + let heading = anchor.previousElementSibling; + this.setFontProp(heading, anchor); + + if(this.supportsAnchorPosition) { + // quit early + return; + } + + let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`); + if(placeholder) { + anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`); + anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`); + } + } + + setFontProp(heading, anchor) { + let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`); + if(placeholder) { + let style = getComputedStyle(placeholder); + let props = ["font-weight", "font-size", "line-height", "font-family"]; + let font = props.map(name => style.getPropertyValue(name)); + anchor.style.setProperty("font", `${font[0]} ${font[1]}/${font[2]} ${font[3]}`); + } + } + + getAccessibleTextPrefix() { + // Useful for i18n + return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled"; + } + + getContent() { + return this.getAttribute(HeadingAnchors.attributes.content) || "#"; + } + + getPlaceholderElement() { + let ph = document.createElement("span"); + ph.setAttribute("aria-hidden", true); + ph.classList.add(HeadingAnchors.classes.placeholder); + ph.textContent = this.getContent(); + + ph.addEventListener("mouseover", (e) => { + let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`); + if(placeholder) { + this.positionAnchorFromPlaceholder(placeholder); + } + }); + + return ph; + } + + getAnchorElement(heading) { + let anchor = document.createElement("a"); + anchor.href = `#${heading.id}`; + anchor.classList.add(HeadingAnchors.classes.anchor); + + let content = this.getContent(); + anchor.innerHTML = `${this.getAccessibleTextPrefix()}: ${heading.textContent}`; + + anchor.addEventListener("focus", e => { + let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`); + if(anchor) { + this.positionAnchor(anchor); + } + }); + + anchor.addEventListener("mouseover", (e) => { + // when CSS anchor positioning is supported, this is only used to set the font + let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`); + this.positionAnchor(anchor); + }); + + return anchor; + } + + get headings() { + return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`)); + } + + get selector() { + return this.getAttribute("selector") || HeadingAnchors.defaultSelector; + } +} + +HeadingAnchors.register(); + +export { HeadingAnchors } \ No newline at end of file diff --git a/feed/feed.xml b/feed/feed.xml new file mode 100644 index 0000000..6ad9ab8 --- /dev/null +++ b/feed/feed.xml @@ -0,0 +1,220 @@ + + + Chris Kaczor + Code, Critters, and whatever I feel like writing about. + + + 2024-11-11T00:00:00Z + https://chriskaczor.com/eleventy-base-blog/ + + Chris Kaczor + + + Where did the time go? + + 2024-11-11T00:00:00Z + https://chriskaczor.com/eleventy-base-blog/blog/where-did-the-time-go/ + <p>Whoa - where the hell did all the time go?! I knew in the back of my mind that I was being a huge slacker by not updating in a while, but almost 5 years? That's crazy! My initial reaction was that the timestamp of my last post had gotten screwed up somehow, but the details of what I wrote proves that the time is right.</p> +<p>I'll fill in more details in upcoming posts but some highlights of the last 5 years:</p> +<ul> +<li>Continued working on my software and hardware projects</li> +<li>Starting playing around with 3D printing</li> +<li>Took a fairly intensive master's degree program in software engineering</li> +<li>Cataract surgery for my right eye (my left was April 2018 which I never wrote about)</li> +<li>Made it through all the craziness of 2020 and beyond</li> +</ul> +<p>I'm sure there are other things, but they're not coming to mind right now - I'll bring them up if I think of them later.</p> +<p>If you've been here before you might notice that this site looks completely different now. I had been using WordPress because I wanted something easy - I had a feeling that if I picked something that needed some coding I'd end up getting lost in all of that rather that writing, but with dispute between WordPress and WP Engine (and some stuff done by Mullenweg that I think is pretty shady) I decided it was time to bail on it completely. The site has switched to Eleventy, and I've done some minimal tweaking to make it look the way I want for now. I'm sure I'll end up making some adjustments as I go, but I need to just make the switch and go with it.</p> + + + + Back Again + + 2019-12-13T00:00:00Z + https://chriskaczor.com/eleventy-base-blog/blog/back-again/ + <p>When I started this blog I had recently been laid off and figured it was as good a time as any to give some visibility to the code I had been creating over the years rather than hoarding it like a jealous dragon. I intended to keep up with it but then I started a new job and the writing sort of fell by the wayside. I kept working on projects when I could but never really got the chance to write about them. I'm hoping to be a bit more consistent this time around but only time will tell.</p> +<p>I've been mostly working on completely recreating my home monitoring project with both new hardware and software - I'll write more about that in upcoming posts.</p> +<p>For the last week or so I've been doing <a href="https://adventofcode.com/">Advent of Code 2019</a> so that I can follow along with my son. He got a good head start due to a few snow days at the beginning of the month but I've gotten ahead of him a bit as the challenges have gotten harder. We just spent some time getting his Intcode computer for day 9 working properly and I nearly swore off the entire contest while doing the second part of day 12 - the coding for that part wasn't really a problem but it took me a bit to figure out the concept for doing it without brute force. I think it would have been more fair if the challenge better explained how to solve the problem and left the coding up to the participant but that's probably just me being whiny. =) We've been posting our code in our GitHub repos in case anyone wants a peek:</p> +<ul> +<li><a href="https://github.com/ckaczor/Advent2019">https://github.com/ckaczor/Advent2019</a></li> +<li><a href="https://github.com/Zakury/Advent-of-Code">https://github.com/Zakury/Advent-of-Code</a></li> +</ul> +<p>I've been working in C# and he has been doing it in Python so it has been interesting to compare the two languages. I've been trying to get him to debug with breakpoints in Visual Studio Code rather than going old-school with print statements. It is definitely a process but we're getting there!</p> + + + + Etsy Shop Widget + + 2018-04-20T00:00:00Z + https://chriskaczor.com/eleventy-base-blog/blog/etsy-shop-widget/ + <p>My wife and a friend run The Crafty Coop (edit: now closed) - an event planning business that also sells handmade party decorations/favors online and at craft shows. One of the ways I help out is as the designated &quot;IT guy&quot; by handling the web/email hosting and other technical stuff.</p> +<p>When we were initially setting up the website using WordPress we looked for a widget that would generate a listing of their Etsy shop items but we weren't really happy with the ones we found - they either required that the Etsy shop be broken up into sections or they didn't quite display the way we wanted. I decided to try to write my own instead of tweaking one of the existing ones and <a href="https://github.com/ckaczor/etsy-shop-widget">Etsy Shop Widget</a> was born.</p> +<p><a href="https://chriskaczor.com/eleventy-base-blog/blog/etsy-shop-widget/images/etsy-shop-widget-widget.png"><picture><source type="image/avif" srcset="https://chriskaczor.com/eleventy-base-blog/img/ZuxNaFUA1y-280.avif 280w"><source type="image/webp" srcset="https://chriskaczor.com/eleventy-base-blog/img/ZuxNaFUA1y-280.webp 280w"><img loading="lazy" decoding="async" src="https://chriskaczor.com/eleventy-base-blog/img/ZuxNaFUA1y-280.png" alt="" width="280" height="391"></picture></a></p> +<p>I decided early on that I wanted to avoid generating the HTML in PHP as much as possible. I knew I'd have use some PHP for the WordPress admin settings but I wanted to use a front-end framework for the widget itself. I was already using <a href="https://vuejs.org/">Vue.js</a> in other projects so when I found a <a href="https://github.com/caldera-learn/vue-webpack-wordpress-plugin">template project</a> for creating a WordPress plugin that used Vue.js I was good to go.</p> +<p>The <a href="https://www.etsy.com/developers/documentation/getting_started/api_basics">Etsy API</a> requires an API key and is rate limited so I wanted to cache the results rather than fetch them each time the page loaded. I added standard WordPress settings for the API key, the shop name, and the cache time and put them all in a settings section.</p> +<p><a href="https://chriskaczor.com/eleventy-base-blog/blog/etsy-shop-widget/images/etsy-shop-widget-settings.png"><picture><source type="image/avif" srcset="https://chriskaczor.com/eleventy-base-blog/img/rbvGYcCS3i-544.avif 544w"><source type="image/webp" srcset="https://chriskaczor.com/eleventy-base-blog/img/rbvGYcCS3i-544.webp 544w"><img loading="lazy" decoding="async" src="https://chriskaczor.com/eleventy-base-blog/img/rbvGYcCS3i-544.png" alt="" width="544" height="289"></picture></a></p> +<p>Right now this limits the widget to only one shop per WordPress installation but that's all we need right now. In the future I'll move the shop name to an attribute of the widget instead.</p> +<p>On the back end I created a custom WordPress action that uses the WordPress <a href="https://codex.wordpress.org/Transients_API">transients API</a> to store the cached data from Etsy. Basically if get_transient returns some data then that data is simply returned to the caller, otherwise wp_remote_request is used to make the call to the Etsy API and the returned data is stored using set_transient with the appropriate cache duration.</p> +<pre class="language-php" tabindex="0"><code class="language-php"><span class="token keyword">function</span> <span class="token function-definition function">ESW_Listings_request</span><span class="token punctuation">(</span><span class="token punctuation">)</span> +<span class="token punctuation">{</span> + <span class="token variable">$listings</span> <span class="token operator">=</span> <span class="token function">get_transient</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'etsy_shop_widget_listings'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> + + <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token variable">$listings</span> <span class="token operator">===</span> <span class="token constant boolean">false</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token variable">$options</span> <span class="token operator">=</span> <span class="token function">get_option</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'ESW_settings'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> + + <span class="token variable">$response</span> <span class="token operator">=</span> <span class="token function">wp_remote_request</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'https://openapi.etsy.com/v2/shops/'</span> <span class="token operator">.</span> <span class="token variable">$options</span><span class="token punctuation">[</span><span class="token string single-quoted-string">'ESW_Etsy_Shop_Name'</span><span class="token punctuation">]</span> <span class="token operator">.</span> <span class="token string single-quoted-string">'/listings/active?includes=MainImage&amp;amp;amp;amp;amp;amp;api_key='</span> <span class="token operator">.</span> <span class="token variable">$options</span><span class="token punctuation">[</span><span class="token string single-quoted-string">'ESW_Etsy_API_Key'</span><span class="token punctuation">]</span> <span class="token operator">.</span> <span class="token string single-quoted-string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span> + + <span class="token variable">$listings</span> <span class="token operator">=</span> <span class="token variable">$response</span><span class="token punctuation">[</span><span class="token string single-quoted-string">'body'</span><span class="token punctuation">]</span><span class="token punctuation">;</span> + + <span class="token function">set_transient</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'etsy_shop_widget_listings'</span><span class="token punctuation">,</span> <span class="token variable">$listings</span><span class="token punctuation">,</span> <span class="token variable">$options</span><span class="token punctuation">[</span><span class="token string single-quoted-string">'ESW_Cache_Time'</span><span class="token punctuation">]</span> <span class="token operator">*</span> <span class="token number">60</span><span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token punctuation">}</span> + + <span class="token keyword">echo</span> <span class="token variable">$listings</span><span class="token punctuation">;</span> + + <span class="token keyword">die</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> +<span class="token punctuation">}</span> + +<span class="token function">add_action</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'admin_post_esw_listings'</span><span class="token punctuation">,</span> <span class="token string single-quoted-string">'ESW_Listings_request'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> +<span class="token function">add_action</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'admin_post_nopriv_esw_listings'</span><span class="token punctuation">,</span> <span class="token string single-quoted-string">'ESW_Listings_request'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> +<p>The rest of the PHP side is pretty straightforward - there's an &quot;[etsy-shop-widget]&quot; shortcode that generates an empty div with an ID of &quot;etsy-shop-widget&quot; that is used as the root of the Vue.js application and some code that links the styles and scripts generated by webpack when building the Vue.js code.</p> +<p>The front-end code is currently pretty basic as well. The Vue.js application just makes an AJAX request to the WordPress custom action, stores the resulting data in a component, and then uses the data to render a list of the item names, pictures, and prices.</p> +<pre class="language-jscript" tabindex="0"><code class="language-jscript">@Component +export default class App extends Vue { + listings: Array<EtsyListing> | null = null; + + async mounted() { + const response = await Axios.get<EtsyResult>(window['esw_wp'].siteurl + '/wp-admin/admin-post.php?action=esw_listings'); + + this.listings = response.data.results.sort((a, b) => a.last_modified_tsz - b.last_modified_tsz); + } +}</EtsyResult></EtsyListing></code></pre> +<pre class="language-xml" tabindex="0"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>esw-listing-container<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>esw-listing-item<span class="token punctuation">"</span></span> <span class="token attr-name">v-for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>listing in listings<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">v-bind:</span>key</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>listing.listing_id<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">:href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>listing.url<span class="token punctuation">"</span></span> <span class="token attr-name">target</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>_blank<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>esw-listing-item-image<span class="token punctuation">"</span></span> <span class="token attr-name">:src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>listing.MainImage.url_170x135<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> + + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">:href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>listing.url<span class="token punctuation">"</span></span> <span class="token attr-name">target</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>_blank<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>esw-listing-item-title<span class="token punctuation">"</span></span> <span class="token attr-name">v-html</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>listing.title<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> + + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>esw-listing-item-price<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> + + + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> +<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">></span></span></code></pre> +<p>At some point I'd like the make the listing a little fancier - maybe with a single image and previous/next buttons rather than a simple scrolling list but we're happy with the way it is working for now.</p> + + + + Feed Center + + 2018-04-04T00:00:00Z + https://chriskaczor.com/eleventy-base-blog/blog/feed-center/ + <p>One of my most complete projects is <a href="https://github.com/ckaczor/FeedCenter">Feed Center</a> - an RSS feed reader designed to blend in with the Windows desktop.</p> +<p><a href="https://chriskaczor.com/eleventy-base-blog/blog/feed-center/images/Feed-Center.png"><picture><source type="image/avif" srcset="https://chriskaczor.com/eleventy-base-blog/img/uZeAJYpepW-300.avif 300w"><source type="image/webp" srcset="https://chriskaczor.com/eleventy-base-blog/img/uZeAJYpepW-300.webp 300w"><img loading="lazy" decoding="async" src="https://chriskaczor.com/eleventy-base-blog/img/uZeAJYpepW-300.png" alt="" width="300" height="188"></picture></a></p> +<p>Years ago when push technology was the hot new thing I got my online news from applications like PointCast and MSNBC News Alert - but they were eventually discontinued as web sites moved to other delivery mechanisms like RSS feeds. I tried to find an RSS reader but never really found one that I liked. I didn't want some Outlook lookalike that I had to specifically open and read through. I wanted an app that would blend in with my desktop as much as possible and be something that I could glance at every so often for the latest news. At some point I started fiddling with creating my own and Feed Center is the result.</p> +<p>In theory Feed Center supports all of the common RSS versions and Atom with some special handling for common errors. The fact that I'm parsing the feeds with an XML parser makes it a little harder to handle the edge cases but it works well enough so far.</p> +<p>Feeds are added in a &quot;default&quot; category but they can optionally be organized into any number of other categories. The top of the main window has a selector for the current category - I'm not sure I like the way it looks but I haven't come up with anything better yet.</p> +<p><a href="https://chriskaczor.com/eleventy-base-blog/blog/feed-center/images/Options-Feed.png"><picture><source type="image/avif" srcset="https://chriskaczor.com/eleventy-base-blog/img/4GWlZwTcq7-706.avif 706w"><source type="image/webp" srcset="https://chriskaczor.com/eleventy-base-blog/img/4GWlZwTcq7-706.webp 706w"><img loading="lazy" decoding="async" src="https://chriskaczor.com/eleventy-base-blog/img/4GWlZwTcq7-706.png" alt="" width="706" height="353"></picture></a></p> +<p>The arrows at the top of the window (as well as mouse buttons 4 and 5 if you have them) will scroll through the feeds in the current category in alphabetical order. The current feed will also scroll automatically every minute unless the mouse is hovering over the window - it would be annoying to have the feed change when you're getting ready to read one.</p> +<p>Double clicking an article will open the web page for the article. By default the system default browser will be used but there's an option to choose another browser instead. There's also buttons to open all of the articles for the current feed and to mark all of the articles as having been read.</p> +<p>The code base has had some major changes over the years - originally the UI was done using WinForms and I used XML files as storage. After a few corrupt XML files from power failures I switched to using SQLite and then to SQL Server Compact. At some point I rewrote the UI using WPF so I could get rid of a bunch of custom painting for the feed list.</p> +<p>The project has a full installer created with <a href="http://wixtoolset.org/">WiX</a> that uses a <a href="https://github.com/ckaczor/WixBalExtensionExt">modified bootstrapper</a> to automatically relaunch the application after an upgrade.</p> +<p>Other than the code on GitHub I haven't made anything public yet but I plan to use <a href="https://www.appveyor.com/">AppVeyor</a> for build and deployment at some point - probably with the installer served as a GitHub release.</p> + + + + Laundry Monitor – Software + + 2018-04-02T00:00:00Z + https://chriskaczor.com/eleventy-base-blog/blog/laundry-monitor-software/ + <p>The software side of <a href="https://github.com/ckaczor/LaundryMonitor">LaundryMonitor</a> is currently pretty straightforward. The <a href="https://getchip.com/pages/chip">C.H.I.P.</a> runs a Node.js application that keeps track of a GPIO pin for each current switch - when the switch is closed the appliance is on and when the switch is open the appliance is off. The application exposes a websocket that applications like <a href="https://github.com/ckaczor/HomeStatusWindow">HomeStatusWindow</a> can watch and messages are sent to a family Telegram channel.</p> +<p><a href="https://chriskaczor.com/eleventy-base-blog/blog/laundry-monitor-software/images/HomeStatusWindow.png"><picture><source type="image/avif" srcset="https://chriskaczor.com/eleventy-base-blog/img/t0izVudIyW-107.avif 107w"><source type="image/webp" srcset="https://chriskaczor.com/eleventy-base-blog/img/t0izVudIyW-107.webp 107w"><img loading="lazy" decoding="async" src="https://chriskaczor.com/eleventy-base-blog/img/t0izVudIyW-107.png" alt="" width="107" height="48"></picture></a></p> +<p><a href="https://chriskaczor.com/eleventy-base-blog/blog/laundry-monitor-software/images/LaundryBot.png"><picture><source type="image/avif" srcset="https://chriskaczor.com/eleventy-base-blog/img/MWYQsdWfr9-238.avif 238w"><source type="image/webp" srcset="https://chriskaczor.com/eleventy-base-blog/img/MWYQsdWfr9-238.webp 238w"><img loading="lazy" decoding="async" src="https://chriskaczor.com/eleventy-base-blog/img/MWYQsdWfr9-238.png" alt="" width="238" height="300"></picture></a></p> +<p>I use the <a href="https://github.com/fivdi/onoff">onoff</a> library to poll the GPIO pins every 500 milliseconds and wait for a pin to be steady for 10 seconds before registering the new state. Originally I tried to use an interrupt but during testing I found that the onoff library could get stuck if switch events came in too fast. I decided that I didn't need real-time updates so a polling mechanism would be enough. I also originally didn't have the 10 second wait time but I found that the washer had a few periods where it would stop completely between wash phases and it made the Telegram alerts somewhat spammy. It looks like there have been a lot of updates to onoff since I first created the application so I plan to see if the interrupt issue has been fixed and if I can use the new debounce support rather than my own custom code.</p> +<p>Since I'm planning to monitor more devices in the future I'm thinking about making the application that runs on each C.H.I.P. generic and moving the alert and websocket implementation to a central &quot;home monitor&quot; service. The monitor application on each C.H.I.P. would be the same (other than configuration settings) and just report the status to the central service which would be in charge of everything else.</p> + + + + Laundry Monitor - Hardware + + 2018-03-29T00:00:00Z + https://chriskaczor.com/eleventy-base-blog/blog/laundry-monitor-hardware/ + <p>The next project up is <a href="https://github.com/ckaczor/LaundryMonitor">LaundryMonitor</a> which uses the most hardware of anything I've done so far.</p> +<p>I'd always wanted something that would let us tell if the washing machine or dryer was running but I never knew exactly the right way to detect it. After we got our solar panels I was doing research on how to track power usage and came across an article about current sensors and had the idea that I could hook up a current sensor to each appliance and watch the output voltage to know if the appliance was running. While trying to find a decent sensor I stumbled across a current switch which was more or less the same idea but had everything in one package - when the current was over a certain threshold the switch would close and that could be easily detected.</p> +<p>My first thought was to use a <a href="http://www.powerswitchtail.com/powerstate-tail">PowerState Tail</a> but only a 120V version was available - there was nothing like it I could use for the 240V of the dryer. Eventually I settled on a <a href="https://smile.amazon.com/gp/product/B00I9IFJOM/ref=oh_aui_search_detailpage?ie=UTF8&amp;psc=1">Dwyer Miniature Current Switch</a> and I'd use one each for both the washer and dryer just to be consistent.</p> +<p>Next up was to figure out how to monitor the switch. I was going to use a <a href="https://www.phidgets.com/?tier=1&amp;catid=2&amp;pcid=1">Phidget I/O Board</a> attached to a central server and run wires across the house from the washer and dryer to the I/O board but the idea of running all the wires wasn't too appealing. I was about to try some sort of <a href="https://www.sparkfun.com/pages/xbee_guide">XBee</a> setup when I found the <a href="https://getchip.com/pages/chip">C.H.I.P.</a> Kickstarter and it seemed like it was worth a try - the C.H.I.P. was cheap, had digital I/O, had WiFi, and I could use anything that'd run on Linux to make it all work. The full board is probably a bit of overkill for just monitoring two switches but at $9 a device it really didn't bother me that much.</p> +<p>Hooking up the dryer was relatively easy - I opened the control panel, used the schematic to find the wire that powers the motor, disconnected the wire, ran the wire through the current switch, and reconnected the wire. I mounted the sensor on the back of the dryer for easy access in case I needed to adjust it but the motor draws enough current that the switch was able to detect it easily.</p> +<p><a href="https://chriskaczor.com/eleventy-base-blog/blog/laundry-monitor-hardware/images/Dryer.jpg"><picture><source type="image/avif" srcset="https://chriskaczor.com/eleventy-base-blog/img/7D8kV0Tyqw-225.avif 225w"><source type="image/webp" srcset="https://chriskaczor.com/eleventy-base-blog/img/7D8kV0Tyqw-225.webp 225w"><img loading="lazy" decoding="async" src="https://chriskaczor.com/eleventy-base-blog/img/7D8kV0Tyqw-225.jpeg" alt="" width="225" height="300"></picture></a></p> +<p>The washer was pretty much the same but I decided to monitor the main power line so I could detect if the washer was running at any point in the cycle. The trouble was that at certain times (like when the washer was filling) the current draw was way too low for the switch to detect even with the sensitivity set as low as possible. I solved this by looping the wire through the sensor several times to amplify the current to the point where the switch could pick it up.</p> +<p><a href="https://chriskaczor.com/eleventy-base-blog/blog/laundry-monitor-hardware/images/Washer.jpg"><picture><source type="image/avif" srcset="https://chriskaczor.com/eleventy-base-blog/img/Qvfav4t14w-225.avif 225w"><source type="image/webp" srcset="https://chriskaczor.com/eleventy-base-blog/img/Qvfav4t14w-225.webp 225w"><img loading="lazy" decoding="async" src="https://chriskaczor.com/eleventy-base-blog/img/Qvfav4t14w-225.jpeg" alt="" width="225" height="300"></picture></a></p> +<p>From here on out things were simple - I just had to run some smaller wires from the switch to the digital I/O ports on the C.H.I.P. and then start working on the software. I ended up sticking the C.H.I.P. a few feet away by the power outlet so I didn't need too long a USB cable to power it.</p> +<p><a href="https://chriskaczor.com/eleventy-base-blog/blog/laundry-monitor-hardware/images/Chip.jpg"><picture><source type="image/avif" srcset="https://chriskaczor.com/eleventy-base-blog/img/y6v3-1HpL1-225.avif 225w"><source type="image/webp" srcset="https://chriskaczor.com/eleventy-base-blog/img/y6v3-1HpL1-225.webp 225w"><img loading="lazy" decoding="async" src="https://chriskaczor.com/eleventy-base-blog/img/y6v3-1HpL1-225.jpeg" alt="" width="225" height="300"></picture></a></p> +<p>So far this has been running for a little over two years and the hardware continues to work well. I'm planning to do a similar build to monitor two sump pumps - the only change is that I'm planning to use the PowerState Tail since they're both 120V with standard plugs so it'll be a lot easier to hook up.</p> +<p>I'll cover the software side of the project in my next post - right now the monitor is a standalone service that does all the work but since I'm going to be adding more devices I'm thinking that I need to break things up a bit.</p> + + + + Work Indicator + + 2018-03-19T00:00:00Z + https://chriskaczor.com/eleventy-base-blog/blog/work-indicator/ + <p>I'm going to take a break from status windows for a little bit to cover my <a href="https://github.com/ckaczor/WorkIndicator">WorkIndicator</a> project.</p> +<p>I've been working remotely for almost 14 years now and my family found it hard to tell when I was working or on the phone. It often looks the same whether I'm working or not - I'm sitting at my computer, sometimes with my headset on. At some point I came across a <a href="https://www.hanselman.com/blog/IsDaddyOnACallABusyLightPresenceIndicatorForLyncForMyHomeOffice.aspx">blog post</a> by Scott Hanselman that described hooking up a status light to Lync and I was inspired to create something similar.</p> +<p>The first task was to find something I could use as a status light - after some research I settled on a <a href="https://www.delcomproducts.com/productdetails.asp?PartNumber=907241">USB HID Green/Yellow/Red Visual Indicator</a> from Delcom. I liked that it had a stoplight design and it came with a C# sample - perfect!</p> +<p>My main workflow is to connect to my work system using Microsoft Remote Desktop so the easiest way to detect if I am working or not is to look for the remote desktop window. I created a class that uses SetWinEventHook to get ObjectCreate, ObjectDestroy, and ObjectNameChanged events and watch for a window with the title of my remote desktop window. If the window was found I'd set the indicator lights to yellow, otherwise I'd set them to green. Lately I've been using a VM as well so I updated the code to be able to look for multiple window titles - if any of them are detected the light is set to yellow.</p> +<p>Originally there was also Skype integration - I was able to use the Skype API to detect if I was on a call and if the call was muted. If I was on a muted call the right light would be on, and if I wasn't muted the red light would blink. This made it easy for the kids to tell how quiet they needed to be when coming into the room. Unfortunately Skype no longer supports this API (and I'm not using Skype anymore anyways) so I removed this code from the project.</p> +<p>The application also has a tray icon with a menu so I can manually set my status - for example, I can select &quot;talking&quot; and the red light will blink. This is my workaround until I come up with something that'll work for my current phone setup.</p> + + + + System Temperature Status Window + + 2018-03-19T00:00:00Z + https://chriskaczor.com/eleventy-base-blog/blog/system-temperature-status-window/ + <p>Next up in the &quot;floating status window&quot; category is <a href="https://github.com/ckaczor/SystemTemperatureStatusWindow">SystemTemperatureStatusWindow</a>. This one does exactly what it says on the tin - it shows various system temperature values.</p> +<p><a href="https://chriskaczor.com/eleventy-base-blog/blog/system-temperature-status-window/images/SystemTemperatureStatusWindow.png"><picture><source type="image/avif" srcset="https://chriskaczor.com/eleventy-base-blog/img/LBCvSAQh1L-121.avif 121w"><source type="image/webp" srcset="https://chriskaczor.com/eleventy-base-blog/img/LBCvSAQh1L-121.webp 121w"><img loading="lazy" decoding="async" src="https://chriskaczor.com/eleventy-base-blog/img/LBCvSAQh1L-121.png" alt="" width="121" height="118"></picture></a></p> +<p>The sensor data is read using the <a href="http://openhardwaremonitor.org/">Open Hardware Monitor</a> library which is pretty straightforward - the one catch is that it requires running as an administrator and getting a UAC prompt each time the application starts got a bit annoying after a while.</p> +<p>I first tried to solve this by splitting the application into two parts - a Windows service that ran with administrator privileges to read the sensors and a normal application to handle the display. While this got past the annoying UAC prompts (after the service was installed) I found that Windows doesn't let you get GPU information from inside a Windows service.</p> +<p>After doing some more research I came across a workaround of using the Windows Task Scheduler to run the service as an administrator. I just turned the service into a regular application and set up the Task Scheduler to run it on system startup - problem solved.</p> +<p>Later on I added some command line switches to the service application that'd do the installation automatically. All I had to do was run the service once with a command line of &quot;-install&quot; and everything would get setup properly with just one UAC prompt.</p> +<p>I haven't gotten around to adding an installer for this one yet but I definitely have plans to do so. I expect some minor complications setting up the service in Squirrel due to the need for administrator access but I think it'll end up being doable.</p> + + + + CPU Usage Status Window + + 2018-03-08T00:00:00Z + https://chriskaczor.com/eleventy-base-blog/blog/cpu-usage-status-window/ + <p>Now that I've written about my <a href="https://github.com/ckaczor/FloatingStatusWindow">FloatingStatusWindow</a> library I can start to talk about the projects that use it. First up is the <a href="https://github.com/ckaczor/ProcessCpuUsageStatusWindow">ProcessCpuUsageStatusWindow</a> project.</p> +<p><a href="https://chriskaczor.com/eleventy-base-blog/blog/cpu-usage-status-window/images/ProcessCpuUsageStatusWindow.png"><picture><source type="image/avif" srcset="https://chriskaczor.com/eleventy-base-blog/img/L-UgyKZ-NI-373.avif 373w"><source type="image/webp" srcset="https://chriskaczor.com/eleventy-base-blog/img/L-UgyKZ-NI-373.webp 373w"><img loading="lazy" decoding="async" src="https://chriskaczor.com/eleventy-base-blog/img/L-UgyKZ-NI-373.png" alt="" width="373" height="139"></picture></a></p> +<p>It is basically a mini task manager showing the top X processes by CPU usage and the total CPU usage of the system. The values update automatically every 2 seconds.</p> +<p>CPU usage is calculated by taking a snapshot of &quot;% processor time&quot; for the &quot;Process&quot; performance counter category every 2 seconds and having the counter sample calculate the percent usage for each process based on the previous snapshot.</p> +<p>This ended needing a lot more calculation than I had hoped although I don't remember all the details as to why - one of the perils of blogging so long after the code was written. From what I remember what I first tried was built into .NET and was easier to code but used a lot more processor time than I was comfortable with. Perhaps that has since been fixed in a later framework version - someday I'll have to try to recreate what I was doing.</p> +<p>One caveat - the code doesn't always work quite right. I have seen a few times where the calculation comes out way over 100% but I haven't been able to reliably reproduce it. It seems to be when either something hangs Windows or there's exceptionally high disk usage. Either way - it works well enough for getting a quick look at CPU usage so I haven't spent much time on it.</p> +<p>This was also the first project where I tried using <a href="https://github.com/Squirrel/Squirrel.Windows">Squirrel</a> for installation and updates. I had used both <a href="http://wixtoolset.org/">WiX</a> (with custom update detection and installation) and <a href="https://en.wikipedia.org/wiki/ClickOnce">ClickOnce</a> in other projects and I think I'll probably go with Squirrel for most things going forward.</p> + + + + Floating Status Window + + 2018-02-21T00:00:00Z + https://chriskaczor.com/eleventy-base-blog/blog/floating-status-window/ + <p>Something that shows up in a lot of my projects is my <a href="https://github.com/ckaczor/FloatingStatusWindow">FloatingStatusWindow</a> library. It allows the creation of something akin to a desktop widget that displays simple text and blends in with the Windows desktop. This is what several of them look like in the corner of my secondary monitor:</p> +<p><a href="https://chriskaczor.com/eleventy-base-blog/blog/floating-status-window/images/Windows-Locked.png"><picture><source type="image/avif" srcset="https://chriskaczor.com/eleventy-base-blog/img/-IG3SLpr1H-770.avif 770w"><source type="image/webp" srcset="https://chriskaczor.com/eleventy-base-blog/img/-IG3SLpr1H-770.webp 770w"><img loading="lazy" decoding="async" src="https://chriskaczor.com/eleventy-base-blog/img/-IG3SLpr1H-770.png" alt="" width="770" height="303"></picture></a></p> +<p>Each one is a separate project that uses the core library to create and manage the window. The code for a few of these is on <a href="https://github.com/ckaczor">GitHub</a> and I'm working to add more.</p> +<p>The windows are locked by default to prevent accidentally moving them but they can be unlocked in order to move or resize them. When moving or resizing the windows will snap to each other and the sides of the screen. This is what the example above looks like unlocked:</p> +<p><a href="https://chriskaczor.com/eleventy-base-blog/blog/floating-status-window/images/Windows-Unlocked.png"><picture><source type="image/avif" srcset="https://chriskaczor.com/eleventy-base-blog/img/2zWtBO5eyV-1358.avif 1358w"><source type="image/webp" srcset="https://chriskaczor.com/eleventy-base-blog/img/2zWtBO5eyV-1358.webp 1358w"><img loading="lazy" decoding="async" src="https://chriskaczor.com/eleventy-base-blog/img/2zWtBO5eyV-1358.png" alt="" width="1358" height="354"></picture></a></p> +<p>Basic appearance settings are built into the library itself along with a dialog to change them:</p> +<p><a href="https://chriskaczor.com/eleventy-base-blog/blog/floating-status-window/images/Window-Appearance.png"><picture><source type="image/avif" srcset="https://chriskaczor.com/eleventy-base-blog/img/mcAwKnxf5g-436.avif 436w"><source type="image/webp" srcset="https://chriskaczor.com/eleventy-base-blog/img/mcAwKnxf5g-436.webp 436w"><img loading="lazy" decoding="async" src="https://chriskaczor.com/eleventy-base-blog/img/mcAwKnxf5g-436.png" alt="" width="436" height="298"></picture></a></p> +<p>Each individual project is responsible for the text, the text layout, using a non-default text color, and keeping the text up to date. Some projects will update using a timer while others will wait for some sort of event and update as needed.</p> +<p>I had tried a number of applications that could do custom widgets but none of them quite worked the way I wanted. I read an article about transparent WPF windows and decided to create something myself.</p> +<p>Originally it was implemented as a single application that could load a number of plugins but that ended up being a bit of a pain - when working on a new window I had to close and restart everything and if a plugin crashed it'd take down all of the rest. I decided to convert it into a library that could detect other instances of the window so they worked together but were implemented as separate binaries.</p> +<p>There will be more about the library later when I write about the various applications that use it.</p> + + + \ No newline at end of file diff --git a/img/-IG3SLpr1H-770.avif b/img/-IG3SLpr1H-770.avif new file mode 100644 index 0000000..5a66533 Binary files /dev/null and b/img/-IG3SLpr1H-770.avif differ diff --git a/img/-IG3SLpr1H-770.png b/img/-IG3SLpr1H-770.png new file mode 100644 index 0000000..122f7ef Binary files /dev/null and b/img/-IG3SLpr1H-770.png differ diff --git a/img/-IG3SLpr1H-770.webp b/img/-IG3SLpr1H-770.webp new file mode 100644 index 0000000..2174a7b Binary files /dev/null and b/img/-IG3SLpr1H-770.webp differ diff --git a/img/.gitkeep b/img/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/img/2zWtBO5eyV-1358.avif b/img/2zWtBO5eyV-1358.avif new file mode 100644 index 0000000..d7e4559 Binary files /dev/null and b/img/2zWtBO5eyV-1358.avif differ diff --git a/img/2zWtBO5eyV-1358.png b/img/2zWtBO5eyV-1358.png new file mode 100644 index 0000000..273a0d4 Binary files /dev/null and b/img/2zWtBO5eyV-1358.png differ diff --git a/img/2zWtBO5eyV-1358.webp b/img/2zWtBO5eyV-1358.webp new file mode 100644 index 0000000..2de32eb Binary files /dev/null and b/img/2zWtBO5eyV-1358.webp differ diff --git a/img/4GWlZwTcq7-706.avif b/img/4GWlZwTcq7-706.avif new file mode 100644 index 0000000..883750b Binary files /dev/null and b/img/4GWlZwTcq7-706.avif differ diff --git a/img/4GWlZwTcq7-706.png b/img/4GWlZwTcq7-706.png new file mode 100644 index 0000000..eb8be15 Binary files /dev/null and b/img/4GWlZwTcq7-706.png differ diff --git a/img/4GWlZwTcq7-706.webp b/img/4GWlZwTcq7-706.webp new file mode 100644 index 0000000..41a3949 Binary files /dev/null and b/img/4GWlZwTcq7-706.webp differ diff --git a/img/7D8kV0Tyqw-225.avif b/img/7D8kV0Tyqw-225.avif new file mode 100644 index 0000000..c0918a9 Binary files /dev/null and b/img/7D8kV0Tyqw-225.avif differ diff --git a/img/7D8kV0Tyqw-225.jpeg b/img/7D8kV0Tyqw-225.jpeg new file mode 100644 index 0000000..50570a6 Binary files /dev/null and b/img/7D8kV0Tyqw-225.jpeg differ diff --git a/img/7D8kV0Tyqw-225.webp b/img/7D8kV0Tyqw-225.webp new file mode 100644 index 0000000..945af68 Binary files /dev/null and b/img/7D8kV0Tyqw-225.webp differ diff --git a/img/CRIFD7TqRl-397.avif b/img/CRIFD7TqRl-397.avif new file mode 100644 index 0000000..c64a5fe Binary files /dev/null and b/img/CRIFD7TqRl-397.avif differ diff --git a/img/CRIFD7TqRl-397.png b/img/CRIFD7TqRl-397.png new file mode 100644 index 0000000..060f6b5 Binary files /dev/null and b/img/CRIFD7TqRl-397.png differ diff --git a/img/CRIFD7TqRl-397.webp b/img/CRIFD7TqRl-397.webp new file mode 100644 index 0000000..9efbe1e Binary files /dev/null and b/img/CRIFD7TqRl-397.webp differ diff --git a/img/DdGjKWFTJJ-1363.avif b/img/DdGjKWFTJJ-1363.avif new file mode 100644 index 0000000..897dab4 Binary files /dev/null and b/img/DdGjKWFTJJ-1363.avif differ diff --git a/img/DdGjKWFTJJ-1363.png b/img/DdGjKWFTJJ-1363.png new file mode 100644 index 0000000..a08001a Binary files /dev/null and b/img/DdGjKWFTJJ-1363.png differ diff --git a/img/DdGjKWFTJJ-1363.webp b/img/DdGjKWFTJJ-1363.webp new file mode 100644 index 0000000..8967308 Binary files /dev/null and b/img/DdGjKWFTJJ-1363.webp differ diff --git a/img/L-UgyKZ-NI-373.avif b/img/L-UgyKZ-NI-373.avif new file mode 100644 index 0000000..001fe10 Binary files /dev/null and b/img/L-UgyKZ-NI-373.avif differ diff --git a/img/L-UgyKZ-NI-373.png b/img/L-UgyKZ-NI-373.png new file mode 100644 index 0000000..5dd969a Binary files /dev/null and b/img/L-UgyKZ-NI-373.png differ diff --git a/img/L-UgyKZ-NI-373.webp b/img/L-UgyKZ-NI-373.webp new file mode 100644 index 0000000..ae337f8 Binary files /dev/null and b/img/L-UgyKZ-NI-373.webp differ diff --git a/img/LBCvSAQh1L-121.avif b/img/LBCvSAQh1L-121.avif new file mode 100644 index 0000000..c29ce07 Binary files /dev/null and b/img/LBCvSAQh1L-121.avif differ diff --git a/img/LBCvSAQh1L-121.png b/img/LBCvSAQh1L-121.png new file mode 100644 index 0000000..a777d77 Binary files /dev/null and b/img/LBCvSAQh1L-121.png differ diff --git a/img/LBCvSAQh1L-121.webp b/img/LBCvSAQh1L-121.webp new file mode 100644 index 0000000..ed01e0e Binary files /dev/null and b/img/LBCvSAQh1L-121.webp differ diff --git a/img/MWYQsdWfr9-238.avif b/img/MWYQsdWfr9-238.avif new file mode 100644 index 0000000..8337769 Binary files /dev/null and b/img/MWYQsdWfr9-238.avif differ diff --git a/img/MWYQsdWfr9-238.png b/img/MWYQsdWfr9-238.png new file mode 100644 index 0000000..7e92a95 Binary files /dev/null and b/img/MWYQsdWfr9-238.png differ diff --git a/img/MWYQsdWfr9-238.webp b/img/MWYQsdWfr9-238.webp new file mode 100644 index 0000000..79ddd34 Binary files /dev/null and b/img/MWYQsdWfr9-238.webp differ diff --git a/img/Qvfav4t14w-225.avif b/img/Qvfav4t14w-225.avif new file mode 100644 index 0000000..379b698 Binary files /dev/null and b/img/Qvfav4t14w-225.avif differ diff --git a/img/Qvfav4t14w-225.jpeg b/img/Qvfav4t14w-225.jpeg new file mode 100644 index 0000000..a7b9ab6 Binary files /dev/null and b/img/Qvfav4t14w-225.jpeg differ diff --git a/img/Qvfav4t14w-225.webp b/img/Qvfav4t14w-225.webp new file mode 100644 index 0000000..beea5eb Binary files /dev/null and b/img/Qvfav4t14w-225.webp differ diff --git a/img/TfbGZVfftX-1361.avif b/img/TfbGZVfftX-1361.avif new file mode 100644 index 0000000..dd3db20 Binary files /dev/null and b/img/TfbGZVfftX-1361.avif differ diff --git a/img/TfbGZVfftX-1361.png b/img/TfbGZVfftX-1361.png new file mode 100644 index 0000000..83de587 Binary files /dev/null and b/img/TfbGZVfftX-1361.png differ diff --git a/img/TfbGZVfftX-1361.webp b/img/TfbGZVfftX-1361.webp new file mode 100644 index 0000000..adddbd7 Binary files /dev/null and b/img/TfbGZVfftX-1361.webp differ diff --git a/img/ZuxNaFUA1y-280.avif b/img/ZuxNaFUA1y-280.avif new file mode 100644 index 0000000..fa0bd0a Binary files /dev/null and b/img/ZuxNaFUA1y-280.avif differ diff --git a/img/ZuxNaFUA1y-280.png b/img/ZuxNaFUA1y-280.png new file mode 100644 index 0000000..4961405 Binary files /dev/null and b/img/ZuxNaFUA1y-280.png differ diff --git a/img/ZuxNaFUA1y-280.webp b/img/ZuxNaFUA1y-280.webp new file mode 100644 index 0000000..a720f4d Binary files /dev/null and b/img/ZuxNaFUA1y-280.webp differ diff --git a/img/_AHvzLh23d-790.avif b/img/_AHvzLh23d-790.avif new file mode 100644 index 0000000..945be7d Binary files /dev/null and b/img/_AHvzLh23d-790.avif differ diff --git a/img/_AHvzLh23d-790.png b/img/_AHvzLh23d-790.png new file mode 100644 index 0000000..973afae Binary files /dev/null and b/img/_AHvzLh23d-790.png differ diff --git a/img/_AHvzLh23d-790.webp b/img/_AHvzLh23d-790.webp new file mode 100644 index 0000000..a210d5e Binary files /dev/null and b/img/_AHvzLh23d-790.webp differ diff --git a/img/apple-touch-icon.png b/img/apple-touch-icon.png new file mode 100644 index 0000000..e437c70 Binary files /dev/null and b/img/apple-touch-icon.png differ diff --git a/img/favicon-16x16.png b/img/favicon-16x16.png new file mode 100644 index 0000000..aeb2b51 Binary files /dev/null and b/img/favicon-16x16.png differ diff --git a/img/favicon-32x32.png b/img/favicon-32x32.png new file mode 100644 index 0000000..c386b1a Binary files /dev/null and b/img/favicon-32x32.png differ diff --git a/img/favicon.ico b/img/favicon.ico new file mode 100644 index 0000000..189ea53 Binary files /dev/null and b/img/favicon.ico differ diff --git a/img/mcAwKnxf5g-436.avif b/img/mcAwKnxf5g-436.avif new file mode 100644 index 0000000..805dacb Binary files /dev/null and b/img/mcAwKnxf5g-436.avif differ diff --git a/img/mcAwKnxf5g-436.png b/img/mcAwKnxf5g-436.png new file mode 100644 index 0000000..437c654 Binary files /dev/null and b/img/mcAwKnxf5g-436.png differ diff --git a/img/mcAwKnxf5g-436.webp b/img/mcAwKnxf5g-436.webp new file mode 100644 index 0000000..a271584 Binary files /dev/null and b/img/mcAwKnxf5g-436.webp differ diff --git a/img/rbvGYcCS3i-544.avif b/img/rbvGYcCS3i-544.avif new file mode 100644 index 0000000..84fb095 Binary files /dev/null and b/img/rbvGYcCS3i-544.avif differ diff --git a/img/rbvGYcCS3i-544.png b/img/rbvGYcCS3i-544.png new file mode 100644 index 0000000..094c01d Binary files /dev/null and b/img/rbvGYcCS3i-544.png differ diff --git a/img/rbvGYcCS3i-544.webp b/img/rbvGYcCS3i-544.webp new file mode 100644 index 0000000..a2d92c9 Binary files /dev/null and b/img/rbvGYcCS3i-544.webp differ diff --git a/img/t0izVudIyW-107.avif b/img/t0izVudIyW-107.avif new file mode 100644 index 0000000..d908553 Binary files /dev/null and b/img/t0izVudIyW-107.avif differ diff --git a/img/t0izVudIyW-107.png b/img/t0izVudIyW-107.png new file mode 100644 index 0000000..f3ba673 Binary files /dev/null and b/img/t0izVudIyW-107.png differ diff --git a/img/t0izVudIyW-107.webp b/img/t0izVudIyW-107.webp new file mode 100644 index 0000000..3558060 Binary files /dev/null and b/img/t0izVudIyW-107.webp differ diff --git a/img/uZeAJYpepW-300.avif b/img/uZeAJYpepW-300.avif new file mode 100644 index 0000000..2718815 Binary files /dev/null and b/img/uZeAJYpepW-300.avif differ diff --git a/img/uZeAJYpepW-300.png b/img/uZeAJYpepW-300.png new file mode 100644 index 0000000..09d1e6b Binary files /dev/null and b/img/uZeAJYpepW-300.png differ diff --git a/img/uZeAJYpepW-300.webp b/img/uZeAJYpepW-300.webp new file mode 100644 index 0000000..b6bcfbf Binary files /dev/null and b/img/uZeAJYpepW-300.webp differ diff --git a/img/y6v3-1HpL1-225.avif b/img/y6v3-1HpL1-225.avif new file mode 100644 index 0000000..0e73ce5 Binary files /dev/null and b/img/y6v3-1HpL1-225.avif differ diff --git a/img/y6v3-1HpL1-225.jpeg b/img/y6v3-1HpL1-225.jpeg new file mode 100644 index 0000000..31d7ae6 Binary files /dev/null and b/img/y6v3-1HpL1-225.jpeg differ diff --git a/img/y6v3-1HpL1-225.webp b/img/y6v3-1HpL1-225.webp new file mode 100644 index 0000000..73800c7 Binary files /dev/null and b/img/y6v3-1HpL1-225.webp differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..99aa851 --- /dev/null +++ b/index.html @@ -0,0 +1,659 @@ + + + + + + Chris Kaczor + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + + + + + +
+ + + + + + + \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..fb5ff3b --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,83 @@ + + + + + https://chriskaczor.com/eleventy-base-blog/blog/hello-world/ + 2018-02-05 + + + + https://chriskaczor.com/eleventy-base-blog/blog/weather-station/ + 2018-02-17 + + + + https://chriskaczor.com/eleventy-base-blog/blog/floating-status-window/ + 2018-02-21 + + + + https://chriskaczor.com/eleventy-base-blog/blog/cpu-usage-status-window/ + 2018-03-08 + + + + https://chriskaczor.com/eleventy-base-blog/blog/system-temperature-status-window/ + 2018-03-19 + + + + https://chriskaczor.com/eleventy-base-blog/blog/work-indicator/ + 2018-03-19 + + + + https://chriskaczor.com/eleventy-base-blog/blog/laundry-monitor-hardware/ + 2018-03-29 + + + + https://chriskaczor.com/eleventy-base-blog/blog/laundry-monitor-software/ + 2018-04-02 + + + + https://chriskaczor.com/eleventy-base-blog/blog/feed-center/ + 2018-04-04 + + + + https://chriskaczor.com/eleventy-base-blog/blog/etsy-shop-widget/ + 2018-04-20 + + + + https://chriskaczor.com/eleventy-base-blog/blog/back-again/ + 2019-12-13 + + + + https://chriskaczor.com/eleventy-base-blog/blog/where-did-the-time-go/ + 2024-11-11 + + + + https://chriskaczor.com/eleventy-base-blog/about/ + 2024-11-21 + + + + https://chriskaczor.com/eleventy-base-blog/ + 2024-11-21 + + + + https://chriskaczor.com/eleventy-base-blog/tags/ + 2024-11-21 + + + + https://chriskaczor.com/eleventy-base-blog/feed/feed.xml + 2024-11-21 + + \ No newline at end of file diff --git a/tags/code/index.html b/tags/code/index.html new file mode 100644 index 0000000..103954e --- /dev/null +++ b/tags/code/index.html @@ -0,0 +1,642 @@ + + + + + + Tagged 'Code' + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

Tagged “Code”

+ + + + + +

See all tags.

+
+
+ + + + + + + \ No newline at end of file diff --git a/tags/critters/index.html b/tags/critters/index.html new file mode 100644 index 0000000..0169439 --- /dev/null +++ b/tags/critters/index.html @@ -0,0 +1,414 @@ + + + + + + Tagged 'Critters' + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

Tagged “Critters”

+ + + + + +

See all tags.

+
+
+ + + + + + + \ No newline at end of file diff --git a/tags/excuses/index.html b/tags/excuses/index.html new file mode 100644 index 0000000..7c2f2e3 --- /dev/null +++ b/tags/excuses/index.html @@ -0,0 +1,435 @@ + + + + + + Tagged 'Excuses' + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

Tagged “Excuses”

+ + + +
    + +
  • + + + Where did the time go? + + + +

    Whoa - where the hell did all the time go?! I knew in the back of my mind that I was being a huge slacker by not updating in a while, but almost 5 years? That's crazy! My initial reaction was that the timestamp of my last post had gotten screwed up somehow, but the details of what I wrote proves that the time is right.

    + + + Read more + +
  • + +
  • + + + Back Again + + + +

    When I started this blog I had recently been laid off and figured it was as good a time as any to give some visibility to the code I had been creating over the years rather than hoarding it like a jealous dragon. I intended to keep up with it but then I started a new job and the writing sort of fell by the wayside. I kept working on projects when I could but never really got the chance to write about them. I'm hoping to be a bit more consistent this time around but only time will tell.

    + + + Read more + +
  • + +
+ +

See all tags.

+
+
+ + + + + + + \ No newline at end of file diff --git a/tags/floating-status-window/index.html b/tags/floating-status-window/index.html new file mode 100644 index 0000000..dab1507 --- /dev/null +++ b/tags/floating-status-window/index.html @@ -0,0 +1,486 @@ + + + + + + Tagged 'Floating Status Window' + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

Tagged “Floating Status Window”

+ + + + + +

See all tags.

+
+
+ + + + + + + \ No newline at end of file diff --git a/tags/index.html b/tags/index.html new file mode 100644 index 0000000..acbb036 --- /dev/null +++ b/tags/index.html @@ -0,0 +1,410 @@ + + + + + + Chris Kaczor + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

Tags

+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/tags/weather/index.html b/tags/weather/index.html new file mode 100644 index 0000000..319b8ba --- /dev/null +++ b/tags/weather/index.html @@ -0,0 +1,414 @@ + + + + + + Tagged 'Weather' + + + + + + + + + + + + + + + + + + Skip to main content + +
+ Chris Kaczor + +
+ +
+ + +

Tagged “Weather”

+ + + +
    + +
  • + + + Weather Station + + + +

    One of the oldest projects I'm still working on today is my weather station. I had always wanted a weather station but it seemed boring to just install something on the roof, stick an LCD display on the wall somewhere, and then forget about it - I wanted something I could build, expand on, and write my own software for.

    + + + Read more + +
  • + +
+ +

See all tags.

+
+
+ + + + + + + \ No newline at end of file