Category Archives: IOT / Domoticz

Mattermost notification thingy

3D printed a little light case for a wemos and a piece of WS2812 led strip I had lying around.

Schematic:
NOTE: The resistor is 100-500 ohm (I forgot, just try)
You can only use this trick for a few leds (I used 4), else you better can use the sacrifice a led to make a level shifter trick.
(Wemos logic is 3.3V and the led strip is 5V)

I flashed ESPHome on the wemos using the flasher in Home Assistant.

Code:

esphome:
  name: matternotification
  friendly_name: matternotification

esp8266:
  board: d1_mini

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "ogFxZUXerNxxxxxxxxxxxxxxxxxWaWyJVxCM="

ota:
  - platform: esphome
    password: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Matternotification"
    password: "rxxxxxxxxxxxxxxx"

captive_portal:

light:
  - platform: neopixelbus
    type: GRB
    variant: WS2812
    pin: D4
    num_leds: 4
    name: "NeoPixelMattermost"        

To get the status of messages and controlling the HA entity, I made a bash script.

First curl command to get a token from Mattermost using the API.
Second curl command to get messages states from Mattermost.

Bottom two curl command turn a light entity on or off in your Home Assistant server using a API

#!/bin/bash
#set -x
# change : mattermost username and password (and server)
# change : mattermost userid and teamid
# change : home assistant long time token (and HA server) 
# change : light entity
#
while true; do
# Get token using login
#token=$(curl -s -i -X POST -H 'Content-Type: application/json' -d '{"login_id":"username","password":"password"}' https://mattermostinstance.com/api/v4/users/login | grep ^Token | awk '{ print $2 }' | tr -d '\r' )
#using a MM auth token (see below)
token=xxxxxxxxxxxxxxxxxxxx

# Get messages 
# Gives you something like
# {"team_id":"j3fd7gksxxxxxxxxxxxxxjr","channel_id":"rroxxxxxxxxxxxxxxtueer","msg_count":0,"mention_count":0,"mention_count_root":0,"urgent_mention_count":0,"msg_count_root":0}
# We need to count ":0" 

messages=$(curl -s  -i -H "Authorization: Bearer ${token}"  https://mattermostinstance.com/api/v4/users/ou5nz5--USER-UUID--rbuw4xy/channels/rropejn--TEAM-ID--tueer/unread | grep channel 
| grep -o ":0" | wc -l)

# If 5 times ":0" then no messages
if [ $messages == 5 ] ; then
# Turn off
curl -s -X POST -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cC--HOME-ASSISTANT-LONG-TIME-TOKEN-CBusTgTUueWpPNdH5WAWOE" \
       -H "Content-Type: application/json" \
       -d '{"entity_id": "light.matternotification_neopixelmattermost_2"}' \
       http://192.168.1.2:8123/api/services/light/turn_off > /dev/null
else
# Turn on
curl -s -X POST -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cC--HOME-ASSISTANT-LONG-TIME-TOKEn--CBusTgTUueWpPNdH5WAWOE" \
       -H "Content-Type: application/json" \
       -d '{"entity_id": "light.matternotification_neopixelmattermost_2"}' \
       https://192.168.1.2:8123/api/services/light/turn_on > /dev/null
fi
sleep 5
done

Get a Long-lived access token from HA:

Profile > Security and Create Token

Create a token in Mattermost:

Home Assistant with Openhasp and cheap display using LGVL.

I bought a cheap esp32 display (4 inch 480×480) from China.

It also has three relays to control lights.

Below is a gallery with default screens.

Flashing OpenHasp was a breeze.
Configure the thing for HA was not so easy.

Install OpenHasp via Hacs on HA.

There is a webinterface on the display, here you have to configure wifi, mqtt and the pages.

Config files used for this first test:

{"page":1,"id":34,"obj":"img","src":"L:/pcb480x480.png","auto_size":0,"w":480}
{"page":1,"id":1,"obj":"btn","x":0,"y":0,"w":480,"h":50,"text":"IOTDesigns","value_font":22,"bg_color":"#2C3E50","text_color":"#FFFFFF","radius":0,"border_side":0}
{"page":1,"id":2,"obj":"btn","x":10,"y":60,"w":105,"h":90,"toggle":true,"text":"\uE335","text_font":32,"mode":"break","align":1}
{"page":1,"id":3,"obj":"dropdown","x":10,"y":160,"w":170,"h":60,"options":"Option 1\nOption 2\nOption 3\nOption 4"}
{"page":0,"id":1,"obj":"label","x":375,"y":45,"h":40,"w":100,"text":"00.0°C","align":2,"bg_color":"#2C3E50","text_color":"#FFFFFF"}
{"page":1,"id":6,"obj":"slider","x":20,"y":300,"w":440,"h":40,"min":15,"max":85}

