<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: 코딩나우(하늘아래)</title>
    <description>The latest articles on DEV Community by 코딩나우(하늘아래) (@_8729c5bde46be2).</description>
    <link>https://dev.to/_8729c5bde46be2</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3963698%2F35ec10a0-2beb-4cc6-a512-724301643a25.png</url>
      <title>DEV Community: 코딩나우(하늘아래)</title>
      <link>https://dev.to/_8729c5bde46be2</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_8729c5bde46be2"/>
    <language>en</language>
    <item>
      <title>Converting WebP to JPG on Windows with no dependencies (and the alpha channel gotcha)</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Wed, 23 Sep 2026 01:53:39 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/converting-webp-to-jpg-on-windows-with-no-dependencies-and-the-alpha-channel-gotcha-1dp8</link>
      <guid>https://dev.to/_8729c5bde46be2/converting-webp-to-jpg-on-windows-with-no-dependencies-and-the-alpha-channel-gotcha-1dp8</guid>
      <description>&lt;p&gt;Someone sends you a folder of &lt;code&gt;.webp&lt;/code&gt; files and the upload form only takes JPG. You don't need ImageMagick for this: Windows has shipped a WebP decoder since 1809 (as the Store-delivered &lt;code&gt;Microsoft.WebpImageExtension&lt;/code&gt;), and WIC exposes it to PowerShell. But the built-in path has two sharp edges I only found by testing them.&lt;/p&gt;

&lt;h2&gt;
  
  
  First: renaming is not converting
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 16 photo.webp | xxd
&lt;span class="go"&gt;00000000: 5249 4646 4e0a 0000 5745 4250 5650 3820  RIFF....WEBPVP8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rename it to &lt;code&gt;photo.jpg&lt;/code&gt; and those bytes don't change. Most viewers sniff the content and open it anyway, which is exactly why people think the rename worked - until something that actually validates the format rejects it. Python agrees about what it really is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;renamed.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;
&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;WEBP&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The no-install route: WIC via PowerShell
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Add-Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AssemblyName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PresentationCore&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Pictures\image.webp"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$frame&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.Windows.Media.Imaging.BitmapDecoder&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'None'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'OnLoad'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Frames&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$enc&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.Windows.Media.Imaging.JpegBitmapEncoder&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$enc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QualityLevel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$enc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Frames&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$frame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.IO.File&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;IO.Path&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;ChangeExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.jpg'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$enc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;BitmapDecoder.Create&lt;/code&gt; picks the codec by content, and on a machine with the WebP extension you can confirm which one it chose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$dec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.Windows.Media.Imaging.BitmapDecoder&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'None'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'OnLoad'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$dec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CodecInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FriendlyName&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;# -&amp;gt; Microsoft Webp Decoder&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A whole folder is the same thing in a loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Add-Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AssemblyName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PresentationCore&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Pictures\*.webp"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.Windows.Media.Imaging.BitmapDecoder&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'None'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'OnLoad'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Frames&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.Windows.Media.Imaging.JpegBitmapEncoder&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QualityLevel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Frames&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.IO.File&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;IO.Path&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;ChangeExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.jpg'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Gotcha 1: the alpha channel is gone
&lt;/h2&gt;

&lt;p&gt;This is the part worth knowing before you run it over a folder of logos. The frame that comes back from the Microsoft WebP decoder is &lt;code&gt;Bgr32&lt;/code&gt; - no alpha. Transparent pixels arrive &lt;strong&gt;black&lt;/strong&gt;, and because the data is already flattened, switching to &lt;code&gt;PngBitmapEncoder&lt;/code&gt; doesn't save you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Format&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="c"&gt;# -&amp;gt; Bgr32&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# transparent WebP -&amp;gt; PNG, then inspect the corner pixel:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;#   (0, 0, 0, 255)     # opaque black, not transparent&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pillow reads the same file as &lt;code&gt;RGBA&lt;/code&gt;, so if transparency matters, do it in Python and decide what goes behind it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.webp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;im&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGBA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;P&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;       &lt;span class="c1"&gt;# composite onto white instead of black
&lt;/span&gt;        &lt;span class="n"&gt;im&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGBA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;bg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;bg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paste&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;im&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bg&lt;/span&gt;
    &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_suffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;quality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Gotcha 2: animated WebP silently loses everything after frame 1
&lt;/h2&gt;

&lt;p&gt;The decoder does expose the frames - &lt;code&gt;$dec.Frames.Count&lt;/code&gt; returned 4 for my test file, and Pillow reported &lt;code&gt;n_frames = 4&lt;/code&gt; for the same one. But &lt;code&gt;Frames[0]&lt;/code&gt; is what the snippet above encodes, and JPEG has nowhere to put the rest. If the motion matters, GIF is two lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anim.webp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anim.gif&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;save_all&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  And it gets bigger
&lt;/h2&gt;

&lt;p&gt;WebP -&amp;gt; JPG is a re-encode of already-lossy data, so you pay twice: a little more generation loss, and usually a larger file. My 2.6 KB test WebP became 7.4 KB as a quality-90 JPEG. That tracks with Google's own numbers - lossy WebP is 25-34% smaller than JPEG at equivalent SSIM, lossless is 26% smaller than PNG - so converting "to save space" is backwards. Keep the original, and if size is the goal, resize instead of re-encoding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One or two files, no install&lt;/td&gt;
&lt;td&gt;Any browser-based converter that runs client-side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A folder, nothing installed&lt;/td&gt;
&lt;td&gt;PowerShell + WIC (above)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transparency preserved or flattened your way&lt;/td&gt;
&lt;td&gt;Pillow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Animation kept&lt;/td&gt;
&lt;td&gt;Pillow -&amp;gt; GIF&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Everything above was run on Windows 11 with &lt;code&gt;Microsoft.WebpImageExtension&lt;/code&gt; 1.2.31: the codec name, the &lt;code&gt;Bgr32&lt;/code&gt; frame format, the black corner pixel, the frame counts and the file sizes are measured, not assumed.&lt;/p&gt;




