What does the M105 / M155 line format (T:20.38 /185.00 @:127) mean?

The M105 G-Code for printer firmwares like Marlin prints lines such as

T:20.38 /185.00 @:127

The RepRap wiki has extensive documentation about those lines. Here are the most important ones

  • T:actual /setpoint Temperature of the hotend in °C
  • T0:actual /setpoint Temperature of the first hotend in °C (only if you have multiple hotends)
  • T1:actual /setpoint Temperature of the second hotend in °C (only if you have multiple hotends)
  • B:actual /setpoint Temperature of the heated bed in °C
  • C:actual /setpoint Temperature of the printer case in °C
  • @:value : Current PWM output value of the hotend. Typically, 8-bit PWM is used, so 255 is full-scale.
  • B@:value : Current PWM output value of the bed. Typically, 8-bit PWM is used, so 255 is full-scale.
Posted by Uli Köhler in 3D printing

How to fix poetry publish HTTP Error 403: Invalid or non-existent authentication information

Problem:

You intend to publish your package on PyPI using poetry using a command such as

poetry publish

However, you see the following error message:

Publishing mypackage (0.1.0) to PyPI
 - Uploading mypackage-0.1.0-py3-none-any.whl FAILED

HTTP Error 403: Invalid or non-existent authentication information. See https://pypi.org/help/#invalid-auth for more information. | b'<html>\n <head>\n  <title>403 Invalid or non-existent authentication information. See https://pypi.org/help/#invalid-auth for more information.\n \n <body>\n  <h1>403 Invalid or non-existent authentication information. See https://pypi.org/help/#invalid-auth for more information.\n  Access was denied to this resource.<br/><br/>\nInvalid or non-existent authentication information. See https://pypi.org/help/#invalid-auth for more information.\n\n\n \n'

Solution:

First, you need to create a PyPI API token if you don’t have one already. The username/password authentication is deprecated.

Now run the following command to configure poetry:

poetry config http-basic.pypi __token__ APITOKEN

where APITOKEN is the API token you want to use on this computer. Leave __token__ as-is since it’s a special username that tells PyPI to use an API token instead of the old & deprecated username/password authentication

It should look like this:

poetry config http-basic.pypi __token__ pypi-AgEIc[...]

Now retry publishing:

poetry publish

Example output:

Publishing mypackage (0.1.0) to PyPI
 - Uploading mypackage-0.1.0-py3-none-any.whl 100%
 - Uploading mypackage-0.1.0.tar.gz 100%

 

Posted by Uli Köhler in Python

Recommended python project initialization & build tool

I recommend Poetry for initializing, managing & publishing python projects.

Posted by Uli Köhler in Python

Robust Linux Python serial port filtering by name and manufacturer using udev

Important note: This code is deprecated as it has been superseded by the UliSerial library on GitHub. UIiSerial implements the serial port filtering in a platform-independent manner using pyserial built-in features.

 

Typically, when one intends to select a serial port in Python, you use a sequence such as

glob.glob('/dev/ttyACM*')[0]

or just go to straight hard-coding the port such as /dev/ttyACM0.

It should be quite obvious why this isn’t robust:

  • There might be multiple serial ports and the order is unknown
  • You code might need to work on different computers, which lead to different ports being assigned
  • When you re-plug a device, it might get assigned a differnent serial port number (e.g. /dev/ttyACM1)

The following code uses only the linux integrated tool udevadm to query information and built-in libraries. Currently it does not work on anything but Linux.

import glob
import subprocess
import re

def get_serial_ports():
    # Get a list of /dev/ttyACM* and /dev/ttyUSB* devices
    ports = glob.glob('/dev/ttyACM*') + glob.glob('/dev/ttyUSB*')
    return ports

def get_udev_info(port):
    # Run the udevadm command and get the output
    result = subprocess.run(['udevadm', 'info', '-q', 'all', '-r', '-n', port], capture_output=True, text=True)
    return result.stdout

