A rainy, soggy, somewhat intense week. I almost enjoyed it.
Monday, 2024-02-05
Pretty quiet day, managed to get a lot done at work.
- Got a few Arduino Pro Micros in the mail and piled them up in a project box while I printed the rest of the BOM for that project. I like this chip because it has a lot of easy to use analog inputs and you can change the USB HID descriptions to just about anything you want, which comes in handy more often than you’d think.
- Upgraded the kernel and Intel microcode on
borg
, since apparently there are improvements in scheduling across both P-cores and E-cores in newer CPUs (you can getintel-microcode
from thenon-free
repositories).
Tuesday, 2024-02-06
Didn’t really get anything fun done until the evening.
- Printed out the parts for a clever joystick-based
6DOF
controller and spent a while investigating SpaceMouse USBHID
reporting. Turns out someone figured most of it out already–except, apparently, all the buttons, but those are documented in this library. - Spent a little while poking at Godot in hopes of building a small desktop app to automate a few things. The UI box model is a bit weird, so my control layout is currently all over the place.
- Recoded my Proxmox VM to HomeKit via Tasmota bridge in
asyncio
, making it a lot simpler and more responsive while using only distribution packages:
#!/usr/bin/env python3
# Requirements: apt install python3-asyncio-mqtt
from asyncio import run, create_task, sleep, create_subprocess_shell
from asyncio.subprocess import PIPE, STDOUT
from asyncio_mqtt import Client
from datetime import datetime
from json import dumps, loads
from logging import basicConfig, INFO, DEBUG, WARNING, getLogger
basicConfig(level=INFO, format='%(asctime)s %(levelname)s %(funcName)s:%(lineno)s %(message)s')
log = getLogger(__name__)
cluster_status = {}
vm_sets = {
"gpu_exclusive_set": ['106','108'],
"media": ['100'],
"windows": ['300','301','303']
}
monitored_vms = list(set([item for sublist in vm_sets.values() for item in sublist]))
async def setup_entities(c: Client) -> None:
global monitored_vms
for vm in monitored_vms:
await c.publish(f"homeassistant/switch/proxmox_{vm}/config", dumps({
"name": f"Proxmox VM {vm}",
"stat_t": f"proxmox_{vm}/tele/STATE",
"avty_t": f"proxmox_{vm}/tele/LWT",
"pl_avail": "Online",
"pl_not_avail": "Offline",
"cmd_t": f"proxmox_{vm}/cmnd/POWER",
"val_tpl": "{{value_json.POWER}}",
"pl_off": "OFF",
"pl_on": "ON",
"uniq_id": f"proxmox_{vm}",
"dev": {
"ids": [f"proxmox_{vm}"]
}
}))
await c.subscribe(f"proxmox_{vm}/#")
async def pvesh(path: str, verb: str="get") -> dict:
proc = await create_subprocess_shell(f"pvesh {verb} {path} --output-format json", stdout=PIPE)
log.debug(proc)
stdout, _ = await proc.communicate()
log.debug(stdout)
try:
return loads(stdout.decode())
except Exception as e:
log.warning(e)
return None
async def monitor_cluster_status(c: Client) -> None:
while(True):
await update_cluster_status(c)
await sleep(45)
async def update_cluster_status(c: Client) -> None:
global cluster_status, monitored_vms
now = datetime.now().isoformat()
for i in await pvesh("/cluster/resources --type vm"):
log.debug(i)
vm = i['id'].split("/")[1]
cluster_status[vm] = {
"id": i['id'],
"node": i['node'],
"name": i['name'],
"status": i['status']
}
if vm in monitored_vms:
log.debug(f"{vm} {i['status']}")
await c.publish(f"proxmox_{vm}/tele/HASS_STATE", dumps({"Version":"0.1","Module":"Proxmox VE"}))
await c.publish(f"proxmox_{vm}/tele/STATE", dumps({"Time":f"{now}","POWER": "ON" if i['status']=="running" else "OFF" }))
async def queue_action(c: Client, path: str) -> None:
await pvesh(path, "create")
await sleep(30)
await update_cluster_status(c)
async def main() -> None:
async with Client("mosquitto.local") as c:
create_task(monitor_cluster_status(c))
await setup_entities(c)
async with c.messages() as messages:
async for message in messages:
for vm in monitored_vms:
if message.topic.matches(f"proxmox_{vm}/cmnd/POWER"):
log.info(f"{message.topic} {message.payload}")
type_id = cluster_status[vm]["id"]
node = cluster_status[vm]["node"]
action = message.payload.decode("utf-8").lower()
status = "start" if action=="on" else "shutdown"
create_task(queue_action(c, f"/nodes/{node}/{type_id}/status/{status}"))
if __name__ == "__main__":
run(main())
To keep it running, I just tacked this unit file on and called it a night:
[Unit]
Description=Tasmota VM bridge
After=network.target
[Service]
ExecStart=/root/scripts/tasmota.py
Restart=always
Type=simple
[Install]
WantedBy=network.target
Wednesday, 2024-02-07
Had a little time in the early morning and a few scattered minutes throughout the day.
- Upgraded Klipper on my KP3S Pro and tweaked the config to dial back speed a bit since I am still getting the occasional Y layer shift.
- Threw away a couple of
CAD
designs and started new ones, because the old ones sucked (and were around a year old anyway). - Poked at some
LLM
stuff. 64GB of RAM might not be enough for what I want to do, so I gave my sandbox 96GB of RAM temporarily.borg
is getting a bit tight (and CPU inference is still much slower than I’d like, but fortunatelyollama
can split models between GPU and system RAM). - Assembled my prototype
6DOF
controller and spent an entertaining hour in the evening getting the Arduino to spoof the USB IDs of a legitimate SpaceMouse:
I had to tweak the Arduino sketch a fair bit to make it work “right” for me, but the 3Dconnexion preference pane adopted it as one of its own:
#include "HID.h"
#define DOF 6
// Analog inputs from joysticks
int port[DOF] = {A0, A2, A6, A1, A3, A7};
int origin[DOF]; // initial sensor values
// conversion matrix from sensor input to rigid motion
int coeff[DOF][DOF] = {
{ 0, 0, 0, 10, 10, -20}, // TX
{ -3, -3, -3, 0, 0, 0}, // TZ
{ 0, 0, 0, -17, 17, 0}, // TY
{ 3, 3, -6, 0, 0, 0}, // RX
{ 0, 0, 0, 3, 3, 3}, // RZ
{6, -6, 0, 0, 0, 0}, // RY
};
int maxValues[2][DOF] = {
{3000, 4000, 4000, 4000, 3000, 4500},//Positive Direction
{3000, 4000, 1500, 4000, 3000, 4500} //Negative Direction
};
static const uint8_t _hidReportDescriptor[] PROGMEM = {
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x08, // 0x08: Usage (Multi-Axis)
0xa1, 0x01, // Collection (Application)
0xa1, 0x00, // Collection (Physical)
0x85, 0x01, // Report ID
0x16, 0x00, 0x80, //logical minimum (-500)
0x26, 0xff, 0x7f, //logical maximum (500)
0x36, 0x00, 0x80, //Physical Minimum (-32768)
0x46, 0xff, 0x7f, //Physical Maximum (32767)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x09, 0x32, // Usage (Z)
0x75, 0x10, // Report Size (16)
0x95, 0x03, // Report Count (3)
0x81, 0x02, // Input (variable,absolute)
0xC0, // End Collection
0xa1, 0x00, // Collection (Physical)
0x85, 0x02, // Report ID
0x16, 0x00, 0x80, //logical minimum (-500)
0x26, 0xff, 0x7f, //logical maximum (500)
0x36, 0x00, 0x80, //Physical Minimum (-32768)
0x46, 0xff, 0x7f, //Physical Maximum (32767)
0x09, 0x33, // Usage (RX)
0x09, 0x34, // Usage (RY)
0x09, 0x35, // Usage (RZ)
0x75, 0x10, // Report Size (16)
0x95, 0x03, // Report Count (3)
0x81, 0x02, // Input (variable,absolute)
0xC0, // End Collection
0xa1, 0x00, // Collection (Physical)
0x85, 0x03, // Report ID
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 32, // Report Count (24)
0x05, 0x09, // Usage Page (Button)
0x19, 1, // Usage Minimum (Button #1)
0x29, 32, // Usage Maximum (Button #24)
0x81, 0x02, // Input (variable,absolute)
0xC0, // End Collection
0xC0 // End Collection
};
void setup() {
static HIDSubDescriptor node(_hidReportDescriptor, sizeof(_hidReportDescriptor));
HID().AppendDescriptor(&node);
delay(300);
for (int i = 0; i < DOF; i++) {
origin[i] = analogRead(port[i]);
}
}
void send_command(int16_t x, int16_t y, int16_t z, int16_t rx, int16_t ry, int16_t rz) {
uint8_t trans[6] = { x & 0xFF, x >> 8, y & 0xFF, y >> 8, z & 0xFF, z >> 8 };
HID().SendReport(1, trans, 6);
uint8_t rot[6] = { rx & 0xFF, rx >> 8, ry & 0xFF, ry >> 8, rz & 0xFF, rz >> 8 };
HID().SendReport(2, rot, 6);
}
void loop() {
int sv[DOF]; // sensor value
int mv[DOF]; // motion vector
int moveFlag = false;
for (int i = 0; i < DOF; i++) {
sv[i] = analogRead(port[i]) - origin[i];
}
for (int i = 0; i < DOF; i++) {
mv[i] = 0;
for (int j = 0; j < DOF; j++) {
mv[i] += coeff[i][j] * sv[j];
}
//Rescale to max value range
if (mv[i] > 0) {
mv[i] = (mv[i]) / (maxValues[0][i]/127);
}
else {
mv[i] = (mv[i]) / (maxValues[1][i]/127);
}
if(mv[i] > 127) {
mv[i] = 127;
}
else if(mv[i] < -128) {
mv[i] = -128;
}
}
send_command(-mv[0], mv[2], mv[1], mv[3], mv[5], -mv[4]);
}
It works surprisingly well, but after a few days of testing I really need to re-design some parts that are a bit too loose.
Thursday, 2024-02-08
Weekly late night meeting day, with a pretty great surprise.
- Fiddled with USB device and vendor IDs just after breakfast, like an engineer ought to (for the record:
vid=0x256f, pid=0xc635, SpaceMouse Compact
). - Whipped together a little Node-RED service to take iOS Health data sent by Shortcuts via HTTP POST and have an LLM provide some commentary on it. Shortcuts remains every bit as inconsistent and irritating as AppleScript…
- Received a Two Trees SK1 to test, which serendipitously is a very snug fit atop an IKEA
KALLAX
(not a millimetre to spare). Very impressed with the build quality.
- After a few weeks of using Agenda I realized that it was getting in the way of both taking quick notes and polishing and finishing drafts, and thus I decided to give Heynote a chance. However, having yet another app with yet another Electron version on my system didn’t jibe, and I decided going back to iA Writer for maintaining multiple drafts would be easier.
- Everything I had “scheduled” in Agenda moved over to
Reminders
, which is now surprisingly usable with its multiple Kanban-like columns and sub-tasks.
Now I know why there are a zillion notes apps.
Friday, 2024-02-09
Ran a few end-of-week errands, had a decent evening tinkering.
- Received a few more electronics parts from AliExpress (including a couple of wrong ones, so I dread trying to sort this out).
- Started testing the Two Trees SK1 with a few functional prints. The built-in Klipper macros are organized a bit differently from what I’m used to, so I made a few changes. Even without calibration, the print quality at speed is pretty amazing.
- Had a stab at enabling IPv6 for
docker
containers on my Synology:
# sudo cat /var/packages/ContainerManager/etc/dockerd.json
{
"data-root":"/var/packages/ContainerManager/var/docker",
"log-driver":"db",
"registry-mirrors":[],
"storage-driver":"btrfs",
"ipv6": true,
"fixed-cidr-v6": "2001:db8:1::/64"
}
# replace the CIDR above with a proper one! I picked fdde:adbe:ef42:cafe::/64
# sudo systemctl restart pkg-ContainerManager-dockerd
However, Synology removes the "ipv6": true
upon restart, which is… sub-optimal. Parked this one for the moment.
Saturday, 2024-02-10
Rainy day, which was a good excuse to stay indoors and tinker.
- Read the Economist and generally caught up on news.
- Personal inbox zero and some advisory work during the afternoon.
- Adjusted the Two Trees SK1 configuration a bit more to my liking and got it to print a number of functional parts.
- Did some
I2C
tinkering with an accelerometer. Plain C can be remarkably refreshing sometimes. - Cleaned up and posted my notes on the YooYeeToo R1, which led to another bonus
I2C
hack. - Set up a neat little app for the Circuit Tracks that I’ll be trying out and reviewing.
Sunday, 2024-02-11
Lazy, leisurely day.
- Played a few games, watched some morning TV.
- Finally figured out an ancient, but working solution to have the G Gloud send motion inputs to Bazzite.
- Tried (somewhat in vain) to clean up my office, but the sheer number of electronics and spare parts is becoming an issue.
- Disassembled the Two Trees SK1’s toolhead to have a look at what kind of hotend and nozzles it uses. Surprisingly, it seems to use a Bambu Labs hotend clone with multiple improvements, like a reinforced heatbreak and removable nozzles. Fished around on AliExpress for a few spares to try out.
- Flashed CYD-Klipper onto one of the 2.4” “cheap yellow displays” I recently got and had it monitor the Two Trees SK1 while it printed:
I can’t quite get over the fact that we have stupefyingly high speed, REST
-enabled 3D printers these days.