How to set zsh as standard shell in Ubuntu

Install zsh

sudo apt install zsh

Add zsh to list of valid shells

sudo vim /etc/shells

Add to the list

/usr/bin/zsh

Set zsh as default shell

chsh -s /usr/bin/zsh

Double check

$ echo $SHELL

Now you might want to check out https://ohmyz.sh/#install to install a nice theme.

Posted by Joshua Simon in Linux

How to fix KiBot ‘KiBoM not installed or too old’

Problem:

When running kibot-check, you see the following warning message:

* KiBoM not installed or too old
  Visit: https://github.com/INTI-CMNB/KiBoM
  Download it from: https://github.com/INTI-CMNB/KiBoM/releases
  - Mandatory for `kibom`

but even installing kibom using pip install kibom, the warning does not disappear.

Solution:

You need to install a specific fork of KiBom:

pip install git+https://github.com/INTI-CMNB/KiBoM

After that, the warning will disappear.

Posted by Uli Köhler in Electronics, KiCAD, Python

How to compute & plot sun path diagram using skyfield in Python

In this example, we’ll show how to generate a sun path diagram for any given day for a preselected location. By using the skyfield library, we can obtain extremely accurate predictions of the sun’s position in the sky, and in contrast to many other libraries, we can use the same method to predict the position of other celestial bodies.

#!/usr/bin/env python3
from skyfield import api
from skyfield import almanac
from datetime import datetime
from datetime import timedelta
from matplotlib import pyplot as plt
import pytz
import dateutil.parser
from collections import namedtuple
from UliEngineering.Utils.Date import *
import matplotlib.ticker as mtick

# Matplotlib formatter
def format_degrees(value, pos=None):
    return f'{value:.0f} °'

ts = api.load.timescale()
ephem = api.load_file('de421.bsp')

sun = ephem["Sun"]
earth = ephem["Earth"]

# Data types
AltAz = namedtuple("AltAz", ["altitude", "azimuth", "distance"])
TimeAltAz = namedtuple("TimeAltAz", ["time", "altitude", "azimuth", "distance"])

def sun_altaz(planet_at_location, ts):
    # Compute the sun position as seen from the observer at <location>
    sun_pos = planet_at_location.at(ts).observe(sun).apparent()
    # Compute apparent altitude & azimuth for the sun's position
    altitude, azimuth, distance = sun_pos.altaz()
    return AltAz(altitude, azimuth, distance)

def sun_altaz_for_day(location, year, month, day, tz):
    earth_at_location = (earth + location)
    
    minutes = list(yield_minutes_on_day(year=year, month=month, day=day, tz=tz))
    skyfield_minutes = ts.from_datetimes(minutes)

    minutely_altaz = [sun_altaz(earth_at_location, ts) for ts in skyfield_minutes]
    # Extract components for plotting
    minutely_alt = [altaz.altitude.degrees for altaz in minutely_altaz]
    minutely_az = [altaz.azimuth.degrees for altaz in minutely_altaz]
    minutely_distance = [altaz.distance for altaz in minutely_altaz]

    return TimeAltAz(minutes, minutely_alt, minutely_az, minutely_distance)

# Random location near Munich
location = api.Topos('48.324777 N', '11.405610 E', elevation_m=519)
# Compute Alt/Az for two different days
jun = sun_altaz_for_day(location, 2022, 6, 15, pytz.timezone("Europe/Berlin"))
dec = sun_altaz_for_day(location, 2022, 12, 15, pytz.timezone("Europe/Berlin"))

# Plot!
plt.gca().yaxis.set_major_formatter(mtick.FuncFormatter(format_degrees))
plt.gca().xaxis.set_major_formatter(mtick.FuncFormatter(format_degrees))

plt.plot(jun.azimuth, jun.altitude, label="15th of June")
plt.plot(dec.azimuth, dec.altitude, label="15th of December")
plt.ylim([0, None]) # Do not show sun angles below the horizon
plt.ylabel("Sun altitude")
plt.xlabel("Sun azimuth")
plt.title("Apparent sun position at our office")
plt.grid() 
plt.gca().legend()
plt.gcf().set_size_inches(10,5)

