Feb 8, 2022

I’m in the process of replacing my voice assistants with a more private, home-based solution that keeps everything inside the local network. I had originally decided on using Rhasspy in a server/satellite configuration to collect voice commands, and Home Assistant to manage turning things on and off. I changed to openHAB after trying and failing for hours to get Home Assistant’s Docker image to listen to and act upon Rhasspy intents, plus running into other annoyances like having no clear way inside the Docker version to do basic things like group lightbulbs in the same lamp post in a way that made sense to me.

image by EFF/Hugh D’Andrade, source/license

A key part of the project is keeping everything local-only, so I didn’t want any accounts on any cloud services, and I wasn’t open to using more than a Docker image for uniformity with my other services. Unfortunately, I found Home Assistant’s Docker image to be a cut-down version of the software that seemed to require constant off-documentation modifications to YAML files to try to achieve the same functionality as running on bare metal or in a VM with a cloud account. I didn’t trust that I’d remember half of the changes I made or why I made them a year from now. openHAB, on the other hand, seemed at home in Docker and didn’t appear to lock any functionality behind creating off-site accounts.

I was still pretty set on using Rhasspy, though, as I was finding it quite friendly and intuitive. Notably, Rhasspy has a Home Assistant setting for intent handling, but none for openHAB, so I knew I’d need to go off the books a bit.

Luckily Rhasspy will emit MQTT events for commands you define, and openHAB can listen for MQTT events just fine. So long as you can capture the data inside those events, there’s nothing stopping you from having openHAB run actions based on that data.

This is where it gets a little tricky, though. How do you catch an MQTT event, read the data inside it, and then act on it in openHAB?

What follows assumes you have working installations of Rhasspy and openHAB, plus some devices added.

Disclaimer

This is almost certainly not the best way to do this, and may just be a flat-out awful way to do it. My total experience with these systems is about one day. I’d love to hear from you if you have advice on more official/correct ways of setting up this integration.

Install MQTT Binding and JSScripting

To get started, go to Settings in openHAB and click Bindings under Add-ons. You’ll need to install the MQTT Binding add-on. Next, go back to Settings and click Automations under Add-ons. Install the JSScripting add-on. Both of these are official add-ons provided by openHAB and pop right into your installation with zero drama or trips back to the command line.

MQTT Binding Add-on page

Create an “MQTT Broker” Thing

Next, create a new Thing in openHAB under Settings > Things. Choose “MQTT Binding” as the binding, then “MQTT Broker” as the Thing type. Fill in the field “Broker Hostname/IP”, then click “Show advanced” and fill in “Broker Port” if you’re not running Rhasspy’s MQTT on 1883 or 8883. By default Rhasspy’s Docker image seems to run MQTT over 12183, so it’s likely you’ll need to fill in this setting.

MQTT Broker configuration screen

Next, you’ll need to configure channels for each of your Rhasspy intents. You’ll find these intents in your Sentences screen in Rhasspy.

I have two intents defined so far, one for RGB lights, and one for what I’m calling “binary devices,” which just take ON or OFF commands (and which I just realized I should have called “boolean devices,” but there’s no way I’m going back and redoing these screenshots now). Apologies for the complication here, but it seems my RGB lights don’t respond to ON and OFF, but instead work by setting their color temperature to turn on, and setting their RGB to 0,0,0 to turn off.

Switch to the channels tab of your new Thing to copy these over into openHAB. I’ve added a ChangeStateRGB and ChangeStateBinary channel on the MQTT Broker Thing to catch these intents:

Here’s the channel configuration–it looks basically the same in each. Note the “MQTT Topic” (e.g. hermes/intent/ChangeStateRGB). This is how we listen only to these events we want to hear and nothing else.

At this point, save the Thing. This is done and won’t be edited again unless you add new intents in Rhasspy.

Create an “MQTT Handler” Rule

Next, we need to do something when an event happens on the ChangeStateRGB or ChangeStateBinary channel of the new MQTT Broker Thing. These events will be picked up automatically whenever Rhasspy interprets commands, but right now openHAB is just ignoring them. To start paying attention, go to Settings and click “Rules” under Automations, then add a new rule.

In the When section, add all your channels from the MQTT Broker Thing. Any time MQTT Broker receives an update, we want to activate this rule.

Under the Then section, click Add Action and choose Run Script. Choose the scripting language “ECMAScript 262 Edition 11” (or higher if you’re reading this years from now). openHAB ships with 262 edition 5.1 included, but from what I’ve read you’re missing access to some variables in there and edition 11 is going to be a smoother experience.

At this point, we want to parse the data attached to the MQTT event, then do something with it. Here’s what an MQTT event coming from Rhasspy will look like:

{
  "input": "turn off the TeaRoomFloorLamp",
  "intent": {
    "intentName": "ChangeStateRGB",
    "confidenceScore": 1
  },
  "siteId": "default",
  "id": "5833d3ae-30f5-4790-9e01-c16787a353bd",
  "slots": [
    {
      "entity": "state",
      "value": {
        "kind": "Unknown",
        "value": "off"
      },
      "slotName": "state",
      "rawValue": "off",
      "confidence": 1,
      "range": {
        "start": 5,
        "end": 8,
        "rawStart": 5,
        "rawEnd": 8
      }
    },
    {
      "entity": "RGBLights",
      "value": {
        "kind": "Unknown",
        "value": "TeaRoomFloorLamp"
      },
      "slotName": "name",
      "rawValue": "tea room floor lamp",
      "confidence": 1,
      "range": {
        "start": 13,
        "end": 32,
        "rawStart": 13,
        "rawEnd": 32
      }
    }
  ],
  "sessionId": "5833d3ae-30f5-4790-9e01-c16787a353bd",
  "customData": null,
  "asrTokens": [
    [
      {
        "value": "turn",
        "confidence": 1,
        "rangeStart": 0,
        "rangeEnd": 4,
        "time": null
      },
      {
        "value": "off",
        "confidence": 1,
        "rangeStart": 5,
        "rangeEnd": 8,
        "time": null
      },
      {
        "value": "the",
        "confidence": 1,
        "rangeStart": 9,
        "rangeEnd": 12,
        "time": null
      },
      {
        "value": "TeaRoomFloorLamp",
        "confidence": 1,
        "rangeStart": 13,
        "rangeEnd": 32,
        "time": null
      }
    ]
  ],
  "asrConfidence": null,
  "rawInput": "turn off the tea room floor lamp",
  "wakewordId": null,
  "lang": null
}

This data is going to come through as a message with some junk in front of the JSON, but by coercing it to a string, running a regex match for the outer brackets, then JSON.parse()ing it, we get a clean object (todo: rewrite this in a sturdier, safer manner). At that point we can pull relevant information out like the intent, state, and device.

One thing to note when writing your script is to use var instead of let or const. No idea why, but running the rule back-to-back will cause collisions with variables that have already been defined with let or const, but it seems to run it clean each time with var.

Here’s my script (so far) that parses the MQTT message, then runs openHAB actions based on the intent, device, and state it pulled out of it:

/* Get the data from the MQTT event
 * {
 *   json: Event Object,
 *   intent: String (intent name),
 *   state: String ("ON" | "OFF"),
 *   device: String (device name)
 * }
 */
var data = {};
data.json = JSON.parse((event + "").match(/\{.*\}/)[0]);
data.intent = data.json.intent.intentName;
data.state = data.json.slots.find(e => e.slotName === "state").value.value.toUpperCase();
data.device = data.json.slots.find(e => e.slotName === "name").value.value;


console.log("MQTT rule triggered: " + data.intent + ", " + data.device + " " + data.state);

// Perform actions by MQTT intent
// object "items", function "items.getItem()", and function "<item>.sendCommand()" are provided by openHAB
if (data.intent === "ChangeStateRGB") {
  
  // handle ChangeStateRGB event
  if (data.state === "ON") items.getItem(data.device + "_ColorTemperature").sendCommand("35");
  if (data.state === "OFF") items.getItem(data.device + "_Color").sendCommand("0,0,0");
  
} else if (data.intent === "ChangeStateBinary") {
  
  // handle ChangeStateBinary event
  items.getItem(data.device).sendCommand(data.state);
  
}

If you need some visibility into your data here, you can tail -f /openhab/userdata/logs/openhab.log. Anything you console.log() here will show up in your terminal each time the rule is triggered. Just throw some test data at Rhasspy to repeatedly trigger it.

Next Steps

This is probably workable for most things I’m doing, although next I’ll need to figure out how to turn groups of items and areas on and off and pull data for dimming. Also of note is no feedback is going back out from here, so I won’t be getting responses when commands are executed yet. I’m sure this can all be tacked on now that I have working core functionality, though.

Let me know if this comes in handy for you, and what additional things you end up doing with it!

Jul 20, 2021

先週末、486のPCを買いました。この頃、486プロセッサを搭載したコンピュータは希少です。たぶんのを見たことがありません。とても古いです。

古いコンピュターが大好きです。若すぎて486を使うことができませんでした、でも私の一番台x86のコンピュターはPentiumでした。けれども押し入れで父の286と386と486がありました。

Cyrixの486DX2とマザーボード

いくつか眠れなかった夜はのを出しました。ぜんぜん分かりませんでした。ヒントが与えませんでした。12歳に486を使うことが大変でした。けれども、いま大人です。もう一度やりたい。

見つけるには簡単じゃなかったです。私の町でいたるところを探しました、でもみんなとすべての店は古いコンピュターをレサイクルします。シアトルのリサイクルショップに行きました。