def find_serial_ports(vendor=None, model=None, usb_vendor=None, usb_model=None, usb_model_id=None, vendor_id=None):
    # Mapping of human-readable names to udevadm keys
    attribute_mapping = {
        "vendor": "ID_VENDOR_FROM_DATABASE",
        "model": "ID_MODEL_FROM_DATABASE",
        "usb_vendor": "ID_USB_VENDOR",
        "usb_model": "ID_USB_MODEL_ENC",
        "usb_model_id": "ID_USB_MODEL_ID",
        "vendor_id": "ID_VENDOR_ID",
        "usb_path": "ID_PATH",
        "serial": "ID_SERIAL_SHORT"
    }

    # Filters based on provided arguments
    filters = {}
    for key, value in locals().items():
        if value and key in attribute_mapping:
            filters[attribute_mapping[key]] = value

    # Find and filter ports
    ports = get_serial_ports()
    filtered_ports = []

    for port in ports:
        udev_info = get_udev_info(port)
        match = True

        for key, value in filters.items():
            if not re.search(f"{key}={re.escape(value)}", udev_info):
                match = False
                break

        if match:
            filtered_ports.append(port)

    return filtered_ports

def get_serial_port_info(port):
    # Mapping of udevadm keys to human-readable names
    attribute_mapping = {
        "ID_VENDOR_FROM_DATABASE": "vendor",
        "ID_MODEL_FROM_DATABASE": "model",
        "ID_USB_VENDOR": "usb_vendor",
        "ID_USB_MODEL_ENC": "usb_model",
        "ID_USB_MODEL_ID": "usb_model_id",
        "ID_VENDOR_ID": "vendor_id",
        "ID_SERIAL_SHORT": "serial",
    }

    # Run the udevadm command and get the output
    udev_info = get_udev_info(port)
    port_info = {}

    for line in udev_info.splitlines():
        if line.startswith('E: '):
            key, value = line[3:].split('=', 1)
            if key in attribute_mapping:
                # Decode escape sequences like \x20 to a space.
                # NOTE: Since only \x20 is common, we currently only replace that one
                port_info[attribute_mapping[key]] = value.replace('\\x20', ' ')

    return port_info

def find_serial_port(**kwargs):
    """
    Find a single serial port matching the provided filters.
    """
    ports = find_serial_ports(**kwargs)
    if len(ports) > 1:
        raise ValueError("Multiple matching ports found for filters: " + str(kwargs))
    elif len(ports) == 0:
        raise ValueError("No matching ports found for filters: " + str(kwargs))
    else:
        return ports[0]

 

Example: Find a serial port

# Example usage: Find a serial port
matching_ports = find_serial_ports(vendor="OpenMoko, Inc.")
print(matching_ports) # Prints e.g. ['/dev/ttyACM0']

Example: Print information about a given port

This is typically used so you know what filters to add for find_serial_ports().

# Example usage: Print info about a serial port
serial_port_info = get_serial_port_info('/dev/ttyACM0')
print(serial_port_info)

This prints, for example:

{
    'serial': '01010A23535223934CF29A1EF5000007',
    'vendor_id': '1d50',
    'usb_model': 'Marlin USB Device',
    'usb_model_id': '6029',
    'usb_vendor':
    'marlinfw.org',
    'vendor': 'OpenMoko, Inc.',
    'model': 'Marlin 2.0 (Serial)'
}

 

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

How I fixed Angular “ng build”, “ng new” etc not terminating

Problem

On my Desktop, Angular commands such as

ng build

or

ng new

worked successfully, but they didnt stop / terminate after running the command. Instead, they just waited indefinitely without any output.

Solution:

This is related to analytics being enabled, see this GitHub post.

In order to fix it, run

ng analytics disable --global

It will print

Global setting: disabled
Local setting: enabled
Effective status: disabled

and it won’t terminate the first time you do this. Just press Ctrl+C to force stop it.

After that, any command will exit after finishing its task

Underlying problem

I suspect this has something to do with IPv6 configuration since my Desktop’s IPv6 network setup is somewhat broken at the moment. However, I did not do any further research into this.

Posted by Uli Köhler in Angular

How to install NodeJS 20.x LTS on Ubuntu in 1 minute

Run these shell commands on your Ubuntu computer to install NodeJS 20.x:

sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg

NODE_MAJOR=20
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list

