OpenPnP script to apply global rotation offset to board

#!/usr/bin/env python3
import sys
import argparse
import xml.etree.ElementTree as ET

def rotate_placements(board_file, rotation_offset):
    tree = ET.parse(board_file)
    root = tree.getroot()

    for placement in root.iter('placement'):
        location = placement.find('location')
        if location is not None:
            rotation = float(location.get('rotation', 0))
            new_rotation = (rotation + rotation_offset) % 360
            location.set('rotation', str(new_rotation))
            print(rotation, new_rotation)

    tree.write(board_file, encoding="utf-8", xml_declaration=True)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Rotate placements in an OpenPnP board.xml file")
    parser.add_argument("board_file", type=str, help="Path to the board.xml file")
    parser.add_argument("-r", "--rotation-offset", required=True, type=float, help="Rotation offset in degrees")

    args = parser.parse_args()

    rotate_placements(args.board_file, args.rotation_offset)

 

Posted by Uli Köhler in Allgemein

How to fix Angular “ng build” not producing index.html

Unless you have errors in your build (which is clearly visible from looking at the ng build output), the reason why ng build doesn’t produce an index.html is that the resulting bundle exceeds the maximum allowed size.

This is evident from the output such as

Error: bundle initial exceeded maximum budget. Budget 1.00 MB was not met by 3.73 kB with a total of 1.00 MB.

In order to fix it, either shrink your application by removing unneeded features or libraries or splitting off certain features into extra modules or, at least temporarily, increase the allowable budget.