&lt;p&gt;Longer version with the non-developer paths (Paint, Photos, a drag-and-drop converter that doesn't upload): &lt;a href="https://www.coding-now.com/en/guides/webp-to-jpg?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/webp-to-jpg?utm_source=devto&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Has the alpha-drop bitten anyone else, or is there a WIC flag I missed?&lt;/p&gt;

</description>
      <category>windows</category>
      <category>powershell</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Transparent PNGs: the 3 background-removal algorithms, why JPG can't do it, and the color-key gotcha</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Tue, 22 Sep 2026 01:39:12 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/transparent-pngs-the-3-background-removal-algorithms-why-jpg-cant-do-it-and-the-color-key-gotcha-3gl6</link>
      <guid>https://dev.to/_8729c5bde46be2/transparent-pngs-the-3-background-removal-algorithms-why-jpg-cant-do-it-and-the-color-key-gotcha-3gl6</guid>
      <description>&lt;p&gt;Every "make this background transparent" tool is doing one of three things under the hood, and picking the wrong one for your picture is why a logo comes back with a hole in it, or the background is only half gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transparency lives in the alpha channel
&lt;/h2&gt;

&lt;p&gt;A pixel needs a fourth value beyond R, G, B: alpha, how see-through it is. Only formats that define an alpha channel can store it - PNG and WebP per-pixel, GIF as one fully-transparent color, never JPEG.&lt;/p&gt;

&lt;p&gt;This isn't an implementation detail, it's in the spec. The HTML Living Standard says it outright for canvas export:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For image types that do not support an alpha component, the serialized image must be the bitmap image composited onto an opaque black background using the source-over compositing operator.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So &lt;code&gt;canvas.toBlob(cb, 'image/jpeg')&lt;/code&gt; on a transparent canvas doesn't warn you, it silently paints black behind everything and hands you that. Pillow is less quiet about it - trying to save an RGBA image as JPEG raises &lt;code&gt;OSError: cannot write mode RGBA as JPEG&lt;/code&gt; outright.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three removal methods
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;AI subject detection&lt;/strong&gt; - segments the main subject (person, product, animal) from everything else. Handles busy backgrounds, one click, but check fine edges (hair, glass) before you trust it. This is what Windows 11 Paint's Remove Background button and the Photos app do (Paint since the update Microsoft announced in Sept 2023, version 11.2306.30).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connected area (flood fill / magic wand)&lt;/strong&gt; - starts at a point you click and grows outward through similar, touching colors. GIMP's Fuzzy Select and macOS Preview's Instant Alpha both work this way. If the background is split into several disconnected regions by the subject, you click each region.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Color key&lt;/strong&gt; - removes every pixel close to one chosen color, anywhere in the image. GIMP's Color to Alpha and PowerPoint's Set Transparent Color use this. Fast on a flat background, but if that color also shows up &lt;em&gt;inside&lt;/em&gt; the subject, it goes transparent too.&lt;/p&gt;

&lt;p&gt;I actually ran both non-AI methods on the same test image with Pillow - a blue circle logo on white, with a white square cut out in the middle (standing in for white text or a hole in a badge):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;

&lt;span class="c1"&gt;# color key: kill every near-white pixel in the whole image
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;color_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;im&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGBA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="n"&gt;tol&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="n"&gt;tol&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;im&lt;/span&gt;

&lt;span class="c1"&gt;# connected area: flood fill from a known-background pixel
&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;logo.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGBA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floodfill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;thresh&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Color key made the inner white square transparent along with the background - a hole in the logo. Flood-fill from the corner only removed the background that's actually connected to it, leaving the inner square intact. Same tolerance, same image, opposite result, because they're answering different questions ("is this pixel near white?" vs "is this pixel reachable from here through near-white pixels?").&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; if the background color repeats inside the subject (white lettering on a white-background logo, a stamp with a white rim), color key gives you a hole - use flood fill or AI instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Doing it in bulk: rembg
&lt;/h2&gt;

&lt;p&gt;For more than a couple of images, &lt;a href="https://github.com/danielgatis/rembg" rel="noopener noreferrer"&gt;rembg&lt;/a&gt; (MIT license, Python 3.11-3.13) wraps several AI segmentation models behind one CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"rembg[cpu,cli]"&lt;/span&gt;

rembg i photo.jpg photo-cutout.png        &lt;span class="c"&gt;# one file&lt;/span&gt;
rembg p input_folder output_folder        &lt;span class="c"&gt;# a whole folder&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It downloads the model on first use (cached under &lt;code&gt;~/.rembg/models/&lt;/code&gt;) and runs locally after that - nothing gets uploaded, which matters if the images aren't meant to leave your machine. The many online "remove.bg"-style tools are AI too, but the file goes to their server; fine for anything you'd publish anyway, not for scanned IDs or internal docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  In the browser, without a server round-trip
&lt;/h2&gt;

&lt;p&gt;If you're building this into a web page rather than a script, the color-key approach is a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; pixel loop plus a distance check - no upload, no dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;knockoutBackground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;kr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;kg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;kb&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;tolerance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;imageData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hypot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;kr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;kg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;kb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;tolerance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;imageData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I use this approach (plus a small feather at the tolerance edge, to soften jagged outlines) in a small &lt;a href="https://www.coding-now.com/en/image-converter?utm_source=devto" rel="noopener noreferrer"&gt;image converter tool&lt;/a&gt; - drop an image, eyedropper the background, adjust tolerance, export as PNG or WebP. Same color-key limitation applies: same-color pixels inside the subject go transparent too.&lt;/p&gt;




&lt;p&gt;Full write-up with the side-by-side comparison image, Paint/Photos/PowerPoint/Preview steps, and a troubleshooting list: &lt;a href="https://www.coding-now.com/en/guides/transparent-background-png?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/transparent-background-png?utm_source=devto&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What's your go-to for batch background removal - rembg, something cloud-based, or hand-rolled?&lt;/p&gt;

</description>
      <category>python</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>No HyperTerminal on Windows 10/11? Free serial terminals and the 4 settings that matter</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Mon, 21 Sep 2026 06:59:17 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/no-hyperterminal-on-windows-1011-free-serial-terminals-and-the-4-settings-that-matter-19oj</link>
      <guid>https://dev.to/_8729c5bde46be2/no-hyperterminal-on-windows-1011-free-serial-terminals-and-the-4-settings-that-matter-19oj</guid>
      <description>&lt;p&gt;Old equipment manuals still say "connect with HyperTerminal". On Windows 10 or 11 you won't find it: according to Hilgraeve, who wrote it and licensed it to Microsoft, it stopped shipping with Windows Vista and 7. Hilgraeve still sells HyperTerminal Private Edition ($69.99 as of September 2026, free trial), but free tools cover everything it did.&lt;/p&gt;

&lt;p&gt;And no, Windows Terminal doesn't do serial. The GitHub request for it (microsoft/terminal #1280) has been open since 2019 and sits in the Icebox milestone.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Find the COM port
&lt;/h2&gt;

&lt;p&gt;Device Manager -&amp;gt; &lt;strong&gt;Ports (COM &amp;amp; LPT)&lt;/strong&gt;. Unplug and replug the device; the entry that disappears and returns is yours. Or ask PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.IO.Ports.SerialPort&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;GetPortNames&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-PnpDevice&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Ports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-PresentOnly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FriendlyName&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No port at all? Install the adapter chip's driver (CH340, CP210x, FTDI) and rule out a charge-only USB cable.&lt;/p&gt;

&lt;p&gt;A big number like COM34 is harmless. On the PC I wrote this on, nine unplugged port devices still carried COM3 to COM20, while the board plugged in right now was on COM34. One catch: through the Win32 API, ports above 9 must be opened as &lt;code&gt;\\.\COM10&lt;/code&gt; (per the CreateFile docs), and some old programs never learned that.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The four settings
&lt;/h2&gt;

&lt;p&gt;A serial line has no negotiation. Both ends assume the same rules, and if one differs the port still opens but the output is wrong.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Usual value&lt;/th&gt;
&lt;th&gt;Symptom when wrong&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Speed (baud)&lt;/td&gt;
&lt;td&gt;9600 or 115200&lt;/td&gt;
&lt;td&gt;A stream of unreadable characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data format&lt;/td&gt;
&lt;td&gt;8N1&lt;/td&gt;
&lt;td&gt;Only some characters come out wrong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flow control&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Sending stalls, or certain bytes vanish&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Line ending&lt;/td&gt;
&lt;td&gt;CR, LF or CR+LF&lt;/td&gt;
&lt;td&gt;Commands get no reply&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;8N1 is 8 data bits, no parity, 1 stop bit, and it's what Arduino's &lt;code&gt;Serial.begin()&lt;/code&gt; uses unless told otherwise. 7E1 (7 data bits, even parity) is the same length on the wire, which is why the speed can be right and some characters still turn into others. You'll meet it in things like Modbus ASCII.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PuTTY gotcha:&lt;/strong&gt; its default serial flow control is XON/XOFF (per the defaults in its source). XON/XOFF sends 0x11 and 0x13 inside the data stream, so binary data containing those bytes gets eaten as flow control. Set Connection &amp;gt; Serial &amp;gt; Flow control to None unless your device really uses it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The free programs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PuTTY&lt;/strong&gt; - Session &amp;gt; Connection type: Serial, Serial line &lt;code&gt;COM3&lt;/code&gt;, Speed. Data bits, parity and flow control live under Connection &amp;gt; Serial. Blank window? Press Enter a few times; some devices wait for the PC to speak first. Shortcut-friendly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="kd"&gt;putty&lt;/span&gt;&lt;span class="err"&gt;.exe&lt;/span&gt; &lt;span class="na"&gt;-serial &lt;/span&gt;&lt;span class="kd"&gt;COM3&lt;/span&gt; &lt;span class="na"&gt;-sercfg &lt;/span&gt;&lt;span class="m"&gt;115200&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="kd"&gt;n&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="kd"&gt;N&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same options work with &lt;code&gt;plink&lt;/code&gt;, so a Windows Terminal profile running &lt;code&gt;plink.exe -serial COM3 -sercfg 115200,8,n,1,N&lt;/code&gt; turns a tab into a serial console (syntax per the PuTTY manual).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tera Term&lt;/strong&gt; - the closest thing to HyperTerminal, including XMODEM/YMODEM/ZMODEM/Kermit file transfer under File &amp;gt; Transfer. File &amp;gt; New connection &amp;gt; Serial, then Setup &amp;gt; Serial port for the line settings. You can't switch ports from that dialog while connected; disconnect first. 5.7.0 (Sept 2026) ships as an installer and as a plain ZIP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RealTerm&lt;/strong&gt; - open source, built for binary streams: hex and 8/16/32-bit views, timestamped capture. Port tab for speed and port.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CNTerminal&lt;/strong&gt; (my tool, open source) - one 8 MB exe, no install: hex send/receive, an ASCII/hex converter, a preview of the exact bytes before sending, and a DTR toggle so an ESP32 doesn't reset on connect. Honest limit: it's fixed at 8N1 with no flow control, so use Tera Term for 7E1 gear or XMODEM.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Nothing installable? PowerShell
&lt;/h2&gt;

&lt;p&gt;Windows PowerShell 5.1 ships with Windows and can use .NET's SerialPort directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.IO.Ports.SerialPort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'COM3'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;115200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'None'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'One'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadTimeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AT'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c"&gt;# default line ending is LF&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadLine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'no reply'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Defaults I checked: 9600, 8N1, no handshake, DTR and RTS both off. The open errors tell you what's wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Access to the port 'COM3' is denied.&lt;/code&gt; - another program (Arduino serial monitor, another terminal, an upload tool) already has it open.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;The port 'COM99' does not exist.&lt;/code&gt; - wrong number, or the device is unplugged.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;mode COM3&lt;/code&gt; in a command prompt prints the port's current speed, parity, data bits and DTR/RTS state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist when nothing shows up
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Port not listed: driver, cable, another USB socket.&lt;/li&gt;
&lt;li&gt;Port won't open: close whatever else holds it.&lt;/li&gt;
&lt;li&gt;Garbled text: fix the speed; if only some characters are off, check 8N1 vs 7E1.&lt;/li&gt;
&lt;li&gt;No reply: try CR, LF and CR+LF. Can't see what you type? The device doesn't echo; turn on local echo.&lt;/li&gt;
&lt;li&gt;Board reboots on connect: that's the DTR/RTS auto-reset circuit on ESP32 and Arduino boards.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;The full guide, with diagrams and a side-by-side comparison table:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guide:&lt;/strong&gt; &lt;a href="https://www.coding-now.com/en/guides/hyperterminal-alternative?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/hyperterminal-alternative?utm_source=devto&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CNTerminal (free, no install):&lt;/strong&gt; &lt;a href="https://www.coding-now.com/en/cnterminal?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/cnterminal?utm_source=devto&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What are you still using HyperTerminal-era manuals for?&lt;/p&gt;

</description>
      <category>windows</category>
      <category>embedded</category>
      <category>arduino</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Text to speech on Windows: the built-in voices, edge-tts, and why subtitle dubbing never fits</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Mon, 21 Sep 2026 01:02:18 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/text-to-speech-on-windows-the-built-in-voices-edge-tts-and-why-subtitle-dubbing-never-fits-4o6l</link>
      <guid>https://dev.to/_8729c5bde46be2/text-to-speech-on-windows-the-built-in-voices-edge-tts-and-why-subtitle-dubbing-never-fits-4o6l</guid>
      <description>&lt;p&gt;Narration for a video, a spoken prompt in an app, a subtitle file turned into audio: all of it starts with getting text read out and saved to a file. Windows can do it with what's already installed, and the neural voices are a &lt;code&gt;pip install&lt;/code&gt; away. Here's what each route costs you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The voices already in Windows
&lt;/h2&gt;

&lt;p&gt;Narrator and Edge's Read Aloud use the built-in speech engine, but neither will save what it reads. &lt;code&gt;System.Speech&lt;/code&gt; will:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Add-Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AssemblyName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.Speech&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.Speech.Synthesis.SpeechSynthesizer&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# What's available&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetInstalledVoices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;VoiceInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" ("&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;VoiceInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Culture&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;")"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SelectVoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Microsoft Zira Desktop"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Rate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="c"&gt;# -10 .. 10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetOutputToWaveFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"C:\temp\narration.wav"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Speak&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Get-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\temp\script.txt"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Raw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Encoding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No install, no network, and it writes a WAV. The delivery is unmistakably synthetic, so it suits prompts and beeps more than narration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gotcha: added voices that never show up
&lt;/h2&gt;

&lt;p&gt;Add a voice under Settings &amp;gt; Time &amp;amp; language &amp;gt; Speech and it often doesn't appear in &lt;code&gt;GetInstalledVoices()&lt;/code&gt;. The two generations of voices live in different registry hives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# SAPI 5 - what System.Speech reads&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'HKLM:\SOFTWARE\Microsoft\Speech\Voices\Tokens'&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# OneCore - what Settings installs, and Narrator uses&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'HKLM:\SOFTWARE\Microsoft\Speech_OneCore\Voices\Tokens'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the Windows 11 (25H2) machine I checked, the SAPI hive had two voices and the OneCore hive had a third one that &lt;code&gt;System.Speech&lt;/code&gt; could never select. If a voice you installed isn't in the list, this is why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Neural voices from the command line
&lt;/h2&gt;

&lt;p&gt;The voices Edge reads pages with are neural, and &lt;code&gt;edge-tts&lt;/code&gt; drives them directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;edge-tts

&lt;span class="c"&gt;# text -&amp;gt; MP3, with a matching .srt&lt;/span&gt;
edge-tts &lt;span class="nt"&gt;--text&lt;/span&gt; &lt;span class="s2"&gt;"Hello, world!"&lt;/span&gt; &lt;span class="nt"&gt;--write-media&lt;/span&gt; hello.mp3 &lt;span class="nt"&gt;--write-subtitles&lt;/span&gt; hello.srt

edge-tts &lt;span class="nt"&gt;--list-voices&lt;/span&gt;

&lt;span class="c"&gt;# a script file, in a chosen voice&lt;/span&gt;
edge-tts &lt;span class="nt"&gt;--voice&lt;/span&gt; en-US-AriaNeural &lt;span class="nt"&gt;--file&lt;/span&gt; script.txt &lt;span class="nt"&gt;--write-media&lt;/span&gt; narration.mp3

edge-tts &lt;span class="nt"&gt;--rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-50&lt;/span&gt;% &lt;span class="nt"&gt;--text&lt;/span&gt; &lt;span class="s2"&gt;"Hello, world!"&lt;/span&gt; &lt;span class="nt"&gt;--write-media&lt;/span&gt; slower.mp3
edge-tts &lt;span class="nt"&gt;--volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-50&lt;/span&gt;% &lt;span class="nt"&gt;--text&lt;/span&gt; &lt;span class="s2"&gt;"Hello, world!"&lt;/span&gt; &lt;span class="nt"&gt;--write-media&lt;/span&gt; quieter.mp3
edge-tts &lt;span class="nt"&gt;--pitch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-50Hz&lt;/span&gt; &lt;span class="nt"&gt;--text&lt;/span&gt; &lt;span class="s2"&gt;"Hello, world!"&lt;/span&gt; &lt;span class="nt"&gt;--write-media&lt;/span&gt; lower.mp3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Options are from the project's README and CLI help. No API key, no account, and it writes MP3 rather than WAV, which matters once a batch of scripts piles up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dubbing subtitles: the lengths never match
&lt;/h2&gt;

&lt;p&gt;A subtitle file states exactly how long each line is on screen. Read that line aloud and it is nearly always longer, because people skim subtitles while a synthesizer pronounces every syllable. Translate first and the gap widens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cue  1  00:00:00,000 --&amp;gt; 00:00:03,500   (3.5s)   speech 3.2s   fits
cue  2  00:00:03,500 --&amp;gt; 00:00:07,000   (3.5s)   speech 4.6s   overruns into cue 3
cue  3  00:00:07,000 --&amp;gt; 00:00:12,000   (5.0s)   speech 4.4s   fits
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What actually works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Raise the rate. 10-20% faster still sounds natural.&lt;/li&gt;
&lt;li&gt;Shorten the line. The narration doesn't have to match the subtitle word for word.&lt;/li&gt;
&lt;li&gt;Merge two short cues into one sentence, which buys room either side.&lt;/li&gt;
&lt;li&gt;If it has to be frame-accurate, cut per cue in an editor instead of fighting the timings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Making something new rather than dubbing? Generate the narration first and cut the visuals to its length. Then nothing has to be squeezed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part people skip: licensing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;ElevenLabs free tier: 10k credits a month, and &lt;strong&gt;no commercial licence&lt;/strong&gt; on the free plan (paid starts at $6/month).&lt;/li&gt;
&lt;li&gt;Naver Clova Dubbing free tier: monthly download and character limits, attribution required, ads and promotion need a paid plan.&lt;/li&gt;
&lt;li&gt;The Edge neural voices have no quota through &lt;code&gt;edge-tts&lt;/code&gt;, but they are Microsoft's service, so read the terms before shipping commercial work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Figures are from September 2026 and these terms change often. "Free to generate, but not for monetised content" is a common combination, and it's the sort of thing that surfaces after the video is published.&lt;/p&gt;




&lt;p&gt;The full version, with diagrams and a GUI option for people who don't want a terminal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guide:&lt;/strong&gt; &lt;a href="https://www.coding-now.com/en/guides/text-to-speech-mp3?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/text-to-speech-mp3?utm_source=devto&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which route do you use for narration?&lt;/p&gt;

</description>
      <category>windows</category>
      <category>powershell</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Windows Task Scheduler: why your scheduled task doesn't run (and what 0x41303 means)</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Fri, 18 Sep 2026 01:05:40 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/windows-task-scheduler-why-your-scheduled-task-doesnt-run-and-what-0x41303-means-2iep</link>
      <guid>https://dev.to/_8729c5bde46be2/windows-task-scheduler-why-your-scheduled-task-doesnt-run-and-what-0x41303-means-2iep</guid>
      <description>&lt;p&gt;Creating a scheduled task in Windows takes five minutes. Then the time comes and nothing happens, or the program starts but you never see its window. Almost always it's one of a few defaults. The defaults below come from &lt;code&gt;New-ScheduledTaskSettingsSet&lt;/code&gt;, and the result codes from Microsoft's Task Scheduler constants.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four gates
&lt;/h2&gt;

&lt;p&gt;A task has to get through four gates before anything visible happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Trigger           -&amp;gt; 0x41303: the task has not yet run
2. Conditions        -&amp;gt; on battery: doesn't start (default)
3. Security options  -&amp;gt; "whether user is logged on or not": no window
4. Action            -&amp;gt; 0x80070002: file not found, 0x1: the program's exit code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Status and Last Run Result columns and the History tab tell you which gate stopped it. If History is empty, click &lt;strong&gt;Enable All Tasks History&lt;/strong&gt; in the Actions pane. The same events are in Event Viewer under Applications and Services Logs &amp;gt; Microsoft &amp;gt; Windows &amp;gt; TaskScheduler &amp;gt; Operational.&lt;/p&gt;

&lt;h2&gt;
  
  
  No window: the security option
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Run whether user is logged on or not&lt;/strong&gt; runs the task in session 0, not on your desktop. A GUI program still starts but never shows up, and the task sits at &lt;code&gt;0x41301&lt;/code&gt; (currently running). The default for an already running task is &lt;em&gt;Do not start a new instance&lt;/em&gt;, so the next scheduled run is skipped too.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Programs you need to see: &lt;strong&gt;Run only when user is logged on&lt;/strong&gt;, the default for Create Basic Task.&lt;/li&gt;
&lt;li&gt;Scripts that need no window: &lt;em&gt;whether logged on or not&lt;/em&gt;, which also stops the console window flashing. You have to save the password. &lt;em&gt;Do not store password&lt;/em&gt; uses an S4U logon, which per Microsoft's docs has no access to the network or encrypted files, so skip it for backups to a NAS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Only fails on a laptop: the power conditions
&lt;/h2&gt;

&lt;p&gt;Two defaults on the Conditions tab:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start the task only if the computer is on AC power&lt;/strong&gt;: on&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop if the computer switches to battery power&lt;/strong&gt;: on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clear both for tasks that should run on battery.&lt;/p&gt;

&lt;h2&gt;
  
  
  The other defaults
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;New-ScheduledTaskSettingsSet&lt;/code&gt; with no arguments shows them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DisallowStartIfOnBatteries : True
StopIfGoingOnBatteries     : True
StartWhenAvailable         : False   &amp;lt;- runs missed while the PC was off are skipped
WakeToRun                  : False
ExecutionTimeLimit         : PT72H   &amp;lt;- stopped after 3 days
MultipleInstances          : IgnoreNew
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 3-day limit bites anything meant to stay open, like trading apps or servers. And an empty &lt;strong&gt;Start in (optional)&lt;/strong&gt; makes the task run from &lt;code&gt;C:\Windows\System32&lt;/code&gt;, so a program that loads its config from a relative path fails only when scheduled. For a batch file, &lt;code&gt;cd /d "%~dp0"&lt;/code&gt; on the first line fixes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading Last Run Result
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Success (exit code 0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x41301&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The task is currently running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x41303&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The task has not yet run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x80070002&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The system cannot find the file specified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x800710E0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The operator or administrator has refused the request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;0x1&lt;/code&gt;, &lt;code&gt;0x2&lt;/code&gt; ...&lt;/td&gt;
&lt;td&gt;Usually the program's own exit code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;0x41303&lt;/code&gt; isn't a fault. Plenty of built-in tasks only run in specific situations: on the PC I checked, 76 of about 200 tasks showed it. For a batch file that ends with &lt;code&gt;0x1&lt;/code&gt;, append &lt;code&gt;&amp;gt;&amp;gt; log.txt 2&amp;gt;&amp;amp;1&lt;/code&gt; to its commands to see which one failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating it from the command line
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;schtasks&lt;/code&gt; has no Start in option, so if you need one, use PowerShell and fix the defaults while you're at it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$action&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-ScheduledTaskAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Execute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Tools\report.exe"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-WorkingDirectory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Tools"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$trigger&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-ScheduledTaskTrigger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Daily&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-At&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;9am&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$settings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-ScheduledTaskSettingsSet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AllowStartIfOnBatteries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-DontStopIfGoingOnBatteries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-StartWhenAvailable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ExecutionTimeLimit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Register-ScheduledTask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-TaskName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Morning report"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Trigger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$trigger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Settings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$settings&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For quick one-liners &lt;code&gt;schtasks&lt;/code&gt; is fine. Paths with spaces need an inner pair of single quotes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="nb"&gt;schtasks&lt;/span&gt; &lt;span class="na"&gt;/create /tn &lt;/span&gt;&lt;span class="s2"&gt;"Report"&lt;/span&gt; &lt;span class="na"&gt;/tr &lt;/span&gt;&lt;span class="s2"&gt;"'C:\Program Files\Report\report.exe' --quiet"&lt;/span&gt; &lt;span class="na"&gt;/sc &lt;/span&gt;&lt;span class="kd"&gt;daily&lt;/span&gt; &lt;span class="na"&gt;/st &lt;/span&gt;&lt;span class="m"&gt;07&lt;/span&gt;:00
&lt;span class="nb"&gt;schtasks&lt;/span&gt; &lt;span class="na"&gt;/run /tn &lt;/span&gt;&lt;span class="s2"&gt;"Report"&lt;/span&gt;
&lt;span class="nb"&gt;schtasks&lt;/span&gt; &lt;span class="na"&gt;/query /tn &lt;/span&gt;&lt;span class="s2"&gt;"Report"&lt;/span&gt; &lt;span class="na"&gt;/v /fo &lt;/span&gt;&lt;span class="kd"&gt;list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;The full guide, with diagrams and the Create Basic Task walkthrough:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guide:&lt;/strong&gt; &lt;a href="https://www.coding-now.com/en/guides/windows-task-scheduler?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/windows-task-scheduler?utm_source=devto&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which default has bitten you?&lt;/p&gt;

</description>
      <category>windows</category>
      <category>powershell</category>
      <category>productivity</category>
      <category>automation</category>
    </item>
    <item>
      <title>ESP32 upload errors decoded: 'Failed to connect', 'Wrong boot mode detected' and friends</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Thu, 17 Sep 2026 11:21:59 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/esp32-upload-errors-decoded-failed-to-connect-wrong-boot-mode-detected-and-friends-ode</link>
      <guid>https://dev.to/_8729c5bde46be2/esp32-upload-errors-decoded-failed-to-connect-wrong-boot-mode-detected-and-friends-ode</guid>
      <description>&lt;p&gt;ESP32 uploads fail in a handful of ways, and the error text already tells you where it stopped. Arduino IDE, PlatformIO and ESP-IDF all upload through esptool, so the messages are the same everywhere. The wording below is taken from the esptool source (v4.8.1 and current master).&lt;/p&gt;

&lt;h2&gt;
  
  
  The four stages
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. open the port        -&amp;gt; Could not open COM3, the port is busy or doesn't exist.
2. reset into download  -&amp;gt; Wrong boot mode detected (0x13)! The chip needs to be in download mode.
3. sync with the chip   -&amp;gt; No serial data received.
                           Download mode successfully detected, but getting no sync reply: The serial TX path seems to be down.
4. write flash          -&amp;gt; Invalid head of packet (0x..): Possible serial noise or corruption.
                           Serial data stream stopped: Possible serial noise or corruption.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything starts with &lt;code&gt;A fatal error occurred:&lt;/code&gt;, and connection-stage errors add &lt;code&gt;Failed to connect to ESP32:&lt;/code&gt; (the chip name changes with the board).&lt;/p&gt;

&lt;h2&gt;
  
  
  Could not open COM3, the port is busy or doesn't exist
&lt;/h2&gt;

&lt;p&gt;The line under it is the real cause:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(could not open port 'COM3': PermissionError(13, 'Access is denied.', None, 5))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;PermissionError(13, ...)&lt;/code&gt; means another program has the port open, like a terminal or a serial monitor in another IDE window. &lt;code&gt;FileNotFoundError(2, ...)&lt;/code&gt; means there is no port with that name. Close whatever holds the port, unplug and replug the board with Device Manager open to spot the real port, install the USB chip driver (Silicon Labs for CP210x, WCH for CH340), and make sure the cable has data lines.&lt;/p&gt;

&lt;p&gt;A detail from the source: esptool adds its &lt;code&gt;Hint:&lt;/code&gt; line by matching the English text &lt;code&gt;Access is denied&lt;/code&gt;. On a non-English Windows that text is localized, so the busy-port hint may not show up at all. On Linux, &lt;code&gt;Permission denied&lt;/code&gt; means your user needs to be in &lt;code&gt;dialout&lt;/code&gt; (or &lt;code&gt;uucp&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  No serial data received
&lt;/h2&gt;

&lt;p&gt;The port opened, but after the reset not a single byte came back.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Manual download mode: hold BOOT while &lt;code&gt;Connecting...&lt;/code&gt; is printed. If that isn't enough, keep holding BOOT and tap EN. Let go once &lt;code&gt;Writing at 0x...&lt;/code&gt; appears.&lt;/li&gt;
&lt;li&gt;Plug straight into the PC with a short data cable, no hub.&lt;/li&gt;
&lt;li&gt;Power: the esptool docs say the 3.3 V supply has to handle 200-300 mA peaks. Disconnect motors, LED strips and other hungry parts.&lt;/li&gt;
&lt;li&gt;Bare module on a USB-serial adapter: adapter TX to ESP32 RX, adapter RX to ESP32 TX, common ground.&lt;/li&gt;
&lt;li&gt;Lower the upload speed (see below).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Wrong boot mode detected (0x13)
&lt;/h2&gt;

&lt;p&gt;Half good news: esptool read the boot log, so the serial link works. The chip just booted the app in flash instead of download mode. esptool decides this by looking for &lt;code&gt;waiting for download&lt;/code&gt; in the boot log it reads right after the reset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;boot:0x13 (SPI_FAST_FLASH_BOOT)                       &amp;lt;- GPIO0 was HIGH at reset: normal boot
boot:0x3 (DOWNLOAD_BOOT(UART0/UART1/SDIO_REI_REO_V2))
waiting for download                                  &amp;lt;- GPIO0 was LOW: download mode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Hold BOOT during &lt;code&gt;Connecting...&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Remove anything wired to GPIO0 or GPIO2. GPIO0 held HIGH blocks download mode, and GPIO2 must be floating or LOW to enter it.&lt;/li&gt;
&lt;li&gt;GPIO12 pulled HIGH selects 1.8 V flash voltage, which breaks boards with 3.3 V flash.&lt;/li&gt;
&lt;li&gt;If every single upload needs BOOT, suspect the board's auto-reset timing or a USB hub before your code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  No sync reply: the serial TX path seems to be down
&lt;/h2&gt;

&lt;p&gt;Download mode was detected, so the chip's output reaches the PC, but it never answers esptool. Only the PC-to-ESP32 direction is broken. With an adapter, check adapter TX to ESP32 RX (GPIO3, U0RXD). On a dev board this is rare, so try another board with the same cable and PC.&lt;/p&gt;

&lt;h2&gt;
  
  
  Invalid head of packet / Serial data stream stopped
&lt;/h2&gt;

&lt;p&gt;The upload starts and dies partway. The esptool docs point at poor cables, pins shorting on a breadboard, and brownouts. Slow it down:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Arduino IDE&lt;/td&gt;
&lt;td&gt;Tools &amp;gt; Upload Speed &amp;gt; &lt;code&gt;115200&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PlatformIO&lt;/td&gt;
&lt;td&gt;&lt;code&gt;upload_speed = 115200&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESP-IDF&lt;/td&gt;
&lt;td&gt;&lt;code&gt;idf.py -p COM3 -b 115200 flash&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;esptool&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;-b 115200&lt;/code&gt; (the docs go as low as &lt;code&gt;-b 9600&lt;/code&gt; to diagnose)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Also keep stray wires off the flash pins (GPIO6-11 on the classic ESP32) and give high-current parts their own supply during uploads.&lt;/p&gt;

&lt;h2&gt;
  
  
  After a successful upload
&lt;/h2&gt;

&lt;p&gt;If nothing runs, the chip is probably still in download mode, so press EN. On an ESP32-S3 or C3 flashed over native USB, that reset doesn't re-sample the strapping pins, and esptool's &lt;code&gt;--after watchdog-reset&lt;/code&gt; handles it.&lt;/p&gt;




&lt;p&gt;The full version with diagrams, plus the companion post on why the board resets whenever a serial monitor opens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guide:&lt;/strong&gt; &lt;a href="https://www.coding-now.com/en/guides/esp32-upload-failed-to-connect?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/esp32-upload-failed-to-connect?utm_source=devto&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serial monitor resets:&lt;/strong&gt; &lt;a href="https://www.coding-now.com/en/guides/esp32-serial-monitor-reset?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/esp32-serial-monitor-reset?utm_source=devto&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which of these do you run into most?&lt;/p&gt;

</description>
      <category>esp32</category>
      <category>arduino</category>
      <category>embedded</category>
      <category>iot</category>
    </item>
    <item>
      <title>Why your ESP32 resets every time you open the serial monitor (and how to stop it)</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Thu, 17 Sep 2026 08:08:15 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/why-your-esp32-resets-every-time-you-open-the-serial-monitor-and-how-to-stop-it-5h2l</link>
      <guid>https://dev.to/_8729c5bde46be2/why-your-esp32-resets-every-time-you-open-the-serial-monitor-and-how-to-stop-it-5h2l</guid>
      <description>&lt;p&gt;You flash the firmware, open a serial monitor to see what it is doing, and the board reboots, wiping out exactly the state you wanted to look at.&lt;/p&gt;

&lt;p&gt;Nothing is broken. The auto-reset circuit that makes uploading convenient reacts to the monitor in exactly the same way. Once you see how it works, the fix is a line or two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why opening the port resets the board
&lt;/h2&gt;

&lt;p&gt;ESP32 dev boards (DevKitC and friends) carry a USB-to-serial chip such as a CP2102 or CH340. Its DTR and RTS pins drive EN (reset) and IO0 (boot mode) through two transistors. Upload tools use those lines to put the chip into download mode and reset it automatically, which is why you rarely press the buttons.&lt;/p&gt;

&lt;p&gt;But upload tools are not the only software touching those lines. Most terminal programs and drivers raise or drop DTR/RTS when they open a port. The circuit cannot tell an upload from a monitor, so it resets the chip either way.&lt;/p&gt;

&lt;p&gt;For the auto-bootloader circuit most boards use, it only reacts when the two lines differ:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;DTR&lt;/th&gt;
&lt;th&gt;RTS&lt;/th&gt;
&lt;th&gt;EN&lt;/th&gt;
&lt;th&gt;IO0&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Normal run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Normal run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Held in reset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Boot-mode select (IO0 low)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If the two lines don't change at exactly the same instant when the port opens, that brief mismatch is enough to reset the chip. So the fix is to not touch either line at all. (Board vendors vary the circuit slightly, so details can differ.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixes by tool
&lt;/h2&gt;

&lt;h3&gt;
  
  
  PlatformIO
&lt;/h3&gt;

&lt;p&gt;Two lines in &lt;code&gt;platformio.ini&lt;/code&gt; stop the monitor from driving the lines. Uploads are unaffected, because the uploader controls them separately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[env:esp32dev]&lt;/span&gt;
&lt;span class="py"&gt;platform&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;espressif32&lt;/span&gt;
&lt;span class="py"&gt;board&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;esp32dev&lt;/span&gt;
&lt;span class="py"&gt;framework&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;arduino&lt;/span&gt;
&lt;span class="py"&gt;monitor_speed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;115200&lt;/span&gt;
&lt;span class="py"&gt;monitor_dtr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;0&lt;/span&gt;
&lt;span class="py"&gt;monitor_rts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Not on native-USB boards.&lt;/strong&gt; On an ESP32-S3 or C3 talking over the built-in USB-Serial/JTAG (no bridge chip), driving both lines low is itself a reset trigger, so these two lines make it reset every time. Leave them out so the monitor never touches DTR/RTS.&lt;/p&gt;

&lt;h3&gt;
  
  
  ESP-IDF
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;idf.py monitor&lt;/code&gt; deliberately resets the board on start so you see the boot log. To attach to a board that is already running, recent versions accept &lt;code&gt;--no-reset&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;idf.py &lt;span class="nt"&gt;-p&lt;/span&gt; COM5 monitor &lt;span class="nt"&gt;--no-reset&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Python (pyserial)
&lt;/h3&gt;

&lt;p&gt;Turning the lines off &lt;strong&gt;after&lt;/strong&gt; opening is too late. Create the port object first, set the line states, then call &lt;code&gt;open()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serial&lt;/span&gt;

&lt;span class="n"&gt;ser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;        &lt;span class="c1"&gt;# not opened yet
&lt;/span&gt;&lt;span class="n"&gt;ser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;COM5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;ser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baudrate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;115200&lt;/span&gt;
&lt;span class="n"&gt;ser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dtr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;              &lt;span class="c1"&gt;# decide before opening
&lt;/span&gt;&lt;span class="n"&gt;ser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;span class="n"&gt;ser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Arduino IDE
&lt;/h3&gt;

&lt;p&gt;The Arduino IDE serial monitor generally has no option to leave these lines alone. If you need to watch without a reset, PlatformIO or a terminal with a DTR control is the quicker route.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stuck on &lt;code&gt;waiting for download&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Sometimes opening the monitor shows only this, and your program never starts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rst:0x1 (POWERON_RESET),boot:0x3 (DOWNLOAD_BOOT(UART0/UART1/SDIO_REI_REO_V2))
waiting for download
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;DOWNLOAD_BOOT&lt;/code&gt; means IO0 was low at the moment of reset, so the chip skipped your firmware and is waiting for a new one. That is the last row of the table (DTR 0, RTS 1).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Close the monitor and press EN (RST) once. With the lines released, it boots normally.&lt;/li&gt;
&lt;li&gt;Reopen the monitor with RTS off, using the settings above.&lt;/li&gt;
&lt;li&gt;If you wired buttons or sensors to GPIO0, check that nothing holds it low during boot.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Sometimes you want the reset
&lt;/h2&gt;

&lt;p&gt;If you are after the messages printed right at boot, like crash reasons or init logs, the auto-reset is a feature. Keep the defaults, or open the monitor with the lines off and press EN yourself.&lt;/p&gt;

&lt;p&gt;Cutting the board's auto-reset traces, or using a USB-serial adapter wired for TX, RX and GND only, removes monitor resets entirely. The price: every upload needs BOOT held while you tap EN.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick answers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Arduino Uno does this too?&lt;/strong&gt; Similar idea. DTR reaches the reset pin through a capacitor, so opening the port resets it once. Around 10 µF between RESET and GND prevents it, but remove it before uploading.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Won't DTR/RTS off break uploads?&lt;/strong&gt; No. &lt;code&gt;monitor_dtr&lt;/code&gt; / &lt;code&gt;monitor_rts&lt;/code&gt; only apply to the monitor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Wrong boot mode detected&lt;/code&gt; on upload?&lt;/strong&gt; The auto-reset failed to enter download mode. Suspect charge-only cables and flaky hubs, or hold BOOT until &lt;code&gt;Connecting...&lt;/code&gt; appears.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ESP32-S3 / C3 native USB?&lt;/strong&gt; Same symptom, reversed fix: don't set &lt;code&gt;monitor_dtr&lt;/code&gt; / &lt;code&gt;monitor_rts&lt;/code&gt; at all, because both lines low is one of the combinations that resets the chip there. &lt;code&gt;ESP_RST_USB&lt;/code&gt; in the boot log (ESP-IDF 5.1.4+) tells you the monitor did it. The upload side is unchanged: its reset doesn't re-sample the strapping pins, so if a board stays in download mode after flashing, press EN once or use esptool's &lt;code&gt;--after watchdog-reset&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Longer version with a diagram, plus CNTerminal, a free portable serial terminal from the same site whose DTR toggle opens the port without resetting the board:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guide:&lt;/strong&gt; &lt;a href="https://www.coding-now.com/en/guides/esp32-serial-monitor-reset?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/esp32-serial-monitor-reset?utm_source=devto&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CNTerminal (free, no install):&lt;/strong&gt; &lt;a href="https://www.coding-now.com/en/cnterminal?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/cnterminal?utm_source=devto&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which board or tool has caught you out with this?&lt;/p&gt;

</description>
      <category>esp32</category>
      <category>arduino</category>
      <category>iot</category>
      <category>embedded</category>
    </item>
    <item>
      <title>JSON.parse errors decoded: what 'Unexpected token &lt;' and friends actually mean</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Thu, 17 Sep 2026 07:57:13 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/jsonparse-errors-decoded-what-unexpected-token-and-friends-actually-mean-3do</link>
      <guid>https://dev.to/_8729c5bde46be2/jsonparse-errors-decoded-what-unexpected-token-and-friends-actually-mean-3do</guid>
      <description>&lt;p&gt;JSON errors arrive as one terse line, but the causes behind them are a short list: the server sent HTML instead of JSON, there was nothing to parse, or the JSON was written by hand like a JavaScript object.&lt;/p&gt;

&lt;p&gt;The Chrome, Node.js and Python messages below come from actually running each input through Chrome 152, Node.js 24 and Python 3.11, so you can match yours word for word.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the position first
&lt;/h2&gt;

&lt;p&gt;For &lt;code&gt;{"name": "kim", "age": 20,}&lt;/code&gt; (note the trailing comma):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Environment&lt;/th&gt;
&lt;th&gt;Message&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Chrome, Edge, Node.js&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Expected double-quoted property name in JSON at position 26 (line 1 column 27)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python &lt;code&gt;json&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Expecting property name enclosed in double quotes: line 1 column 27 (char 26)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Older Chrome / Node.js&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Unexpected token } in JSON at position 26&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;position&lt;/code&gt; (and Python's &lt;code&gt;char&lt;/code&gt;) counts from &lt;strong&gt;0&lt;/strong&gt;. &lt;code&gt;line&lt;/code&gt; and &lt;code&gt;column&lt;/code&gt; count from &lt;strong&gt;1&lt;/strong&gt;, like your editor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"name": "kim", "age": 20,}
                         ^ position 25: the comma you actually need to delete
                          ^ position 26: where the parser gave up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reported spot is where the parser could no longer continue, not where you made the mistake. Start there and read backwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;Unexpected token '&amp;lt;'&lt;/code&gt; — you got HTML, not JSON
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chrome/Edge : SyntaxError: Failed to execute 'json' on 'Response': Unexpected token '&amp;lt;', "&amp;lt;!DOCTYPE "... is not valid JSON
Firefox     : SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
Safari      : SyntaxError: JSON Parse error: Unrecognized token '&amp;lt;'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;&amp;lt;&lt;/code&gt; is the start of &lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/code&gt;. Staring at the code that builds your JSON will not help — the server sent a document. Usual suspects: a typo in the API path (404 page), a server crash (500 page), an expired session redirecting to a login page, or a dev server without a proxy returning &lt;code&gt;index.html&lt;/code&gt; for &lt;code&gt;/api/...&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Open DevTools → Network → the request → Response. If you see HTML, that is your answer. Then stop it from hiding the real cause:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`HTTP &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Expected JSON, got: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;Unexpected end of JSON input&lt;/code&gt; — nothing to parse
&lt;/h2&gt;

&lt;p&gt;You parsed an empty string: a &lt;code&gt;204 No Content&lt;/code&gt; response, an empty error body, or a zero-byte file. Python reports this &lt;em&gt;and&lt;/em&gt; the HTML case identically as &lt;code&gt;Expecting value: line 1 column 1 (char 0)&lt;/code&gt;, so print what you received.&lt;/p&gt;

&lt;p&gt;Truncated JSON looks different: &lt;code&gt;{"name": "kim", "tags": ["a", "b"&lt;/code&gt; gives &lt;code&gt;Expected ',' or ']' after array element in JSON at position 33&lt;/code&gt;. If the position is the very end of the input, suspect a cut-off response or a log copied without its tail.&lt;/p&gt;

&lt;h2&gt;
  
  
  JavaScript syntax is not JSON
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mistake&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Chrome / Node.js&lt;/th&gt;
&lt;th&gt;Python&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Trailing comma (object)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{"a": 1,}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Expected double-quoted property name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Expecting property name enclosed in double quotes&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trailing comma (array)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[1, 2, 3,]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Unexpected token ']'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Expecting value&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single quotes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{'name': 'kim'}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Expected property name or '}'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Expecting property name enclosed in double quotes&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unquoted key&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{name: "kim"}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Expected property name or '}'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Expecting property name enclosed in double quotes&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Comment&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{"a": 1 // note}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Expected ',' or '}' after property value&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Expecting ',' delimiter&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice that the message rarely names the mistake. A comment does not say "comments are not allowed". Trust the position more than the wording. (And &lt;code&gt;tsconfig.json&lt;/code&gt; accepting comments is why this habit sneaks into &lt;code&gt;package.json&lt;/code&gt; — that one is JSONC, this one is not.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Values JSON doesn't have
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;NaN&lt;/code&gt;, &lt;code&gt;Infinity&lt;/code&gt;, &lt;code&gt;undefined&lt;/code&gt;, Python's &lt;code&gt;True&lt;/code&gt;/&lt;code&gt;None&lt;/code&gt;, leading zeros (&lt;code&gt;007&lt;/code&gt;) and hex (&lt;code&gt;0x1F&lt;/code&gt;) all fail. &lt;code&gt;Unexpected token 'N'&lt;/code&gt; covers both &lt;code&gt;NaN&lt;/code&gt; and &lt;code&gt;None&lt;/code&gt;, so read the quoted fragment after it.&lt;/p&gt;

&lt;p&gt;The sneaky one: &lt;strong&gt;Python writes &lt;code&gt;NaN&lt;/code&gt; by default and reads it back happily.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
&lt;span class="c1"&gt;# '{"score": NaN}'  &amp;lt;- not standard JSON; JavaScript will throw
&lt;/span&gt;
&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt; &lt;span class="n"&gt;allow_nan&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ValueError: Out of range float values are not JSON compliant
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also: &lt;code&gt;str(data)&lt;/code&gt; in Python gives &lt;code&gt;{'ok': True, 'v': None}&lt;/code&gt; — not JSON. Use &lt;code&gt;json.dumps()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Errors inside strings
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bad control character in string literal in JSON at position 14   &amp;lt;- a real newline or tab inside quotes; write \n
Bad escaped character in JSON at position 13                     &amp;lt;- "C:\Users\kim": \U is not an escape
Unexpected token '', "{"a": 1}" is not valid JSON                &amp;lt;- looks empty? that's a BOM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the Windows path, use &lt;code&gt;C:\\Users\\kim&lt;/code&gt; or &lt;code&gt;C:/Users/kim&lt;/code&gt;. Python's message for the same input points one character earlier (&lt;code&gt;char 12&lt;/code&gt;, the backslash) than V8 (&lt;code&gt;position 13&lt;/code&gt;, the &lt;code&gt;U&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The BOM case is invisible in most editors. On Windows, PowerShell 5.1 is a common source: &lt;code&gt;Set-Content -Encoding UTF8&lt;/code&gt; adds a BOM, and &lt;code&gt;&amp;gt;&lt;/code&gt; or &lt;code&gt;Out-File&lt;/code&gt; write UTF-16. Fixes: &lt;code&gt;open(path, encoding="utf-8-sig")&lt;/code&gt; in Python, &lt;code&gt;text.replace(/^\uFEFF/, "")&lt;/code&gt; in Node.js, or re-save as plain UTF-8.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;"undefined" is not valid JSON&lt;/code&gt; and &lt;code&gt;"[object Object]"&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;JSON.parse&lt;/code&gt; stringifies whatever you pass it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;"undefined" is not valid JSON&lt;/code&gt; — often &lt;code&gt;localStorage.setItem("user", undefined)&lt;/code&gt;, which stores the literal string &lt;code&gt;"undefined"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"[object Object]" is not valid JSON&lt;/code&gt; — you parsed something already parsed, like axios's &lt;code&gt;response.data&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Unexpected non-whitespace character after JSON&lt;/code&gt; — two JSON values back to back, e.g. a JSON Lines file parsed in one go. Split on newlines first.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;&lt;/code&gt; in the message → check the response, not the JSON.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;end of JSON input&lt;/code&gt; → empty or truncated input.&lt;/li&gt;
&lt;li&gt;A position → look just before it.&lt;/li&gt;
&lt;li&gt;Big file, no position → &lt;code&gt;python -m json.tool file.json&lt;/code&gt; prints &lt;code&gt;line 4 column 1&lt;/code&gt;-style locations.&lt;/li&gt;
&lt;li&gt;Building JSON by string concatenation → switch to &lt;code&gt;JSON.stringify()&lt;/code&gt; / &lt;code&gt;json.dumps()&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;The full version with diagrams, plus a browser-only formatter that highlights the failing line (nothing is uploaded):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guide:&lt;/strong&gt; &lt;a href="https://www.coding-now.com/en/guides/json-parse-errors?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/json-parse-errors?utm_source=devto&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON formatter &amp;amp; validator:&lt;/strong&gt; &lt;a href="https://www.coding-now.com/en/json-formatter?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/json-formatter?utm_source=devto&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which of these has cost you the most debugging time?&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>python</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Modbus RTU vs TCP: the same command in two different envelopes</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Wed, 16 Sep 2026 08:20:01 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/modbus-rtu-vs-tcp-the-same-command-in-two-different-envelopes-1bco</link>
      <guid>https://dev.to/_8729c5bde46be2/modbus-rtu-vs-tcp-the-same-command-in-two-different-envelopes-1bco</guid>
      <description>&lt;p&gt;Equipment manuals say "supports Modbus" and leave you to work out whether that means RTU or TCP. The names make it sound like two protocols. It is one protocol with two envelopes.&lt;/p&gt;

&lt;p&gt;The command you send — read these registers, write that coil — is byte-for-byte identical. Only the wrapper differs, and every practical consequence (wiring, update rate, device count, how you debug) falls out of that one difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The frames, side by side
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RTU   [ slave addr 1B ][ function code + data  N B ][ CRC 2B ]
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TCP   [ txn id 2B ][ proto 2B ][ length 2B ][ unit id 1B ][ function code + data  N B ]
      \________________ MBAP header, 7 bytes ____________/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The part under the carets is the PDU, and it is the same in both. RTU frames are delimited by 3.5 character times of silence and carry a CRC. TCP frames have no CRC at all — TCP already guarantees integrity, which trips people up when they go hunting for one in a packet capture.&lt;/p&gt;

&lt;p&gt;Because the PDU is shared, so are the function codes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Does&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read coils&lt;/td&gt;
&lt;td&gt;Output bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;02&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read discrete inputs&lt;/td&gt;
&lt;td&gt;Input bits (read-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;03&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read holding registers&lt;/td&gt;
&lt;td&gt;16-bit values — the common one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;04&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read input registers&lt;/td&gt;
&lt;td&gt;16-bit values (read-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;05&lt;/code&gt; / &lt;code&gt;06&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Write single coil / register&lt;/td&gt;
&lt;td&gt;One value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;15&lt;/code&gt; / &lt;code&gt;16&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Write multiple&lt;/td&gt;
&lt;td&gt;A contiguous block&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Wiring is where the real difference lands
&lt;/h2&gt;

&lt;p&gt;RTU runs over two RS-485 wires, daisy-chained. Cheap, and it reaches past a kilometre at 9600 bps. Everyone shares the line, so the master polls one device at a time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fit &lt;strong&gt;120 ohm terminators at both ends&lt;/strong&gt;. Missing ones give intermittent errors that only show up once the cable gets long.&lt;/li&gt;
&lt;li&gt;Vendors label A/B (D+/D-) inconsistently. Swapping the pair is the fastest test.&lt;/li&gt;
&lt;li&gt;Run a common signal ground. Ground potential differences make the link flaky.&lt;/li&gt;
&lt;li&gt;Addresses are 1-247, and two devices sharing one will collide on every reply.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;TCP gives each device an IP on a switch. No new cabling if the network already exists, and several clients can talk at once — in exchange for the 100 m per-segment limit and IPs to manage.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much faster is TCP, really?
&lt;/h2&gt;

&lt;p&gt;Not raw bit rate — round-trip time. RTU at 9600 bps, 8N1, is 10 bits per byte, so roughly 1 ms per byte:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;request 8 B + response 9 B = 17 B      ~ 18 ms
+ inter-frame silence (3.5 chars) x2   ~  7 ms
+ device processing                    ~  5-20 ms
-----------------------------------------------
one round trip                         ~ 30-45 ms  -&amp;gt;  20-30 per second
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ten slaves on that bus means two or three updates per device per second. Fine for temperature or flow; useless when you need tens of milliseconds. The same exchange on Ethernet is 1-5 ms, and devices can be polled in parallel.&lt;/p&gt;

&lt;h2&gt;
  
  
  The addressing trap that eats afternoons
&lt;/h2&gt;

&lt;p&gt;When the manual says &lt;code&gt;40001&lt;/code&gt;, the number that goes on the wire is &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Manual says&lt;/th&gt;
&lt;th&gt;On the wire&lt;/th&gt;
&lt;th&gt;Function code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;40001&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;03&lt;/code&gt; holding register&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;40100&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;99&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;03&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;30001&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;04&lt;/code&gt; input register&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;00001&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;01&lt;/code&gt; coil&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And when a 32-bit value spans two registers, which half comes first is device-specific. A reading that is wildly large or unexpectedly negative is almost always reversed &lt;strong&gt;word order&lt;/strong&gt;, not bad arithmetic — swap the two registers and recombine.&lt;/p&gt;

&lt;h2&gt;
  
  
  In Python, only the client line changes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pip install pymodbus
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pymodbus.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ModbusSerialClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ModbusTcpClient&lt;/span&gt;

&lt;span class="c1"&gt;# RTU - serial port
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ModbusSerialClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;COM3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;baudrate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;9600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;parity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;N&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stopbits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bytesize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# TCP - IP address (this line instead of the one above)
# client = ModbusTcpClient("192.168.0.50", port=502)
&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_holding_registers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slave&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;registers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is pymodbus 3.x; version 2.x used &lt;code&gt;unit=1&lt;/code&gt; instead of &lt;code&gt;slave=1&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When nothing answers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;RTU&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Match baud rate, parity and stop bits exactly. 9600 8N1 against 9600 8E1 gives you silence.&lt;/li&gt;
&lt;li&gt;Swap A/B.&lt;/li&gt;
&lt;li&gt;Check for duplicate slave addresses, and whether the manual is 0- or 1-based.&lt;/li&gt;
&lt;li&gt;Check the terminators and the common ground.&lt;/li&gt;
&lt;li&gt;Connect a single slave directly. One works but several fail? Wiring or termination.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;TCP&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;ping&lt;/code&gt; first, then check port 502 — firewalls block it regularly.&lt;/li&gt;
&lt;li&gt;Check the Unit ID. Behind a gateway it has to match the serial slave address.&lt;/li&gt;
&lt;li&gt;Check the connection limit; cheap devices accept one or two sockets and refuse the rest.&lt;/li&gt;
&lt;li&gt;Capture with Wireshark. An exception reply is the function code plus 0x80: 02 means a bad address, 03 a bad quantity.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;Longer version with diagrams, plus a free robot-communication track that builds a Modbus server and client on a single PC:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guide:&lt;/strong&gt; &lt;a href="https://www.coding-now.com/en/guides/modbus-rtu-vs-tcp?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/modbus-rtu-vs-tcp?utm_source=devto&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Robot communication lessons (free):&lt;/strong&gt; &lt;a href="https://www.coding-now.com/en/blog/robot-comm?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/blog/robot-comm?utm_source=devto&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What tripped you up the first time you wired up Modbus? For me it was terminators — everything worked on the bench and fell apart in the panel.&lt;/p&gt;

</description>
      <category>embedded</category>
      <category>python</category>
      <category>iot</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Decoding Keil µVision build errors: what each message actually means</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Wed, 16 Sep 2026 05:07:06 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/decoding-keil-uvision-build-errors-what-each-message-actually-means-3dg9</link>
      <guid>https://dev.to/_8729c5bde46be2/decoding-keil-uvision-build-errors-what-each-message-actually-means-3dg9</guid>
      <description>&lt;p&gt;Keil µVision tells you a build failed with something like &lt;code&gt;L6218E&lt;/code&gt; or &lt;code&gt;#67&lt;/code&gt;, and that's about it. No hint about which file to open, no suggestion of what to try. The first few times, you end up pasting the code into a search box and hoping.&lt;/p&gt;

&lt;p&gt;In practice the same handful of errors account for most lost afternoons, and each one has a small set of likely causes. Here they are, grouped by the stage that produced them.&lt;/p&gt;

&lt;h2&gt;
  
  
  First: which stage failed?
&lt;/h2&gt;

&lt;p&gt;Before reading the message itself, look at its prefix. It tells you where to look, which cuts the search space in half.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Prefix&lt;/th&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;#20&lt;/code&gt;, &lt;code&gt;#67&lt;/code&gt; ...&lt;/td&gt;
&lt;td&gt;Compile&lt;/td&gt;
&lt;td&gt;C syntax or types — inside that one &lt;code&gt;.c&lt;/code&gt; file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;L6218E&lt;/code&gt;, &lt;code&gt;L6050U&lt;/code&gt; ...&lt;/td&gt;
&lt;td&gt;Link&lt;/td&gt;
&lt;td&gt;Files compiled fine, combining them failed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;A1586E&lt;/code&gt; ...&lt;/td&gt;
&lt;td&gt;Assemble&lt;/td&gt;
&lt;td&gt;Assembly file, usually the startup code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*** error 65&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Simulator&lt;/td&gt;
&lt;td&gt;Not a build failure at all&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One more habit worth forming: &lt;strong&gt;fix the first error, then rebuild.&lt;/strong&gt; Thirty errors rarely means thirty mistakes — usually one early failure cascades.&lt;/p&gt;

&lt;h2&gt;
  
  
  Link errors
&lt;/h2&gt;

&lt;h3&gt;
  
  
  L6050U — "the code size of this image exceeds the maximum"
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: L6050U: The code size of this image (35128 bytes) exceeds the maximum
allowed for this version of the linker.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing is wrong with your code. The free &lt;strong&gt;MDK-Lite&lt;/strong&gt; edition caps output at 32 KB, and you crossed it. In order of effort:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Raise optimisation to &lt;code&gt;-O2&lt;/code&gt; or higher (&lt;em&gt;Options for Target &amp;gt; C/C++&lt;/em&gt;). Often enough on its own.&lt;/li&gt;
&lt;li&gt;Drop &lt;code&gt;printf&lt;/code&gt;. The full formatter pulls in kilobytes; send debug output over UART directly, or enable &lt;strong&gt;MicroLIB&lt;/strong&gt; (&lt;em&gt;Options for Target &amp;gt; Target&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Remove driver and library files the project doesn't actually use.&lt;/li&gt;
&lt;li&gt;Beyond that you need a full licence — the limit is not configurable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If this is a learning project on STM32, note that ST's own STM32CubeIDE is free with no size limit.&lt;/p&gt;

&lt;h3&gt;
  
  
  L6218E — Undefined symbol
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: L6218E: Undefined symbol delay_ms (referred from main.o).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The linker found the &lt;em&gt;call&lt;/em&gt; but not the &lt;em&gt;body&lt;/em&gt;. Check in this order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the &lt;code&gt;.c&lt;/code&gt; file that defines the function actually in the project tree? Including the header and forgetting the &lt;code&gt;.c&lt;/code&gt; is the most common cause by a wide margin.&lt;/li&gt;
&lt;li&gt;Do spelling and case match between declaration and definition?&lt;/li&gt;
&lt;li&gt;Calling C from C++? It needs &lt;code&gt;extern "C"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Undefined symbol main&lt;/code&gt; means &lt;code&gt;main&lt;/code&gt; is missing, or &lt;code&gt;main.c&lt;/code&gt; is excluded from the build.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  L6236E — No section matches selector
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: L6236E: No section matches selector - no section to be FIRST/LAST.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The startup file (&lt;code&gt;startup_xxx.s&lt;/code&gt;) is missing. Add the one for your device, or tick &lt;em&gt;Device &amp;gt; Startup&lt;/em&gt; in &lt;strong&gt;Manage Run-Time Environment&lt;/strong&gt;. Switching target devices and leaving the old startup file behind gives the same error.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Link errors are project-structure problems. Reading source code won't reveal them — start from the file list in the Project pane.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Compile errors
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Message&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#5: cannot open source input file "stm32f4xx.h"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Include path not registered&lt;/td&gt;
&lt;td&gt;Add the folder under &lt;em&gt;C/C++ &amp;gt; Include Paths&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#20: identifier "GPIOA" is undefined&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device header missing, or device macro undefined&lt;/td&gt;
&lt;td&gt;Add the macro (e.g. &lt;code&gt;STM32F407xx&lt;/code&gt;) under &lt;em&gt;C/C++ &amp;gt; Define&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#67: expected a "}"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Missing brace or semicolon&lt;/td&gt;
&lt;td&gt;Look at the line &lt;strong&gt;before&lt;/strong&gt; the reported one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#513: a value of type ... cannot be assigned&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Type mismatch&lt;/td&gt;
&lt;td&gt;Cast explicitly between pointers and integers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If "cannot open source input file" keeps coming back after you move the project, register include paths &lt;strong&gt;relative to the project folder&lt;/strong&gt; instead of absolute paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flashing failures
&lt;/h2&gt;

&lt;p&gt;These are debugger settings, not code.&lt;/p&gt;

&lt;h3&gt;
  
  
  No Algorithm found for: 08000000H - 080003FFH
&lt;/h3&gt;

&lt;p&gt;No flash algorithm is registered for your device. &lt;em&gt;Options for Target &amp;gt; Debug &amp;gt; Settings &amp;gt; Flash Download &amp;gt; Add&lt;/em&gt;, pick your device family, and check the start address and size match the part.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flash Download failed - "Cortex-M4"
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Power and probe first. A charge-only USB cable will not enumerate.&lt;/li&gt;
&lt;li&gt;In &lt;em&gt;Debug &amp;gt; Settings&lt;/em&gt;, confirm the probe reports an IDCODE. If not, it's wiring.&lt;/li&gt;
&lt;li&gt;Previously flashed firmware may have disabled the debug pins — switch to &lt;strong&gt;Connect under Reset&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Still stuck: full chip erase with ST-Link Utility or STM32CubeProgrammer, then retry.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If another tool is holding the probe (CubeProgrammer, a serial monitor), µVision can't claim it. Close it first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simulator: access violation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*** error 65: access violation at 0x40021000 : no 'read' permission
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your code touched a peripheral register the simulator doesn't model. The code is fine; the simulator simply doesn't know that address. Either switch &lt;em&gt;Debug&lt;/em&gt; from the simulator to a real probe, or map the region from the Debug command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MAP 0x40000000, 0x40030000 READ WRITE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Peripheral behaviour is never fully reproduced in simulation. Use the simulator for logic, and check hardware behaviour on the board.&lt;/p&gt;

&lt;h2&gt;
  
  
  A five-minute checklist when nothing makes sense
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;Project &amp;gt; Clean Target&lt;/em&gt;, then rebuild. Stale object files cause phantom errors.&lt;/li&gt;
&lt;li&gt;Read only the &lt;strong&gt;first&lt;/strong&gt; error.&lt;/li&gt;
&lt;li&gt;Confirm every required &lt;code&gt;.c&lt;/code&gt; is in the Project pane — especially the startup file.&lt;/li&gt;
&lt;li&gt;Check &lt;em&gt;Options for Target &amp;gt; Device&lt;/em&gt; against the actual part, and the define macro with it.&lt;/li&gt;
&lt;li&gt;Open &lt;strong&gt;Manage Run-Time Environment&lt;/strong&gt; and look for yellow/red markers — missing packs surface here.&lt;/li&gt;
&lt;li&gt;Move the project somewhere without spaces or non-ASCII characters in the path. Older toolchains still trip over those.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;I keep the full version of this updated here, along with a register-level embedded C track (8051 and STM32, no HAL) if you're learning this from scratch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guide:&lt;/strong&gt; &lt;a href="https://www.coding-now.com/en/guides/keil-uvision-build-errors?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/keil-uvision-build-errors?utm_source=devto&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embedded C lessons (free):&lt;/strong&gt; &lt;a href="https://www.coding-now.com/en/blog/c-embedded?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/blog/c-embedded?utm_source=devto&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which Keil error has eaten the most of your time? If it's one I haven't listed, tell me and I'll add it with a proper explanation.&lt;/p&gt;

</description>
      <category>embedded</category>
      <category>c</category>
      <category>arm</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I built a portable serial terminal for ESP32/Arduino in Rust — because my board kept resetting</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Fri, 12 Jun 2026 06:36:58 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/i-built-a-portable-serial-terminal-for-esp32arduino-in-rust-because-my-board-kept-resetting-4gan</link>
      <guid>https://dev.to/_8729c5bde46be2/i-built-a-portable-serial-terminal-for-esp32arduino-in-rust-because-my-board-kept-resetting-4gan</guid>
      <description>&lt;p&gt;If you debug ESP32 boards over serial, you probably know this one: you flash the firmware, rush to open a serial monitor to catch the boot logs… and the act of opening the monitor resets the board. The DTR/RTS lines toggle on connect, the ESP32 auto-resets, and the first lines you actually wanted are gone.&lt;/p&gt;

&lt;p&gt;I hit this enough times that I built my own terminal.&lt;/p&gt;

&lt;p&gt;CNTerminal&lt;br&gt;
Live + download: &lt;a href="https://www.coding-now.com/en/cnterminal" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/cnterminal&lt;/a&gt; Source (MIT): &lt;a href="https://github.com/cflab2017/tool_serial_terminal_Rust" rel="noopener noreferrer"&gt;https://github.com/cflab2017/tool_serial_terminal_Rust&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's a free, portable serial terminal for Windows — a single ~8 MB .exe. No installer, no runtime, no admin rights: drop it on a USB stick, run it on a locked-down lab PC, done.&lt;/p&gt;

&lt;p&gt;What it does:&lt;/p&gt;

&lt;p&gt;DTR / RTS control — the reason it exists. Keep DTR from toggling on connect and the ESP32 doesn't auto-reset, so you actually see the boot output.&lt;br&gt;
HEX send &amp;amp; receive — for binary protocols, plus an ASCII ↔ HEX converter built in (no more alt-tabbing to a converter site mid-debug).&lt;br&gt;
5,000-line auto-trim — long logging sessions don't slowly eat your RAM or lag the UI.&lt;br&gt;
The usual: port/baud selection, timestamps, a dark amber CRT theme that's easy on the eyes during long sessions.&lt;br&gt;
Why Rust + egui&lt;br&gt;
I wanted a single file people could run anywhere, which ruled out anything with a runtime. Rust + egui turned out to be a great fit: the whole GUI app compiles to one small native binary, immediate-mode UI keeps the serial read loop and the rendering loop simple to reason about, and serialport-rs handles the device side.&lt;/p&gt;

&lt;p&gt;The trickiest part wasn't the UI — it was making the serial reader thread, the auto-trimmed scrollback buffer, and the GUI repaint cooperate without dropping bytes at high baud rates. If you're curious, the source is MIT and pretty small.&lt;/p&gt;

&lt;p&gt;Honest scope&lt;br&gt;
Windows only for now (single-exe portability was the goal).&lt;br&gt;
It's a terminal, not an IDE plugin — it does serial I/O well and nothing else.&lt;br&gt;
If you live in a serial monitor — Arduino, ESP32, STM32, industrial gear over RS-232 — give it a try and tell me what's missing compared to your current setup. Feature requests and bug reports are very welcome, here or on GitHub.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>iot</category>
      <category>arduino</category>
      <category>showdev</category>
    </item>
    <item>
      <title>5 free dev tools I built — no signup, no ads (most run entirely in your browser)</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Tue, 02 Jun 2026 03:41:17 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/5-free-dev-tools-i-built-no-signup-no-ads-most-run-entirely-in-your-browser-mh2</link>
      <guid>https://dev.to/_8729c5bde46be2/5-free-dev-tools-i-built-no-signup-no-ads-most-run-entirely-in-your-browser-mh2</guid>
      <description>&lt;p&gt;I got tired of "free" online dev tools that bury you in ads, make you sign up, or quietly upload your data to a server just to format some JSON or resize an image. So over the last while I built a handful of small, single-purpose tools that fix that: no signup, no ads, and the web ones do everything in your browser — nothing is uploaded.&lt;/p&gt;

&lt;p&gt;Sharing them here in case they're useful, and I'd genuinely love feedback on edge cases. I'm the author of all of these.&lt;/p&gt;

&lt;p&gt;Web tools (100% client-side — your data never leaves the tab)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;JSON Formatter &amp;amp; Validator → &lt;a href="https://www.coding-now.com/en/json-formatter" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/json-formatter&lt;/a&gt; Pretty-print (2/4/tab) or minify, with a collapsible color-coded tree view. The part I use most: when JSON is invalid it points to the exact line:column of the error instead of a vague "unexpected token." Safe to paste API responses with tokens in them, since nothing is uploaded.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Image Format Converter (PNG · JPEG · WebP) → &lt;a href="https://www.coding-now.com/en/image-converter" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/image-converter&lt;/a&gt; Drag an image, pick a format, tweak quality, download. Handy for shrinking PNG screenshots to WebP, or flattening transparency before a JPEG export — all in the canvas, no upload.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Audio Cutter (trim to lossless WAV) → &lt;a href="https://www.coding-now.com/en/audio-cutter" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/audio-cutter&lt;/a&gt; Drop an MP3/WAV/M4A/OGG/FLAC, drag a region on the waveform, preview it, and export just that slice as WAV. Great for grabbing a clip without firing up an editor.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;QR Code Generator (text · URL · WiFi · vCard + logo) → &lt;a href="https://www.coding-now.com/en/qrcode" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/qrcode&lt;/a&gt; Beyond plain URLs: generate WiFi QR codes (guests scan to join) and vCard contact QR codes, drop a logo in the center, and download PNG or SVG (the SVG embeds the logo too).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Bonus — a tiny desktop one&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SnapLaunch (Windows) → &lt;a href="https://www.coding-now.com/en/snaplaunch" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/snaplaunch&lt;/a&gt; A portable tool that adds your favorite apps to the right-click menu of the Windows desktop/folders. No admin rights, single EXE. (This one's a native app, not a web tool — including it for completeness.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Common thread: each is small, free, and respects your data — the web ones are a single static HTML file with zero dependencies. If you find a bug or a missing feature (especially in the JSON validator's error positions), I'd really appreciate hearing it in the comments.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