plt.savefig("Sun path.svg")

 

Posted by Uli Köhler in Physics, Python, skyfield

How to generate datetime for every hour on a given day in Python

This example code generates a timezone-aware datetime for every hour on a given day (minutes & seconds are always set to 0) in a given timezone.

First, install the UliEngineering library and pytz for timezones:

pip install --user UliEngineering pytz

Now you can use UliEngineering.Utils.Date.yield_hours_on_day():

from UliEngineering.Utils.Date import *

for hour in yield_hours_on_day(year=2022, month=6, day=15, tz=pytz.timezone("Europe/Berlin"):
    pass # TODO: Your code goes here

Or, if you want to have a list of datetime instances instead of a generator:

from UliEngineering.Utils.Date import *

hours = list(yield_hours_on_day(year=2022, month=6, day=15, tz=pytz.timezone("Europe/Berlin")))
Posted by Uli Köhler in Python

How to generate datetime for every second on a given day in Python

This example code generates a timezone-aware datetime for every second on a given day in a given timezone.

First, install the UliEngineering library and pytz for timezones:

pip install --user UliEngineering pytz

Now you can use UliEngineering.Utils.Date.yield_seconds_on_day():

from UliEngineering.Utils.Date import *

for second in yield_seconds_on_day(year=2022, month=6, day=15, tz=pytz.timezone("Europe/Berlin"):
    pass # TODO: Your code goes here

Or, if you want to have a list of datetime instances instead of a generator:

from UliEngineering.Utils.Date import *

seconds = list(yield_seconds_on_day(year=2022, month=6, day=15, tz=pytz.timezone("Europe/Berlin")))
Posted by Uli Köhler in Python

How to generate datetime for every minute on a given day in Python

This example code generates a timezone-aware datetime for every minute (the seconds are always set to 0) for a given day in a given timezone.

First, install the UliEngineering library and pytz for timezones:

pip install --user UliEngineering pytz

Now you can use UliEngineering.Utils.Date.yield_minutes_on_day():

from UliEngineering.Utils.Date import *

for minute in yield_minutes_on_day(year=2022, month=6, day=15, tz=pytz.timezone("Europe/Berlin"):
    pass # TODO: Your code goes here

Or, if you want to have a list of datetime instances instead of a generator:

from UliEngineering.Utils.Date import *

minutes = list(yield_minutes_on_day(year=2022, month=6, day=15, tz=pytz.timezone("Europe/Berlin")))
Posted by Uli Köhler in Python

Matplotlib: How to format angle in degrees (°)

Based on our previous post on Matplotlib custom SI-prefix unit tick formatters, this is a simple snippet which you can use to format the Y axis of your matplotlib plots. In our example, the function shows 2 digits after the decimal points (.2f) but you can change that to how ever many you prefer.

import matplotlib.ticker as mtick
from matplotlib import pyplot as plt

def format_degrees(value, pos=None):
    return f'{value:.2f} °'

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

Example diagram

From our post How to compute & plot sun path diagram using skyfield in Python

Posted by Uli Köhler in Python

How to convert skyfield Time into datetime at specific timezone

When you have a skyfield Time object like

t = ts.now()
# Example: <Time tt=2459750.027604357>

you can convert it to a Python datetime in a specific timezone (Europe/Berlin in this example) using .astimezone() and the pytz library:

t.astimezone(pytz.timezone("Europe/Berlin"))
# Example: datetime.datetime(2022, 6, 19, 14, 38, 35, 832445, tzinfo=<DstTzInfo 'Europe/Berlin' CEST+2:00:00 DST>)

Complete example

from skyfield import api
from datetime import datetime
import pytz

ts = api.load.timescale()
t = ts.now()
dt = t.astimezone(pytz.timezone("Europe/Berlin"))
print(dt) # e.g. 2022-06-19 14:42:47.406786+02:00

 

Posted by Uli Köhler in Python, skyfield

How to compute position of sun in the sky in Python using skyfield

The following code computes the position of the sun (in azimuth/altitude coordinates) in the sky using the skyfield library. Note that you need to download de421.bsp .

from skyfield import api
from skyfield import almanac
from datetime import datetime
from datetime import timedelta
import dateutil.parser
from calendar import monthrange

ts = api.load.timescale()
ephem = api.load_file('de421.bsp')

sun = ephem["Sun"]
earth = ephem["Earth"]

# Compute sunrise & sunset for random location near Munich
location = api.Topos('48.324777 N', '11.405610 E', elevation_m=519)
# Compute the sun position as seen from the observer at <location>
sun_pos = (earth + location).at(ts.now()).observe(sun).apparent()
# Compute apparent altitude & azimuth for the sun's position
altitude, azimuth, distance = sun_pos.altaz()

# Print results (example)
print(f"Altitude: {altitude.degrees:.4f} °")
print(f"Azimuth: {azimuth.degrees:.4f} °")

Example output

Altitude: -3.3121 °
Azimuth: 48.4141 °

Factors influencing the accuracy of the calculation

This way of calculating the position takes into account:

  • The slight shift in position caused by light speed
  • The very very slight shift in position caused by earth’s gravity

But it does not take into account:

  • Atmospheric distortions shifting the sun’s position
  • The extent of the sun’s disk causing the sun to emanate not from a point but apparently from an area
Posted by Uli Köhler in Physics, Python, skyfield

How to fix Python skyfield FileNotFoundError: [Errno 2] No such file or directory: ‘de421.bsp’

Problem:

When trying to use the Python skyfield library, you see an exception like

Input In [2], in <cell line: 11>()
      8 from calendar import monthrange
     10 ts = api.load.timescale()
---> 11 ephem = api.load_file('de413.bsp')

File /usr/local/lib/python3.10/dist-packages/skyfield/iokit.py:412, in load_file(path)
    410 base, ext = os.path.splitext(path)
    411 if ext == '.bsp':
--> 412     return SpiceKernel(path)
    413 raise ValueError('unrecognized file extension: {}'.format(path))

File /usr/local/lib/python3.10/dist-packages/skyfield/jpllib.py:71, in SpiceKernel.__init__(self, path)
     69 self.path = path
     70 self.filename = os.path.basename(path)
---> 71 self.spk = SPK.open(path)
     72 self.segments = [SPICESegment(self, s) for s in self.spk.segments]
     73 self.codes = set(s.center for s in self.segments).union(
     74                  s.target for s in self.segments)

File /usr/local/lib/python3.10/dist-packages/jplephem/spk.py:49, in SPK.open(cls, path)
     46 @classmethod
     47 def open(cls, path):
     48     """Open the file at `path` and return an SPK instance."""
---> 49     return cls(DAF(open(path, 'rb')))

FileNotFoundError: [Errno 2] No such file or directory: 'de421.bsp'

Solution:

Take a look at the api.load(...) line in your code:

ephem = api.load_file('de421.bsp')

It tries to load the data from the file de421.bsp in the current directory. This file contains positional data of objects in the sky and you need to manually download that file.

You can download the file from NASA. Just take care to either place it into the right directory or modifying the path in the api.load() call to point to the file.

URL for downloading the file:

https://ssd.jpl.nasa.gov/ftp/eph/planets/bsp/de421.bsp

My preferred way to download it is using wget:

wget https://ssd.jpl.nasa.gov/ftp/eph/planets/bsp/de421.bsp

This command will place the file into the current directory.

Posted by Uli Köhler in Python, skyfield

How to compute sunrise & sunset in Python using skyfield

The following code will compute the sunrise & sunset at a specific location & elevation using the skyfield library. Note that you need to download de413.bsp .

from skyfield import api
from skyfield import almanac
from datetime import datetime
from datetime import timedelta
import dateutil.parser
from calendar import monthrange

ts = api.load.timescale()
ephem = api.load_file('de413.bsp')

def compute_sunrise_sunset(location, year=2019, month=1, day=1):
    t0 = ts.utc(year, month, day, 0)
    # t1 = t0 plus one day
    t1 = ts.utc(t0.utc_datetime() + timedelta(days=1))
    t, y = almanac.find_discrete(t0, t1, almanac.sunrise_sunset(ephem, location))
    sunrise = None
    for time, is_sunrise in zip(t, y):
        if is_sunrise:
            sunrise = dateutil.parser.parse(time.utc_iso())
        else:
            sunset = dateutil.parser.parse(time.utc_iso())
    return sunrise, sunset

# Compute sunrise & sunset for random location near Munich
location = api.Topos('48.324777 N', '11.405610 E', elevation_m=519)
now = datetime.now()
sunrise, sunset = compute_sunrise_sunset(location, now.year, now.month, now.day)

# Print result (example)
print(f'Sunrise today: {sunrise}')
print(f'Sunset today: {sunset}')

Definition of sunrise & sunset in this context

According to the skyfield documentation:

Skyfield uses the same definition as the United States Naval Observatory: the Sun is up when its center is 0.8333 degrees below the horizon, which accounts for both its apparent radius of around 16 arcminutes and also for the 34 arcminutes by which atmospheric refraction on average lifts the image of the Sun.

Other caveats

  • Note that obstructions like mountains are not taken into account for this model
  • Note that the resulting timestamps are UTC, if you want local time, you’ll have to convert them appropriately

Example output:

Sunrise today: 2022-06-19 03:12:56+00:00
Sunset today: 2022-06-19 19:18:38+00:00

 

Posted by Uli Köhler in Physics, Python, skyfield

How to fix Python skyfield FileNotFoundError: [Errno 2] No such file or directory: ‘de413.bsp’

Problem:

When trying to use the Python skyfield library, you see an exception like

Input In [2], in <cell line: 11>()
      8 from calendar import monthrange
     10 ts = api.load.timescale()
---> 11 ephem = api.load_file('de413.bsp')

File /usr/local/lib/python3.10/dist-packages/skyfield/iokit.py:412, in load_file(path)
    410 base, ext = os.path.splitext(path)
    411 if ext == '.bsp':
--> 412     return SpiceKernel(path)
    413 raise ValueError('unrecognized file extension: {}'.format(path))

File /usr/local/lib/python3.10/dist-packages/skyfield/jpllib.py:71, in SpiceKernel.__init__(self, path)
     69 self.path = path
     70 self.filename = os.path.basename(path)
---> 71 self.spk = SPK.open(path)
     72 self.segments = [SPICESegment(self, s) for s in self.spk.segments]
     73 self.codes = set(s.center for s in self.segments).union(
     74                  s.target for s in self.segments)

File /usr/local/lib/python3.10/dist-packages/jplephem/spk.py:49, in SPK.open(cls, path)
     46 @classmethod
     47 def open(cls, path):
     48     """Open the file at `path` and return an SPK instance."""
---> 49     return cls(DAF(open(path, 'rb')))

FileNotFoundError: [Errno 2] No such file or directory: 'de413.bsp'

Solution:

Take a look at the api.load(...) line in your code:

ephem = api.load_file('de413.bsp')

It tries to load the data from the file de413.bsp in the current directory. This file contains positional data of objects in the sky and you need to manually download that file.

You can download the file from NASA. Just take care to either place it into the right directory or modifying the path in the api.load() call to point to the file.

URL for downloading the file:

https://ssd.jpl.nasa.gov/ftp/eph/planets/bsp/de413.bsp

My preferred way to download it is using wget:

wget https://ssd.jpl.nasa.gov/ftp/eph/planets/bsp/de413.bsp

This command will place the file into the current directory.

Posted by Uli Köhler in Python, skyfield

How to fix Nextcloud updater PHP Fatal error:  Allowed memory size of … bytes exhausted

Problem:

While trying to update Nextcloud using the command line (e.g. SSH) using a command like

php updater/updater.phar

you see an error message containing PHP Fatal error:  Allowed memory size of ... bytes exhausted such as this one:

[✔] Check for expected files
[✔] Check for write permissions
[✔] Create backup
[✔] Downloading
[ ] Verify integrity ...PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 155061456 bytes) in phar:///owncloud.mydomain.com/updater/updater.phar/lib/Updater.php on line 637

Solution:

First, try to adjust the memory limit in your webhosting panel or php.ini. If this is not possible – such as for my hoster, which has different settings for the FastCGI PHP as opposed to the command line (CLI) PHP, you can manually set the memory limit using

php -d memory_limit=512M updater/updater.phar

 

Posted by Uli Köhler in Networking, Nextcloud, PHP

How to fix Nextcloud Step … is currently in process. Please call this command later.

Problem:

While trying to update Nextcloud using the command line (e.g. SSH) using a command like

php updater/updater.phar

you see the following error message:

Nextcloud Updater - version: v20.0.0beta4-11-g68fa0d4

Step 5 is currently in process. Please call this command later.

Solution:

No matter if the step that appears to be currently in progress is Step 3Step 5 or any other step, the solution is always the same: Reset the update by deleting the data/updater-occ[random-string] folder.

Recommended: If you are paranoid about losing data, just rename the directory:

mv data/updater-occ* ../DELETEME-updater

Not recommended: You can also just delete the directory

rm -rf data/updater-occ*

 

Posted by Uli Köhler in Networking, Nextcloud

Traefik container labels for the Unifi controller via docker-compose

For the basic configuration & setup of the Unifi controller via docker-compose, see Simple Unifi controller setup using docker-compose ! This post just covers the Traefik label part.

This setup is based on our previous post on the Unifi docker-compose setup. Furthermore, our traefik configuration is discussed in more detail in our post on Simple Traefik docker-compose setup with Lets Encrypt Cloudflare DNS-01 & TLS-ALPN-01 & HTTP-01 challenges.

For this example, we’ll use a wildcart Let’s Encrypt certificate for the domain *.mydomain.com via the Traefik certificate provider named cloudflare, with the Unifi controller running on unifi.mydomain.com

Here’s the container label config:

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.unifi.rule=Host(`unifi.mydomain.com`)"
  - "traefik.http.routers.unifi.entrypoints=websecure"
  - "traefik.http.routers.unifi.tls.certresolver=cloudflare"
  - "traefik.http.routers.unifi.tls.domains[0].main=mydomain.com"
  - "traefik.http.routers.unifi.tls.domains[0].sans=*.mydomain.com"
  - "traefik.http.services.unifi.loadbalancer.server.port=8443"
  - "traefik.http.services.unifi.loadbalancer.server.scheme=https"

Note particularly these lines which make Traefik access the Unifi controller via HTTPS:

- "traefik.http.services.unifi.loadbalancer.server.port=8443"
- "traefik.http.services.unifi.loadbalancer.server.scheme=https"

Complete example

version: '2.3'
services:
  mongo_unifi:
    image: mongo:3.6
    network_mode: host
    restart: always
    volumes:
      - ./mongo_db:/data/db
      - ./mongo/dbcfg:/data/configdb
    command: mongod --port 29718
  controller:
    image: "jacobalberty/unifi:latest"
    depends_on:
      - mongo_unifi
    init: true
    network_mode: host
    restart: always
    volumes:
      - ./unifi_dir:/unifi
      - ./unifi_data:/unifi/data
      - ./unifi_log:/unifi/log
      - ./unifi_cert:/unifi/cert
      - ./unifi_init:/unifi/init.d
      - ./unifi_run:/var/run/unifi
      - ./unifi_backup:/unifi/data/backup
#    sysctls:
#      net.ipv4.ip_unprivileged_port_start: 0
    environment:
      - DB_URI=mongodb://localhost:29718/unifi
      - STATDB_URI=mongodb://localhost:29718/unifi_stat
      - DB_NAME=unifi
      - UNIFI_HTTP_PORT=8090
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.unifi.rule=Host(`unifi.mydomain.com`)"
      - "traefik.http.routers.unifi.entrypoints=websecure"
      - "traefik.http.routers.unifi.tls.certresolver=cloudflare"
      - "traefik.http.routers.unifi.tls.domains[0].main=mydomain.com"
      - "traefik.http.routers.unifi.tls.domains[0].sans=*.mydomain.com"
      - "traefik.http.services.unifi.loadbalancer.server.port=8443"
      - "traefik.http.services.unifi.loadbalancer.server.scheme=https"
# Ports commentet out since network mode is set to "host"
#    ports:
#      - "3478:3478/udp" # STUN
#      - "6789:6789/tcp" # Speed test
#      - "8080:8080/tcp" # Device/ controller comm.
#      - "8443:8443/tcp" # Controller GUI/API as seen in a web browser
#      - "8880:8880/tcp" # HTTP portal redirection
#      - "8843:8843/tcp" # HTTPS portal redirection
#      - "10001:10001/udp" # AP discovery
  logs:
    image: bash
    depends_on:
      - controller
    command: bash -c 'tail -F /unifi/log/*.log'
    restart: always
    volumes:
      - ./unifi_log:/unifi/log
Posted by Uli Köhler in Networking, Traefik

How to check MikroTik RouterOS license level

How to find RouterOS license using the web interface

In the WebFig web UI, you can check the license level by clicking on System -> License

How to find RouterOS license level using the Terminal

Run the following command:

/system license print

Look for the nlevel line. In the following example, the MikroTik RouterOS license level is Level 5:

[[email protected]] > /system license print
  software-id: 5ABC-DEF0
       nlevel: 5
     features: 
Posted by Uli Köhler in MikroTik, Networking

How to find out architecture of your Mikrotik RouterOS router

Find out the CPU architecture using the webinterface (WebFig)

In the WebFig Web UI you can go to System -> Resources where you can see the architecture listed as CPU:

Find out the CPU architecture using the terminal

On the terminal, run

/system resource print

and look for the cpu line. In the following example, the architecutre is ARM:

[[email protected]] > /system resource print
                   uptime: 10m24s
                  version: 7.3.1 (stable)
               build-time: Jun/09/2022 08:58:15
         factory-software: 6.44.6
              free-memory: 446.0MiB
             total-memory: 512.0MiB
                      cpu: ARM
                cpu-count: 2
                 cpu-load: 0%
           free-hdd-space: 1148.0KiB
          total-hdd-space: 16.0MiB
  write-sect-since-reboot: 595
         write-sect-total: 139871
               bad-blocks: 0%
        architecture-name: arm
               board-name: CRS326-24G-2S+
                 platform: MikroTik

 

Posted by Uli Köhler in MikroTik, Networking

How to see PlatformIO actual commands during build or upload

Having a look at the actual commands being used by PlatformIO is pretty easy:

Instead of clicking Build or Upload, open the Advanced folder and select Verbose Build or Verbose Upload.

This will show you all raw commands such as esptool.py commands that are being run.

Posted by Uli Köhler in PlatformIO

How to fix /dev/ttyUSB0 or /dev/ttyACM0 permission error

Problem:

When trying to connect to a USB device, you see an error like

[Errno 13] Permission denied: '/dev/ttyUSB0'

and the USB connection can’t be established.

Solution:

On Debian & Ubuntu, the /dev/ttyUSBx and /dev/ttyACMx devices are owned by the dialout group. Therefore, the solution is to add your user to the dialout group:

sudo usermod -a -G dialout $USER

After that, you need to logout completely and login again (if in doubt, reboot your computer)!

While you can sometimes quickly solve this issue by running whatever command you want to use as root using sudo, this is not a recommended solution since it will allow the program you’re calling full access to the entire computer – not just access restricted to the USB device. Therefore, this approach poses a significant security risk and additionally, some programs don’t work properly when called using sudo.

Posted by Uli Köhler in Embedded, Linux
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