Designer at : https://haspdesigner.qrisonline.nl/

4inchdisplay2:
    objects:
      - obj: "p0b1"  # temperature label on all pages
        properties:
          "text": '{{ states("sensor.livingtemperature") }}°C'
      - obj: "p1b2"  # light-switch toggle button
        properties:
          "val": '{{ 1 if states("switch.livingshelly") == "on" else 0 }}'
          "text": '{{ "\uE6E8" if is_state("switch.livingshelly", "on") else "\uE335" | e }}'
        event:
          "up":
            - service: homeassistant.toggle
              entity_id: "switch.livingshelly"
      - obj: "p1b3"  # dropdown
        event:
          "changed":
            - service: persistent_notification.create
              data:
                message: I like {{ text }}
      - obj: "p1b6" # Light brightness
        properties:
          "val": "{{ state_attr('number.dinnertable_brightness_0', 'brightness') if state_attr('number.dinnertable_brightness_0', 'brightness') != None else 0 }}"
        event:
          "changed":
            - service: light.turn_on
              data:
                entity_id: number.dinnertable_brightness_0
                brightness:  "{{ val }}"
          "up":
            - service: light.turn_on
              data:
                entity_id: number.dinnertable_brightness_0
                brightness:  "{{ val }}"

NOTE: Dimmer is not working via HA (yet), but mqtt messages are working.

objTypeDescriptionExtra Parts
btnBinaryButton
switchToggleSwitchindicator, knob
checkboxToggleCheckboxindicator
labelVisualLabel
ledVisualLED
spinnerVisualSpinnerindicator
objVisualBase Object
lineVisualLine
imgVisualImage
cpickerSelectorColor pickerknob
rollerSelectorRollerselected
dropdownSelectorDropdown Listselected, items, scrollbar
btnmatrixSelectorButton Matrixitems
msgboxSelectorMessageboxitems, items_bg
tabviewSelectorTabviewitems, items_bg, indicator, selected
tabSelectorTab
barRangeProgress Barindicator
sliderRangeSliderindicator, knob
arcRangeArcindicator, knob
linemeterRangeLine Meter
gaugeRangeGaugeindicator, ticks
qrcodeVisualQrcode

Flipper Zero

I’ve got a flipper zero at last.
https://flipperzero.one/

I know, it’s more an useful toy than a serious tool.
It’s too limited. But useful for me.

Learning about tools and sub gigahertz monitoring.

I hoped to get a BFFB for it, that will be a big plus.
https://www.justcallmekokollc.com/product/flipper-zero-bffb/31


One of the first things was reflashing the device with Momentum firmware.
I’ve ordered a Wi-Fi Dev Board, so I can use Marauder.

Here are some qFlipper screenshots.

Will add pictures and info about the Wifi dev board.

Some information:

The Flipper Zero is a versatile multi-tool for geeks, hackers, and hardware enthusiasts. It is designed as a portable, open-source device with numerous capabilities for interacting with digital systems and hardware. Here’s an overview of what the Flipper Zero can do:

1. RFID and NFC Communication

  • Read and Emulate: Supports RFID cards (low-frequency 125 kHz) and NFC cards (high-frequency 13.56 MHz). It can read, emulate, and clone certain types of RFID/NFC tags, such as access cards and contactless payment cards (within legal limits).
  • Protocols Supported: Includes MIFARE, HID Prox, and others used in access control systems.

2. Sub-GHz Radio Transmission

  • Works with a wide range of sub-GHz frequencies (300-900 MHz) used in garage door openers, key fobs, IoT devices, and wireless sensors.
  • Transmit and Analyze: It can capture, analyze, and even replay radio signals for research and testing purposes.

3. Infrared (IR) Control

  • Universal Remote: The Flipper Zero has an IR transmitter/receiver that allows it to control TVs, air conditioners, and other IR-enabled devices.
  • It can learn IR commands and replay them for universal control.

4. GPIO Pins for Custom Projects

  • Hardware Hacking: Provides GPIO (General Purpose Input/Output) pins for connecting to external hardware.
  • You can use the GPIO pins to interact with sensors, control relays, or debug devices like routers or microcontrollers.

5. Bluetooth and Wi-Fi (with Modules)

  • Bluetooth LE: Built-in Bluetooth Low Energy support allows communication with BLE-enabled devices.
  • Wi-Fi: Optional Wi-Fi dev board attachment (like the ESP8266 or ESP32) expands its capabilities for network penetration testing or IoT device research.