シアトルのリサイクルショップ、RE・PC

ところが、一台がありませんでした、でも2つのマザーボードとシーピーユーがありました! 家に一つを持って来ました。ほか古くてランダムなパーツにもたくさん買いました!

猫はコンピューターの修理のが上手です。

いまパーツが足りません。電源回路がありません。PCケースは非対応。リボンケーブルは新しくて過ぎます。いくつかのパーツが駄目かもね。

取り合わせるのときに、また書きます。


Apologies, I am learning Japanese and figured some less interesting projects could make good writing prompts for practice. すみません。書きます、でもまだ上手じゃありません。

Jul 8, 2021

I recently received complaints from WordPress multisite users about not being able to embed iframes for Google calendars. Upon testing, I found nothing wrong–embeds worked absolutely fine.

My users kept insisting they disappeared, though, and could prove it with screenshots.

Nothing was off about their browsers. Nothing strange was happening with plugins. They’d just create a Custom HTML block, and then paste a totally normal <iframe> embed in, like so:

<iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" width="560" height="315" frameborder="0"></iframe>

As soon as they clicked publish and left the page, poof! Gone.

It seems this was a change in security policy in Gutenberg. I’m not sure when this change happened, but if one of your multisite users (even an administrator!) wants to place iframes or a <script> tag into a Custom HTML block, WordPress will simply delete it.

This isn’t hard to change, luckily. You just need to add a filter to wp_kses_allowed_html. The best way to do this would be with a plugin. It would look like this:

<?php
/*
Plugin Name: Allow iframe Tags in Editor
Description: Allows use of iframe tags in Custom HTML block for non-super-admins.
Author: Joshua Woehlke
*/

function allow_iframes( $allowedposttags ){

	$allowedposttags['iframe'] = array(
		'align' => true,
		'allow' => true,
		'allowfullscreen' => true,
		'class' => true,
		'frameborder' => true,
		'height' => true,
		'id' => true,
		'marginheight' => true,
		'marginwidth' => true,
		'name' => true,
		'scrolling' => true,
		'src' => true,
		'style' => true,
		'width' => true,
		'allowFullScreen' => true,
		'class' => true,
		'frameborder' => true,
		'height' => true,
		'mozallowfullscreen' => true,
		'src' => true,
		'title' => true,
		'webkitAllowFullScreen' => true,
		'width' => true
	);

	return $allowedposttags;
}

add_filter( 'wp_kses_allowed_html', allow_iframes, 1 );

?>

If you want <script> tags available, you’d do the same thing for $allowedposttags['script'] within that same filter.

I won’t be uploading this to the official WP plugin directory because, per the developer expectations, all code “should be made as secure as possible.” I understand this can be interpreted in different ways, but we’re literally removing a security feature WordPress chose to implement, so…

You can clone this from github into your plugins folder, or download the .zip.

Jul 17, 2020

I receive frequent emails about my platinum cure silicone posts (part 1, part 2). People all around the world want clarification of my process, so I’m happy to provide it.

I’ve been casting platinum cure silicone in SLA printed masters for over a year now for my keycap business, Cherry Festival, and have successfully done it hundreds of times with perfect accuracy in the finished mold. I’ve settled on a very strict but easy process to ensure it works every time.

Here’s what you do:

  1. Wash all excess resin from your part

    It’s important to have a completely dry, clean part for this process. I wash my parts under a heavy stream of water, then place them into an ultrasonic bath of isopropyl alcohol, but you can use any method. Just make sure all uncured resin is cleaned off.
     
  2. Heat your part at 300F/150C for 2-3 minutes

    This is half the secret. You’re looking for wisps of smoke. I use a toaster oven for this, but any heated chamber where smoke can escape will work. The moment I see wisps of smoke coming out of the door, I open it. As cold air rushes in, the part will smoke heavily. This is good! If you let it heat for too long, the part will develop cosmetic fractures. The perfect heating is right before these fractures would appear. Also, don’t breathe the smoke.
     
  3. UV cure your part normally

    Don’t go overboard, just do it how you would normally do it. Extended UV curing does not make this process work any better.
     
  4. Tumble your part in Inhibit X for 5 minutes

    I like to place the parts in a jar of Inhibit X inside a rock tumbler, but this probably works fine just swishing them around every few minutes by hand. You just want the Inhibit X to touch every surface and have enough time to get those surfaces fully reacted.
     
  5. Let your part dry

    This happens pretty fast. Once the parts are dry, they’re ready for platinum cure silicone.
     

So long as your silicone is mixed well, the quality of the mold will be the same as any other non-SLA part. No gumminess, no lost detail.

I’d love to see what you make with this process! And please feel free to keep emailing–I’m always happy to answer any questions about this.

Fork me on GitHub