sudo apt-get update
sudo apt-get install nodejs -y

 

Instead of 20 you can also choose other versions like 18. However, using this method, you can’t install multiple versions of NodeJS in parallel.

Source: Official nodesource documentation

Posted by Uli Köhler in Linux, NodeJS

ESP32 HTTP POST with JSON body using ArduinoJson

This example parses a HTTP POST request body using ArduinoJson. I recommend using my HumanESPHTTP library as simple server library based on the ESP-IDF HTTP server.

constexpr size_t JsonParseBufferSize = 2048;
typedef ArduinoJson::StaticJsonDocument JsonParseBuffer;

/**
 * This buffer is used to parse /api/configure events
*/
static JsonParseBuffer document;

static const httpd_uri_t configureHandler = {
    .uri       = "/api/configure",
    .method    = HTTP_POST,
    .handler   = [](httpd_req_t *req) {
        // Receive POST body data into a new buffer
        char* jsonBody = (char*)malloc(req->content_len + 1);
        jsonBody[req->content_len] = '\0'; // NUL terminate [just in case]
        if(jsonBody == nullptr) {
            return SendStatusError(req, "Failed to allocate memory for JSON body");
        }
        int err = httpd_req_recv(req, jsonBody, req->content_len);
        if(err != req->content_len) {
            free(jsonBody);
            return SendStatusError(req, "Failed to read request body");
        }
        // Parse the body as JSON
        deserializeJson(document, jsonBody, req->content_len);
        // TODO: Do something with [document]
        // Cleanup
        free(jsonBody);
        return SendStatusOK(req);
    }
};

which uses the following utility functions:

esp_err_t SendStatusError(httpd_req_t *request, const char* description) {
    httpd_resp_set_type(request, "application/json");
    httpd_resp_send_chunk(request, "{\"status\":\"error\", \"error\": \"", HTTPD_RESP_USE_STRLEN);
    // NOTE: We silently assume that description does not have any special characters
    httpd_resp_send_chunk(request, description, HTTPD_RESP_USE_STRLEN);
    httpd_resp_send_chunk(request, "\"}", HTTPD_RESP_USE_STRLEN);
    httpd_resp_send_chunk(request, nullptr, 0); // Finished
    return ESP_OK;
}

esp_err_t SendStatusOK(httpd_req_t *request) {
    httpd_resp_set_type(request, "application/json");
    httpd_resp_sendstr(request, "{\"status\":\"ok\"}");
    return ESP_OK;
}

 

Posted by Uli Köhler in C/C++, ESP8266/ESP32

How to fix Angular standalone component: Can’t bind to ‘routerLink’ since it isn’t a known property of ‘a’

Problem:

When opening your Angular app with a standalone component, you see the following error message:

Can't bind to 'routerLink' since it isn't a known property of 'a'

Solution:

In the component where the error occurs, add

import { RouterModule } from '@angular/router';

and add

RouterModule,

to the imports of the component. Example:

@Component({
  selector: 'app-top-bar',
  standalone: true,
  imports: [
    CommonModule,
    RouterModule
  ],
  templateUrl: './top-bar.component.html',
  styleUrl: './top-bar.component.sass'
})
export class TopBarComponent {

}

 

Posted by Uli Köhler in Angular, Typescript

How to fix ESP32 rmt: rmt_transmit(466): loop count is not supported

If you encounter

rmt: rmt_transmit(466): loop count is not supported

on the ESP32, this is because you have used a rmt_transmit_config_t with explicitly set loop_count.

rmt_transmit_config_t cfg = {
  .loop_count = 1,
  .flags = {
      .eot_level = 0,
   }
};
ESP_ERROR_CHECK(rmt_transmit(/* ... */, &cfg));

but your IC (e.g. ESP32-D0WD-V3)  does not support hardware loop mode.

Fixing this is easy: Just comment out the .loop_count line:

rmt_transmit_config_t cfg = {
  //.loop_count = 1, // DISABLED as chip does not support it
  .flags = {
      .eot_level = 0,
   }
};
ESP_ERROR_CHECK(rmt_transmit(/* ... */, &cfg));

Note that if you leave .loop_count at its default, it will always act as if .loop_count = 1.