6. BadUSB and HID Attacks

  • Emulate USB Devices: Can act as a USB keyboard or mouse for automating tasks or security testing.
  • Useful for penetration testing with scripts (similar to tools like Rubber Ducky).

7. Universal Debugging

  • The Flipper can debug and interact with devices via UART, SPI, and I2C protocols, making it a powerful tool for developers and hackers.

8. Tamagotchi Mode

  • Includes a fun “pet” feature where you care for and interact with a digital creature that grows and evolves based on how you use the device.

9. Extensible and Open Source

  • The Flipper Zero’s firmware is open-source, allowing developers to modify and expand its capabilities.
  • It supports custom plugins, applications, and firmware modifications.

10. Signal Analysis and Replay

  • Capture, analyze, and replay signals (e.g., remote controls) for testing and research.
  • Legal Disclaimer: Using these features responsibly and within the bounds of the law is crucial.

Common Uses

  • Security auditing and penetration testing.
  • Reverse engineering and debugging hardware.
  • Researching IoT devices and wireless communications.
  • Fun DIY projects and learning electronics.

The Flipper Zero is a powerful tool, but its legality depends on how it is used. Be sure to respect laws and ethical guidelines when exploring its capabilities.

Reverse engineering Epaper Arduino for own image pusher

To display quotes, changing once per hour.