In order to do that, open angular.json and look for the "budgets": {... section:

"budgets": [
  {
    "type": "initial",
    "maximumWarning": "500kb",
    "maximumError": "1mb"
  },
  {
    "type": "anyComponentStyle",
    "maximumWarning": "2kb",
    "maximumError": "4kb"
  }
],

Increase the maximumError value for "type": "initial" (the first entry in the list shown above).

For example, you could increase it from 1mb to 4mb to fix your build.

After that, run your ng build command again and index.html should be generated.

Be aware, however, that having huge JS bundles can really slow down your web application especially for mobile users.

Posted by Uli Köhler in Angular

How to query USB device information using udev

You can use udevadm to query information such as the USB device path for a given USB device such as /dev/usbtmc0:

udevadm info -q all /dev/usbtmc0

Example output:

P: /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.3/1-1.3:1.0/usbmisc/usbtmc0
N: usbtmc0
L: 0
E: DEVPATH=/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.3/1-1.3:1.0/usbmisc/usbtmc0
E: DEVNAME=/dev/usbtmc0
E: MAJOR=180
E: MINOR=176
E: SUBSYSTEM=usbmisc

 

Posted by Uli Köhler in Linux

Thorlabs PM100(D) USB-TMC udev rules

SUBSYSTEMS=="usb", ACTION=="add", ATTRS{idVendor}=="1313", ATTRS{idProduct}=="8078", GROUP="dialout", MODE="0666"

How to install:

sudo cp 99-thorlabs-pm100.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules && sudo udevadm trigger

 

Posted by Uli Köhler in Linux

How to fix PyVISA ValueError: Please install PyUSB to use this resource type

Problem:

When trying to run a Python script using PyVISA with a USB instrument, you see the following error message:

ValueError: Please install PyUSB to use this resource type.
No module named 'usb'

Solution:

On Linux distributions such as Ubuntu, you can fix this using

sudo apt-get -y install python3-usb

This will install the Python3 usb package and the libusb system packages.

Posted by Uli Köhler in Python

What is the default IP address for the NETGEAR MS108EUP?

The default IP address for the Netgear MS108EUP PoE switch is 192.168.0.239.

Posted by Uli Köhler in Networking

What is the default password for the NETGEAR MS108EUP?

The default password for the Netgear MS108EUP PoE switch is password.

Posted by Uli Köhler in Networking

How to connect the INHibit/Enable input of the 74HC4051 analog multiplexer?

The 74HC4051 and its variants such as the CD74HC4051 have an input called INH or ~E depending on the manufacturer.

Typically you should hard-connect this pin to GND in order to always enable the multiplexer.

Whenever you want to disable the multiplexer (i.e. none of the inputs is connected to the output, connect this pin to Vcc temporarily.

 

Source: This NXP datasheet, section 7.

Posted by Uli Köhler in Components, Electronics

Angular HttpClient: Observable with data continous data stream using interval requests to API endpoint

The following example requests /api/data every second, creating an Observable where you can subscribe to the continous value stream.

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { interval, mergeMap, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class DataService {

  constructor(private http: HttpClient) { }

  continousDataStream(_interval=1000): Observable<any> {
    return interval(_interval).pipe(mergeMap(_ => {
        return this.http.get<any>(`/api/data`);
    }));
  }
}

 

Posted by Uli Köhler in Angular

How to set Linux hardware clock to a specific date

hwclock --set --date '2021-01-04 13:04:00'

 

Posted by Uli Köhler in Linux

Systemd service to use a DS3231 RTC on the Raspberry Pi

The following systemd service will automatically. See this guide for more information on the setup and ensure sudo i2cdetect -y 1 detects the RTC with address 0x68.

This is an automatic service installation & enable script based on A simple systemd service autoinstall script . This script will automatically enable the service on boot:

#!/bin/bash
# This script installs and enables/starts a systemd service
# It also installs the service file
export NAME=ConfigureRTC

cat >/etc/systemd/system/${NAME}.service <<EOF
[Unit]
Description=${NAME}

[Service]
ExecStart=/bin/bash -c 'echo ds1307 0x68 > /sys/class/i2c-adapter/i2c-1/new_device && hwclock -s'
Restart=always

[Install]
WantedBy=multi-user.target
EOF

# Enable and start service
systemctl enable --now ${NAME}.service

This is just the systemd service:

[Unit]
Description=ConfigureRTC

[Service]
ExecStart=/bin/bash -c 'echo ds1307 0x68 > /sys/class/i2c-adapter/i2c-1/new_device && hwclock -s'
Restart=always

[Install]
WantedBy=multi-user.target

 

Posted by Uli Köhler in Linux, Raspberry Pi

How to enable or disable NTP time synchronization on the Raspberry Pi

Disable NTP:

sudo systemctl disable --now systemd-timesyncd

Enable NTP:

sudo systemctl enable --now systemd-timesyncd

Verifying if NTP is active

You can verify if NTP is active or not by running

timedatectl

Then look for these lines:

System clock synchronized: yes
NTP service: active

System clock synchronized will tell you if the NTP service has successfully synchronized the system time to a NTP time server: yes if synchronized, no if not synchronized.

NTP service will tell you if the NTP service is running, i.e. if it is trying to synchronize the system time to a NTP time server: active if running, inactive when not running

Output with NTP active:

               Local time: Tue 2023-03-14 16:49:28 CET
           Universal time: Tue 2023-03-14 15:49:28 UTC
                 RTC time: Tue 2023-03-14 15:49:28
                Time zone: Europe/Berlin (CET, +0100)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

Output with NTP inactive:

               Local time: Tue 2023-03-14 16:48:01 CET
           Universal time: Tue 2023-03-14 15:48:01 UTC
                 RTC time: Tue 2023-03-14 15:48:01
                Time zone: Europe/Berlin (CET, +0100)
System clock synchronized: no
              NTP service: inactive
          RTC in local TZ: no

 

Posted by Uli Köhler in Linux, Raspberry Pi

How to enable I2C port on the Raspberry Pi using raspi-config

sudo raspi-config nonint do_i2c 0

Now load the relevant modules:

sudo modprobe "i2c-bcm2835"
sudo modprobe "i2c-dev"
sudo modprobe "rtc-ds1307"

and now check if the I2C device file exists using stat /dev/i2c-1:

$ stat /dev/i2c-1
  File: /dev/i2c-1
  Size: 0               Blocks: 0          IO Block: 4096   character special file
Device: 5h/5d   Inode: 169         Links: 1     Device type: 59,1
Access: (0660/crw-rw----)  Uid: (    0/    root)   Gid: (  998/     i2c)
Access: 2023-03-14 16:23:06.643999999 +0100
Modify: 2023-03-14 16:23:06.643999999 +0100
Change: 2023-03-14 16:23:06.643999999 +0100
 Birth: -

If you instead see

ls: cannot access '/dev/i2c-1': No such file or directory

either the driver is not loaded properly or I2C is disabled. Try rebooting the system and repeating the commands above and possibly checking dmesg for any error messages.

Posted by Uli Köhler in Raspberry Pi

How to automatically add module to /etc/modules if it doesn’t exist

#!/bin/bash
check_module() {
  local module="$1"
  if grep -Fxq "$module" /etc/modules
  then
    echo "$module already exists in /etc/modules"
  else
    echo "$module not found in /etc/modules. Adding it now..."
    echo "$module" | sudo tee -a /etc/modules
  fi
}

check_module "i2c-dev"

This script will at the module if it doesn’t exist in /etc/modules yet.

Note that it will only detect if exactly the same line as the argument to check_module is already present in /etc/modules. For example if i2c-dev option1=value1 is in /etc/modules, the script will only detect this line correctly if you call it like

check_module "i2c-dev option1=value1"

but not if you call it like

check_module "i2c-dev"

 

 

 

Posted by Uli Köhler in Linux

How to fix Raspberry Pi i2cdetect: command not found

Problem:

When trying to detect I2C devices on the Raspberry Pi (Raspbian) using i2cdetect, you see the following error:

$ i2cdetect
bash: i2cdetect: command not found

Solution:

Install i2c-tools using

sudo apt -y install i2c-tools

After installing i2c-tools , you can use i2cdetect and other related tools such as i2cget.

Posted by Uli Köhler in Embedded, Linux, Raspberry Pi

How to fix NodeJS Error: EXDEV: cross-device link not permitted, rename …

Problem:

While trying to rename a file using fs.move() in NodeJS, you see an error message such as

Error: EXDEV: cross-device link not permitted, rename '/tmp/upload_8a8c71784abf9942b40c1359935b1997' -> 'myfile.pdf'

Solution:

This error occurs because you are moving a file from one drive (/tmp in our example above) to another drive (the current directory in our example above). On most platforms, this can’t be done using a simple rename operation.

Instead, fs.copyFile() the file.

For example, instead of

await fs.move('/tmp/upload_8a8c71784abf9942b40c1359935b1997', 'myfile.pdf')

first, copy the file:

await fs.copyFile('/tmp/upload_8a8c71784abf9942b40c1359935b1997', 'myfile.pdf')

and then – if desired – remove the source file to mirror the behaviour of os.move() as closely as possible.

await fs.remove('/tmp/upload_8a8c71784abf9942b40c1359935b1997', 'myfile.pdf')
await fs.unlink('/tmp/upload_8a8c71784abf9942b40c1359935b1997')

 

Posted by Uli Köhler in NodeJS

How to get filesize in NodeJS / TypeScript using Promisese

First, import stat from the NodeJS standard library:

import { stat } from "node:fs/promises";

Async-await style

// Async-await style:
const statResult = await stat("myfile.pdf");
const fileSizeInBytes = statResult.size;

Promise.then() style

stat("myfile.pdf").then(statResult => {
    const fileSizeInBytes = statResult.size;
    // TODO your code goes here
});

 

Posted by Uli Köhler in NodeJS, Typescript

How to format axis as dB (decibel) using matplotlib

In our previous post Matplotlib custom SI-prefix unit tick formatter we showed how to format a matplotlib Y axis with a custom unit with SI prefixes.

Similarly, we can use UliEngineering.Math.Decibel in order to format plots (most notably plots with a logarithmic Y axis) as decibels:

from UliEngineering.Math.Decibel import *
import matplotlib.ticker as mtick

def decibel_formatter(v0=1.0, unit='dB'):
    def format_value(value, pos=None):
        dB = value_to_dB(value, v0=v0)
        return f'{dB:.0f} {unit}'
    return format_value

# Usage example:
plt.gca().set_yscale("log") # Optional

plt.gca().yaxis.set_major_formatter(mtick.FuncFormatter(decibel_formatter()))

 

Posted by Uli Köhler in Python

How to get footprint graphical elements using KiCAD pcbnew plugin Python API

See our previous post How to get list of all footprints using KiCAD pcbnew plugin Python API and How to get all selected footprints using KiCAD pcbnew plugin Python API for more info on how to obtain a footprint object.

Once you have a pcbnew.FOOTPRINT object, you can get its graphical elements (which are basically the lines, rectangles, circles etc in the footprint – but not any silkscreen labels) using

footprint.GraphicalElements()

Example:

for graphical_element in footprint.GraphicalItems():   
    print(graphical_element)

Output:

<pcbnew.FP_TEXT; proxy of <Swig Object of type 'FP_TEXT *' at 0x7fc47f2c41e0> >
<pcbnew.FP_SHAPE; proxy of <Swig Object of type 'FP_SHAPE *' at 0x7fc47f0f10b0> >
<pcbnew.FP_SHAPE; proxy of <Swig Object of type 'FP_SHAPE *' at 0x7fc47f2c69d0> >
<pcbnew.FP_SHAPE; proxy of <Swig Object of type 'FP_SHAPE *' at 0x7fc47f2c6d30> >
<pcbnew.FP_SHAPE; proxy of <Swig Object of type 'FP_SHAPE *' at 0x7fc47f0f10e0> >
<pcbnew.FP_SHAPE; proxy of <Swig Object of type 'FP_SHAPE *' at 0x7fc47f2c6cd0> >
<pcbnew.FP_SHAPE; proxy of <Swig Object of type 'FP_SHAPE *' at 0x7fc47f2c6ca0> >
<pcbnew.FP_SHAPE; proxy of <Swig Object of type 'FP_SHAPE *' at 0x7fc47f0f38d0> >
<pcbnew.FP_SHAPE; proxy of <Swig Object of type 'FP_SHAPE *' at 0x7fc47f2c68b0> >
<pcbnew.FP_SHAPE; proxy of <Swig Object of type 'FP_SHAPE *' at 0x7fc47f2c41e0> >
<pcbnew.FP_SHAPE; proxy of <Swig Object of type 'FP_SHAPE *' at 0x7fc47f0f10b0> >
<pcbnew.FP_SHAPE; proxy of <Swig Object of type 'FP_SHAPE *' at 0x7fc47f2c69d0> >
<pcbnew.FP_SHAPE; proxy of <Swig Object of type 'FP_SHAPE *' at 0x7fc47f2c6d30> >

You can filter them using

fp_texts: list[pcbnew.FP_TEXT] = [
    item for footprint in selected_footprint.GraphicalItems()
    if type(item).__name__ == 'FP_TEXT'
]
fp_shapes: list[pcbnew.FP_SHAPE] = [
    item for footprint in selected_footprint.GraphicalItems()
    if type(item).__name__ == 'FP_SHAPE'
]

 

Posted by Uli Köhler in Electronics, KiCAD

How to get all selected footprints using KiCAD pcbnew plugin Python API

In our previous post, we showed how to get all selected objects using the KiCAD python API using

pcbnew.GetCurrentSelection()

You can simply filter these entries to obtain just a list of selected footprints using either a for loop with inline filtering:

for selected_object in pcbnew.GetCurrentSelection():
    if type(selected_object).__name__ == 'FOOTPRINT':
        print(selected_object.GetReference())

or using a list comprehension:

selected_footprints: list[pcbnew.FOOTPRINT] = [
    footprint for footprint in pcbnew.GetCurrentSelection() if type(footprint).__name__ == 'FOOTPRINT'
]

Complete plugin example:

#!/usr/bin/env python
import pcbnew
import os

class SimplePlugin(pcbnew.ActionPlugin):
    def defaults(self):
        self.name = "Plugin Name as shown in Pcbnew: Tools->External Plugins"
        self.category = "A descriptive category name"
        self.description = "A description of the plugin and what it does"
        self.show_toolbar_button = False # Optional, defaults to False
        self.icon_file_name = os.path.join(os.path.dirname(__file__), 'simple_plugin.png') # Optional, defaults to ""

    def Run(self):
        board: pcbnew.BOARD = pcbnew.GetBoard()
        footprints: list[pcbnew.FOOTPRINT] = board.GetFootprints()
        
        # TODO Do something useful with [board]
        for selected_object in pcbnew.GetCurrentSelection():
            print(selected_object)

SimplePlugin().register() # Instantiate and register to Pcbnew

Example output (excerpt):

D39
D32
D23
D37
D18
D34
D11
D15

 

Posted by Uli Köhler in Electronics, KiCAD
This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Cookie settingsACCEPTPrivacy &amp; Cookies Policy