Posted by Uli Köhler in C/C++, ESP8266/ESP32

Jupyter widget notebook with two sliders, making a HTTP POST request on change

This extended version of Jupyter Widget notebook with interactive IntSlider making a HTTP POST request features two sliders instead of one.

import ipywidgets as widgets
import httpx
from IPython.display import display
# Define the slider widget
delaySlider = widgets.IntSlider(
    value=450,  # Initial value
    min=0,    # Minimum value
    max=2000,    # Maximum value
    step=1,  # Step size
    description='Delay:'
)
lengthSlider = widgets.IntSlider(
    value=20*10,  # Initial value
    min=0,    # Minimum value
    max=40*10,    # Maximum value
    step=1,  # Step size
    description='Length:'
)
# Define a function to handle slider changes
def on_slider_change(change):
    # Define the API URL with the slider value
    httpx.post("http://10.1.2.3/api/configure", json={"channels":[{
        "channel": 0,
        "delay": delaySlider.value,
        "length": lengthSlider.value,
    }]})
# Attach the slider change handler to the slider widget
delaySlider.observe(on_slider_change, names='value')
lengthSlider.observe(on_slider_change, names='value')
# Display the slider widget in the notebook
display(widgets.Box(children=[delaySlider, lengthSlider]))

 

Posted by Uli Köhler in Jupyter, Python

Jupyter Widget notebook with interactive IntSlider making a HTTP POST request

This Jupyter notebook displays an IntSlider and makes a HTTP POST request with JSON body on every change using the httpx library.

import ipywidgets as widgets
import httpx
from IPython.display import display
# Define the slider widget
slider = widgets.IntSlider(
    value=450,  # Initial value
    min=0,    # Minimum value
    max=2000,    # Maximum value
    step=1,  # Step size
    description='Value:'
)
# Define a function to handle slider changes
def on_slider_change(change):
    slider_value = change['new']
    # Define the API URL with the slider value
    httpx.post("http://10.1.2.3/api/configure", json={"delay": slider_value})
# Attach the slider change handler to the slider widget
slider.observe(on_slider_change, names='value')
# Display the slider widget in the notebook
display(slider)

 

Posted by Uli Köhler in Jupyter, Python

ESP32-D0WD-V3 does not support RMT sync manager

I was trying to use a RMT sync manager on the ESP32-D0WD-V3 (Version 3.1). However, trying to use rmt_new_sync_manager() returns ESP_ERR_NOT_SUPPORTED with the following message:

␛[0;31mE (560) rmt: rmt_new_sync_manager(331): sync manager not supported␛[0m
ESP_ERROR_CHECK failed: esp_err_t 0x106 (ESP_ERR_NOT_SUPPORTED) at 0x400d2681

This leads me to believe that the ESP32-D0WD-V3 does not support any RMT sync managers. So far, I have not tried with other controllers.

Posted by Uli Köhler in ESP8266/ESP32

ESP-IDF minimal Wifi client hello world example using PlatformIO

This basic example showcases how to use PlatformIO with ESP-IDF only (no Arduino) to connect to Wifi in station mode and print Hello world in a loop. It is based on the basic Hello World example from PlatformIO ESP32 with ESP-IDF minimal C++ example:

#include <cstdio>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

#include <esp_wifi.h>
#include <esp_log.h>
#include <nvs_flash.h>
#include <esp_netif.h>
#include <esp_event.h>


extern "C" {
    void app_main(void);
}


static void NetworkEventHandler(void* arg, esp_event_base_t event_base,
                          int32_t event_id, void* event_data) {
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        esp_wifi_connect();
        ESP_LOGI("wifi", "Retrying to connect to Wifi...");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI("wifi", "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
    }
}

void InitNVS() {
    // Initialize NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);
}

void InitWifi() {

    // Initialize TCP/IP network interface (required for Wi-Fi)
    ESP_ERROR_CHECK(esp_netif_init());

    // Initialize the event loop
    ESP_ERROR_CHECK(esp_event_loop_create_default());    
    esp_netif_create_default_wifi_sta();

    // Initialize Wi-Fi
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    // Set Wi-Fi to station mode
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));

    // Configure the Wi-Fi connection
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = "MyWifi",
            .password = "mypassword"
        }
    };
    // Register event handler
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &NetworkEventHandler,
                                                        NULL,
                                                        NULL));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                        IP_EVENT_STA_GOT_IP,
                                                        &NetworkEventHandler,
                                                        NULL,
                                                        NULL));

    // Set Wi-Fi configuration and start Wi-Fi
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());
}