There is not much to be found for Waveshare 4.2 Epaper.
Except for an Arduino web example.
( see https://www.waveshare.com/wiki/E-Paper_ESP32_Driver_Board )

I reversed engineered the workings, and created a python upload script to push images.

Original workings are a mess.
Per 4 bit of color, high-low switched in a byte.
Black and red separated.
Using a till p encoding over curl commands.

My implementation uses a python script called as:

python3 epaper-pusher.py ~/Downloads/Untitled.png
http://10.1.0.99/EPDI_
30 times something like 
http://10.1.0.99/ppppppppppppppppppppppppppppppppppppppppppppppppppppppaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppiodaLOAD_
http://10.1.0.99/NEXT_
30 times something like
http://10.1.0.99/pbcdefghijjjjjjffffffoooooooaaabbbbbbeeeedddppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppiodaLOAD_
http://10.1.0.99/SHOW_
NOTES:
a = 0000
-
-
-
p = 1111 = 15

30 lines with 1000 bytes ( ending with iodaLOAD_ )

black pixels
first block 1
second block 0

red pixels
first block 0
second block 1

white pixels
first block 1
second block 1

PIXEL Example
RBRB
BWBW

First block 
1010 - letter K
0101 - Letter F - second nibble = white

Second block
0101 - Letter F
1111 - Letter P - second nibble white

Code

from PIL import Image
import numpy
import requests

url="http://10.1.0.99/" 

black_pixels = numpy.zeros((400,300))
red_pixels = numpy.zeros((400,300))



def classify_pixel_color(pixel):
    """
    Classify a pixel as black, white, or red.
    """
    r, g, b = pixel[:3]  # Ignore alpha if present

    # Define thresholds for classification
    if r < 128 and g < 128 and b < 128:
        return 'black'
    elif r > 200 and g > 200 and b > 200:
        return 'white'
    elif r > 128 and g < 100 and b < 100:
        return 'red'
    else:
        return None

def process_image(image_path):
    """
    Process the image and classify its pixels into black, white, or red.
    """
    image = Image.open(image_path)
    image = image.convert("RGB")  # Ensure the image is in RGB mode

    width, height = image.size
    pixel_data = image.load()

    color_counts = {'black': 0, 'white': 0, 'red': 0}

    for y in range (0, 299):
        for x in range (0, 399):
            black_pixels[x][y] = 0
            red_pixels[x][y] = 0

    for y in range(299):
        for x in range(399):
            color = classify_pixel_color(pixel_data[x, y])
            if color:
                color_counts[color] += 1
                if color == 'black':
                    black_pixels[x][y] = 1;
                if color == 'red':
                    red_pixels[x][y] = 1;
                if color == 'white':
                    black_pixels[x][y] = 1;
                    red_pixels[x][y] = 1;

    return color_counts, black_pixels, red_pixels

def number_to_letter(num):
    """
    Translates a number from 0 to 15 into a corresponding letter (a-p).

    Args:
        num (int): The number to translate.

    Returns:
        str: The corresponding letter (a-p).
    """
    if 0 <= num <= 15:
        return chr(ord('a') + num)
    else:
        raise ValueError("Number must be between 0 and 15, inclusive.")

def print_array_in_chunks(array, chunk_size=1001):
    current_chunk = ""
    for item in array:
        # Convert item to string and add to the current chunk
        item_str = str(item)
        if len(current_chunk) + len(item_str) + 1 > chunk_size:
            # Print the current chunk and reset it
            current_chunk += "iodaLOAD_"
            try:
                requests.get(url + current_chunk, verify=False)
                if not response.content:  # Equivalent to expecting an empty reply
                    pass
            except requests.exceptions.RequestException as e:
                # Catch any request-related errors
                pass
            current_chunk = item_str
        else:
            # Append the item to the current chunk
            current_chunk += (item_str)
    current_chunk += "iodaLOAD_"
    # Print any remaining items in the chunk
    if current_chunk:
        try:
            requests.get(url + current_chunk, verify=False)
            if not response.content:  # Equivalent to expecting an empty reply
                pass
        except requests.exceptions.RequestException as e:
            # Catch any request-related errors
            pass
        

def switch_in_pairs(arr):
    # Loop through the array with a step of 2
    for i in range(0, len(arr) - 1, 2):
        # Swap values at index i and i+1
        arr[i], arr[i + 1] = arr[i + 1], arr[i]
    return arr

if __name__ == "__main__":
    import sys

    if len(sys.argv) < 2:
        print("Usage: python3 script.py <image_path>")
        sys.exit(1)

    image_path = sys.argv[1]
    try:
        color_counts, black_pixels, red_pixels = process_image(image_path)
        try:
            requests.get(url + "EPDI_" , verify=False)
            if not response.content:  # Equivalent to expecting an empty reply
                pass
        except requests.exceptions.RequestException as e:
            # Catch any request-related errors
            pass

        
        lines=[]
        for y in range(300):
            for x in range(0,399,4):
                first = red_pixels[x][y]
                second = red_pixels[x+1][y]
                thirth = red_pixels[x+2][y]
                fourth = red_pixels[x+3][y]
                nibble = 0
                if (first ==  1):
                        nibble = nibble + 8
                if (second ==  1):
                        nibble = nibble + 4
                if (thirth ==  1):
                        nibble = nibble + 2
                if (fourth ==  1):
                        nibble = nibble + 1
                lines.append(number_to_letter(nibble))
        switched_array = switch_in_pairs(lines)
        print_array_in_chunks(switched_array)
        try:
            requests.get(url + "NEXT_" , verify=False)
            if not response.content:  # Equivalent to expecting an empty reply
                pass
        except requests.exceptions.RequestException as e:
            # Catch any request-related errors
            pass
        lines=[]
        for y in range(300):
            for x in range(0,399,4):
                first = black_pixels[x][y]
                second = black_pixels[x+1][y]
                thirth = black_pixels[x+2][y]
                fourth = black_pixels[x+3][y]
                nibble = 0
                if (first ==  1):
                        nibble = nibble + 8
                if (second ==  1):
                        nibble = nibble + 4
                if (thirth ==  1):
                        nibble = nibble + 2
                if (fourth ==  1):
                        nibble = nibble + 1
                lines.append(number_to_letter(nibble))
        switched_array = switch_in_pairs(lines)
        print_array_in_chunks(switched_array)

        try:
            requests.get(url + "SHOW_" , verify=False)
            if not response.content:  # Equivalent to expecting an empty reply
                pass
        except requests.exceptions.RequestException as e:
            # Catch any request-related errors
            pass

    except Exception as e:
        pass

Home Assistant and Dehumidifier

A while ago, I bought a small Dehumidifier for my wine cellar.
I liked it a lot, so I bought another for our bedroom.

I saw some posts about people asking which Dehumidifier is supported by Home Assistant.
This one is. The “Eeese Otto Dehumidifier”

This works with the LocalTuya integration.

There are many examples how to integrate LocalTuya in HA which can be found easily using a search on the net. So, I’m not going to explain that.

I could not find a configuration example, that’s why I’ll post that part here.

Pre config:

  • Install App on phone to connect Tuya device to cloud (one time only)
    You need this to extract the localkey
  • Add a developer account to https://eu.platform.tuya.com/
    (Enable devices and change from Read to Control)
    (Get localkey from API Explorer, here is also a hint to be found about the entities)
    See below pictures
  • Install LocalTuya to HA
End result after config

Gallery of config steps

Developer website information, where to find your credentials.
(And a list of entities)

Home Assistant Voice and OpenMqttGateway

Yesterday I got my Home Assistant Voice!

This is a Non-Cloud solution like Alexa and Google devices.
I only could play with it for a few minutes because I was working on Arduino code with an ILI9341 Display and a BME280 (Temperature/Humidity/Air pressure).

Today I got some new goodies in, one of these is a LilyGO LoRa display which works on 433 Mhz.

I flashed OpenMQTTGateway on this device.

In the past, I posted about the RFCOM Gateway using Domoticz.
This runs on a Raspberry Pi.
While looking for alternatives, I found a rtl-sdr solution.

https://github.com/merbanan/rtl_433

Using this:

But I liked the ESP32 solution more.
Now I can dismantle Domoticz, which served me well for many years.

How cool to see realtime updates!

Note: This is a receiver device only!
But I only use read-only sensors like : Door/window, doorbell, temperature/humidity and Firesensors.

These are automatically detected in Home Assistant.

No more RFXCOM with a Raspberry.

Display work

While working on a client project, I tested multiple displays.

  • ILI9341
  • 1.3inch SPI TFT LCD Display RGB (ST7789)
  • Waveshare 4.2 Epaper with ESP32 Controller

I thought it was fun to connect the Epaper to ESPHome.

This probably ends up being a Quote displayer
Universal e-Paper Driver Board with WiFi / Bluetooth SoC ESP32 onboard, supports various Waveshare SPI e-Paper raw panels

It was not without problems. For example, the ESPHome editor gave squiggly lines under type.
This has to be changed in the libraries.
(Already notified developers)

model: 4.20in-V2 does not work .. use model: 4.20in-v2

esphome:
  name: epaperqoute
  friendly_name: epaperqoute

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "tzRSzZky3Jk+hUYtiybzT90kxxxxxxxxxxxxxxxxxxxxx="

ota:
  - platform: esphome
    password: "4f127e114a7a44fxxxxxxxxxxxxxxxxxxxxx"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Epaperqoute Fallback Hotspot"
    password: "yLSoxxxxxxxxxx"

captive_portal:


external_components:
  - source: github://pr#6209
    components: [ waveshare_epaper ]

text_sensor:
  - platform: homeassistant
    entity_id: input_text.epaper_display_text
    id: epaper_display_text
    on_value:
      then:
        - component.update: epaperdisplay
    
spi:
  clk_pin: GPIO13
  mosi_pin: GPIO14

# Upload own ttf to a directory in esphome/fonts using file editor in Home Assistant
font:
  - file: "fonts/newspaper.ttf"
    id: tahoma
    size: 64

http_request:
  verify_ssl: false

# image test
online_image:
  - url: "https://www.henriaanstoot.nl/epapertest.png"
    id: example_image
    format: PNG

#it.image(0, 0, id(example_image));

display:
  - platform: waveshare_epaper
    id: epaperdisplay
    cs_pin: GPIO15
    dc_pin: GPIO27
    busy_pin: GPIO25
    reset_pin: GPIO26
    model: 4.20in-v2
    reset_duration: 200ms
    update_interval: never
    lambda: |
           it.printf(0, 0, id(tahoma), "%s", id(epaper_display_text).state.c_str());  

BLD-305S and Arduino

Part of a client’s build for powerful DC motors, so no details

Controlling this with an Arduino is straightforward, except for the SV signal.

This controls the speed using a voltage level.
A Uno has analog inputs, no outputs.

The trick is using a digital potmeter.

256 steps potmeter MCP41100

#include <SPI.h>
int svpin = 5;
setup:
  pinMode(svpin, OUTPUT);

loop:
// SPI Digital potmeter
  digitalPotWrite(0x20);

Divers …

Running into some Ubuntu machines with keyboard mouse problems after upgrading to 24.04

fix:

apt get install xserver-xorg-input-synaptics
apt get install xserver-xorg-input-all

3D printing some test models generated with AI from a photo to make some boardgame pieces.

Meanwhile, I am testing big motor controllers for a new client.

Last week I was at a friend’s place, time to make a launcher creator in bash

#!/bin/bash
#
if [ $# -lt 2 ]; then
	echo "createlauncher.sh name (path/bin) path/name"
    exit 1
fi


cat << EOF > /tmp/$1
[Desktop Entry]
Type=Application
Terminal=false
Name=$1
Icon=~/bin/icon/$1.png
Exec=$2 $3
EOF

cp /tmp/$1  ~/.local/share/applications/$1.desktop
update-desktop-database

Made a cable holder in my lab (Already modded)
Can be folded upwards.

Did a lot of work in my new lab/workshop.

Got some cool new tools in. Post later

Also working on a new arrangement for a bagpipe tune.