void app_main() {
    InitNVS();
    InitWifi();

    while(true) {
        printf("Hello PlatformIO!\n");
        // Wait for one second
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = espidf
monitor_speed = 115200

All sdkconfig settings have been left at their respective defaults.

Posted by Uli Köhler in C/C++, ESP8266/ESP32, PlatformIO

How to fix ESP-IDF error: cannot convert ‘esp_interface_t’ to ‘wifi_interface_t’

Problem:

While trying to  compile your ESP-IDF app using code such as

ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));

you see an error message such as

src/main.cpp:76:41: error: cannot convert 'esp_interface_t' to 'wifi_interface_t'
   76 |     ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
      |                                         ^~~~~~~~~~~~~~~
      |                                         |
      |                                         esp_interface_t

Solution:

Instead of ESP_IF_WIFI_STA, use WIFI_IF_STA (which has the correct type):

ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));

 

Posted by Uli Köhler in C/C++, ESP8266/ESP32

PlatformIO ESP32 with ESP-IDF minimal C++ example

By default, when you initialize a PlatformIO ESP-IDF project, it will generate main.c – not a C++ but a pure C file

This minimal example instead uses main.cpp (you can just rename your main.c – see How to use C++ main.cpp with PlatformIO on ESP32 / esp-idf for a detailed list of steps to do).

#include <cstdio>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

extern "C" {
    void app_main(void);
}

void app_main() {
    while(true) {
        printf("Hello PlatformIO!\n");
        // Wait for one second
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

 

Posted by Uli Köhler in C/C++, ESP8266/ESP32, PlatformIO

How to use C++ main.cpp with PlatformIO on ESP32 / esp-idf

When you are using PlatformIO to compile your firmware, it is easily possible to use a C++ main.cpp instead of the pure C main.c by just renaming main.c to main.cpp

However, you also need to properly declary app_main(). After the #include section of your main.cpp (or at the top of the file if you don’t have an #include section yet), add this code:

extern "C" {
    void app_main(void);
}

This will tell the compiler that app_main() is a C function, not a C++ function.

Full main.cpp example

#include <cstdio>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

extern "C" {
    void app_main(void);
}

void app_main() {
    while(true) {
        printf("Hello PlatformIO!\n");
        // Wait for one second
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

 

Posted by Uli Köhler in C/C++, ESP8266/ESP32, PlatformIO

How to fix ESP32 error: ‘portTICK_PERIOD_MS’ was not declared in this scope

Problem:

While trying to compile your ESP-IDF firmware (with or without PlatformIO), you see an error message such as

src/main.cpp:13:27: error: 'portTICK_PERIOD_MS' was not declared in this scope
   13 |         vTaskDelay(1000 / portTICK_PERIOD_MS);

Solution:

Include FreeRTOS by adding the following lines to the top of the file where the error occured (src/main.cpp in this example):

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

 

Posted by Uli Köhler in ESP8266/ESP32, FreeRTOS, PlatformIO

How to fix ESP32 error: ‘vTaskDelay’ was not declared in this scope

Problem:

While trying to compile your ESP-IDF firmware (with or without PlatformIO), you see an error message such as

src/main.cpp:13:9: error: 'vTaskDelay' was not declared in this scope
   13 |         vTaskDelay(1000 / portTICK_PERIOD_MS);

Solution:

Include FreeRTOS by adding the following lines to the top of the file where the error occured (src/main.cpp in this example):

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

 

Posted by Uli Köhler in ESP8266/ESP32, FreeRTOS, PlatformIO

What pitch do Molex Mini-Fit Jr connectors have?

According to the Molex Website, the entire Mini-Fit family has a pin pitch of 4.2mm

 

Posted by Uli Köhler in Components
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