<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Abstracts</title>
    <description>Random discoveries around programming, software development and life.</description>
    <link>https://sakhnik.com/</link>
    <atom:link href="https://sakhnik.com/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Mon, 13 Oct 2025 11:51:10 +0300</pubDate>
    <lastBuildDate>Mon, 13 Oct 2025 11:51:10 +0300</lastBuildDate>
    <generator>Jekyll v4.4.1</generator>
    
    
      <item>
        <title>New CV</title>
        <description>&lt;p&gt;I didn’t update my CV for quite some time. And somehow I lost the LaTeX source
code on some of the previous computers. That’s a pity of course, but also a
perfect opportunity to improve both the content and the design. I still wanted
concise and well structured presentation, simple and pleasant design, original
and maintainable implementation. Therefore I kept LaTeX, despite considering
the aspiring &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;typst&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The amount of information that I’d like to present, is constantly growing. So I
have to prioritize what’s to be included and what’s less relevant or impressive.
To improve the density, the text is typeset in two columns. The font is better
to be changed to Noto Sans. At least it’s more accurately printed on my laser
printer.&lt;/p&gt;

&lt;p&gt;The same source file can be used to produce both English and Ukrainian PDF.
Indeed, CV isn’t a free-form text, rather a listing of short phrases and
abbreviations. I suppose it’s worth maintaining both translations in one place.&lt;/p&gt;

&lt;p&gt;I’ve gone an extra mile to preserve the source code this time. First of all,
there’s the repository: &lt;a href=&quot;https://github.com/sakhnik/cv&quot;&gt;CV&lt;/a&gt;. Second, it’s
convenient to attach the source files to the PDF itself. It can be retrieved
later in some viewers or using CLI programs like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pdfdetach&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sakhnik@vivo ~/w/sakhnik.com &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;master&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;1]&amp;gt; pdfdetach &lt;span class=&quot;nt&quot;&gt;-list&lt;/span&gt; assets/sakhnik.pdf 
2 embedded files
1: Makefile
2: sakhnik.tex
sakhnik@vivo ~/w/sakhnik.com &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;master&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;1]&amp;gt; pdfdetach &lt;span class=&quot;nt&quot;&gt;-saveall&lt;/span&gt; assets/sakhnik.pdf
sakhnik@vivo ~/w/sakhnik.com &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;master&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;1]&amp;gt; &lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; sakhnik.tex Makefile
&lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt; 1 sakhnik sakhnik  172 oct 12 21:16 Makefile
&lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt; 1 sakhnik sakhnik 9131 oct 12 21:16 sakhnik.tex
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It is still possible to compile the CV with &lt;a href=&quot;/2022/08/23/texlive-net.html&quot;&gt;texlive.net web API&lt;/a&gt;. I’ve committed and updated script to the
repository.&lt;/p&gt;

&lt;p&gt;The actual CV will be always available on the &lt;a href=&quot;/about/&quot;&gt;About&lt;/a&gt; page. Here is today’s version:&lt;/p&gt;

&lt;embed src=&quot;/assets/sakhnik-2025-10.pdf&quot; type=&quot;application/pdf&quot; width=&quot;100%&quot; height=&quot;600px&quot; /&gt;

</description>
        <pubDate>Sun, 12 Oct 2025 20:55:49 +0300</pubDate>
        <link>https://sakhnik.com/2025/10/12/new-cv.html</link>
        <guid isPermaLink="true">https://sakhnik.com/2025/10/12/new-cv.html</guid>
        
        <category>cv</category>
        
        <category>latex</category>
        
        
      </item>
    
      <item>
        <title>Hello Meshtastic</title>
        <description>&lt;p&gt;A couple of weeks ago, my 9-year daughter got lost in a forest during an
orienteering training session. It happens sometimes, and children are assumed to
follow the predefined algorithm to return to the base. But something was off
this time: she didn’t take a compass or a phone with her. Neither could I locate
her following the planned route. Luckily, she was able to focus and return by
herself, although a tired more than usual. So there came an idea to me. An
electronic tracker could help locate younger students and spare our rescuing
efforts. Quick research showed that there are different options for a ‘GPS
Tracker’.&lt;/p&gt;

&lt;p&gt;Here are my requirements:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;GPS/GNSS receiver for precise geolocation&lt;/li&gt;
  &lt;li&gt;LoRa radio for telemetry&lt;/li&gt;
  &lt;li&gt;Basic screen with directions to other nodes relative to the north, sun or moon&lt;/li&gt;
  &lt;li&gt;Battery power for ~10 hour autonomy.&lt;/li&gt;
  &lt;li&gt;Affordable cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, a smartphone is a viable option, despite it’s impractical to carry
on a training course, and the cellular service isn’t everywhere available in the
forests. So a purposed gadget, designed to be carried as a necklace would be
much more preferable.&lt;/p&gt;

&lt;p&gt;My choice is &lt;a href=&quot;https://heltec.org/project/wireless-tracker/&quot;&gt;Heltec Wireless
Tracker&lt;/a&gt;, which costs ~20$. I
figured out how to draw shapes and print text on the display, how to receive
geolocation coordinates, how to transmit and receive through the LoRa radio.
But then I realized that there’s no need to develop my own system because
&lt;a href=&quot;https://meshtastic.org/&quot;&gt;Meshtastic&lt;/a&gt; already offers everything I need. It’s a
communication system primarily, but it allows sharing and displaying precise
location of the neighbour nodes on the map in a private channel. It already has
mobile app, a convenient infrastructure for maintenance and support.&lt;/p&gt;

&lt;p&gt;So the only thing left for me was to order and package some 2000 mAh LiPo
battery and create an enclosure. I did it with OpenSCAD this time:
&lt;a href=&quot;https://github.com/sakhnik/heltec-wireless-tracker&quot;&gt;repo&lt;/a&gt;. The total cost
turned out to be ~1000 ₴.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2025-04/case-model.png&quot; alt=&quot;Case model&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2025-04/tracker.jpg&quot; alt=&quot;Tracker&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Meshtastic is an open source project, so it can be customized for our use case.
For example, to display the direction to the sun and the moon to help navigation
when the compass isn’t available.&lt;/p&gt;

&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://ut3usw.dead.guru/docs/ham/meshtastic/&quot;&gt;Meshtastic in Ukraine&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://gorges.us/meshtastic-pet-tracker/&quot;&gt;Meshtastic pet tracker&lt;/a&gt; — similar story and application&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Sat, 26 Apr 2025 16:18:34 +0300</pubDate>
        <link>https://sakhnik.com/2025/04/26/hello-meshtastic.html</link>
        <guid isPermaLink="true">https://sakhnik.com/2025/04/26/hello-meshtastic.html</guid>
        
        <category>radio</category>
        
        <category>meshtastic</category>
        
        
      </item>
    
      <item>
        <title>Liberating Tuya WiFi plug</title>
        <description>&lt;p&gt;I use Home Assistant nowadays to manage electric equipment at home. This is a
remarkable leap forward since my first automation attempts &lt;a href=&quot;/2023/01/01/enter-iot.html&quot;&gt;last year&lt;/a&gt;. I’ve been using
&lt;a href=&quot;https://esphome.io&quot;&gt;ESPHome&lt;/a&gt; as custom firmware for Sonoff S26 plugs, which
are based on ubiquitous microcontroller ESP8266. But now it’s easier to find
cheaper Tuya 20A smart sockets for as low as 150₴ (under 4$) a piece. They are
even equipped with power meter. But the challenge is that they use LibreTiny
chip BK7231N. Here’s what I learnt do.&lt;/p&gt;

&lt;p&gt;First of all, I tried
&lt;a href=&quot;https://github.com/tuya-cloudcutter/tuya-cloudcutter&quot;&gt;tuya-couldcutter&lt;/a&gt;. While
it could potentially help with my Aubess 20A plug, it failed with my specific
version. Probably, Tuya patched the vulnerability. So I have to open the case to
attach a serial programmator.&lt;/p&gt;

&lt;p&gt;To do that without destroying it is a challenge. Unlike Sonoff, these
plugs don’t have any screws. Parts of the case are glued and the glue can be
broken by applying pressure gradually to deform the case slightly. Some people
use &lt;a href=&quot;https://www.youtube.com/watch?v=AK7Aibrhzbs&quot;&gt;clamps&lt;/a&gt;. I succeeded using
entrance door as a leverage.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2024-07/door-clamp.jpg&quot; alt=&quot;Door clamp&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2024-07/bk7231n.jpg&quot; alt=&quot;BK7231N&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The pins of the board &lt;a href=&quot;https://docs.libretiny.eu/boards/cb2s&quot;&gt;C2BS&lt;/a&gt;
(&lt;a href=&quot;https://developer.tuya.com/en/docs/Document/cb2s-module-datasheet&quot;&gt;datasheet&lt;/a&gt;)
are easy to identify. I use &lt;a href=&quot;https://www.aliexpress.com/item/1005005936436715.html&quot;&gt;IC
clamps&lt;/a&gt; from AliExpress
to attach the programmator.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2024-07/ic-clamps.jpg&quot; alt=&quot;IC clamps&quot; /&gt;&lt;/p&gt;

&lt;p&gt;ESPHome configuration could be bootstraped using an online tool
&lt;a href=&quot;https://upk.libretiny.eu/&quot;&gt;UPK2ESPHome&lt;/a&gt;. And finally, to install the firmware,
it’s enough to run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;esphome run tuya-1.yml&lt;/code&gt;. Conveniently, it suggests how to
connect the programmator, and how to reset the microcontroller while the
flasher waits for connection (grounding the pin CEN). It could take a couple of
attempts because of unstable connections but succeeded eventually.&lt;/p&gt;

&lt;p&gt;Now beyond of obvious use of smart switches with Home Assistant, there are a
couple of unexpected opportunities. First of all, this is an embedded software
platform in an elegant case, connected to WiFi network and conveniently powered
from a standard AC socket. Second, BK7231N is capable of BLE communications.
So potentially, &lt;a href=&quot;https://github.com/syssi/esphome-jk-bms&quot;&gt;esphome-jk-bms&lt;/a&gt; could
be ported from ESP32 to create a cheap and convenient battery monitor.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2024-07/ha-dashboard.png&quot; alt=&quot;HA dashboard&quot; /&gt;&lt;/p&gt;
</description>
        <pubDate>Fri, 12 Jul 2024 17:32:53 +0300</pubDate>
        <link>https://sakhnik.com/2024/07/12/libre-tuya.html</link>
        <guid isPermaLink="true">https://sakhnik.com/2024/07/12/libre-tuya.html</guid>
        
        <category>linux</category>
        
        <category>iot</category>
        
        <category>esphome</category>
        
        <category>libretuya</category>
        
        <category>home-assistant</category>
        
        
      </item>
    
      <item>
        <title>This day in photo gallery</title>
        <description>&lt;p&gt;Some time ago, I ditched Google Photos in favour of pigallery2. See, how I
expose Shotwell media &lt;a href=&quot;/2021/01/09/gallery.html&quot;&gt;gallery&lt;/a&gt;.
Yet one feature was still left to be wished for. Google prepares appealing
summaries periodically: what happened this day three-five years ago. And it
shouldn’t be too complicated in GNOME Shotwell, I thought. Just go and select
random photos from the database based on the date. Here’s how this idea was
implemented.&lt;/p&gt;

&lt;p&gt;First of all, I researched &lt;a href=&quot;https://github.com/bpatrik/pigallery2&quot;&gt;pigallery2&lt;/a&gt;
features and discovered that it can render markdown files.
So if I generated one and put into a directory, it’d be served alongside the
photos from the same domain. This simplifies referencing full-resolution photos
a lot.&lt;/p&gt;

&lt;p&gt;Then I spent some time coding in Python. The program would iterate over years
into the past until, let’s say, the year 2000. For every year, a random set of
photos are selected for the current day, ordered by time, grouped by event. And
the links to the chosen photos with thumbnail previews are carefully laid out
into a markdown file. The code has been kept in a local &lt;a href=&quot;https://fossil-scm.org&quot;&gt;Fossil
SCM&lt;/a&gt; repository, and this time I exported it to GitHub
for publicity:
&lt;a href=&quot;https://github.com/sakhnik/shotwell-view/blob/e658f22f4b0f7922d866676b75208ee95d7cc725/daily.py&quot;&gt;daily.py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, a systemd timer (or a cron job) could be used to run the program each
day early in the morning. Here’s how I did it:
&lt;a href=&quot;https://github.com/sakhnik/shotwell-view/commit/649bd67997bfb258dd927522c9e52bb488bda125&quot;&gt;systemd&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There’s more that could be enhanced in the future:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Notes could be extracted from the Shotwell database and rendered as markdown
files for pigallery2&lt;/li&gt;
  &lt;li&gt;It should be researched how to attach generic files like GPX tracks to the
events in Shotwell.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As usual, here’s an illustration:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023-08/this-day.jpg&quot; alt=&quot;this day&quot; /&gt;&lt;/p&gt;
</description>
        <pubDate>Sun, 27 Aug 2023 08:20:01 +0300</pubDate>
        <link>https://sakhnik.com/2023/08/27/this-day-gallery.html</link>
        <guid isPermaLink="true">https://sakhnik.com/2023/08/27/this-day-gallery.html</guid>
        
        <category>raspberry</category>
        
        <category>web</category>
        
        <category>media</category>
        
        <category>python</category>
        
        
      </item>
    
      <item>
        <title>Reserve power</title>
        <description>&lt;p&gt;This is the story of how I spent the winter building reserve power sources to
deal with blackouts. When the Russians began to interfere with our power grid,
we started experiencing load shedding. This became a major inconvenience for
our work, our children’s studies, and our daily lives. I talked to a friend,
who advised me to use a hybrid solar inverter with a lithium-ion accumulator to
store energy when it’s available and then recuperate it during power outages.&lt;/p&gt;

&lt;h2 id=&quot;switching&quot;&gt;Switching&lt;/h2&gt;

&lt;p&gt;I purchased a couple of hybrid inverters on AliExpress and waited about a month
for them to arrive. When they finally came, I was visiting my parents and had
to look into the switching panel to figure out how to connect the wires
correctly. There were a couple of different layouts with different inputs
(single-phase, three-phase), available space, and requirements. I did four
installations in total, all very different. But here is the summary of my
experience:&lt;/p&gt;

&lt;p align=&quot;center&quot; width=&quot;100%&quot;&gt;
  &lt;img src=&quot;/assets/2023-05/reserve-power.svg&quot; width=&quot;75%&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;This circuit appears to be the most flexible in the required scenarios:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The grid is online and can power the house including the accumulator charging&lt;/li&gt;
  &lt;li&gt;The grid is semi-functional, some phases are missing, yet the house could be powered by one phase&lt;/li&gt;
  &lt;li&gt;The grid is off-line, a gasoline generator is used to power the house and store the excessive energy&lt;/li&gt;
  &lt;li&gt;The generator is shut down, and the inverter is powering the house using the stored energy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;possible-combinations&quot;&gt;Possible combinations&lt;/h3&gt;

&lt;p&gt;The first switch is a three-pole reserve selection I-0-II. When it’s in position I,
the house is connected to the grid directly. When it’s in position II, the house
is connected to the reserve power source: generator, inverter output or even a single selected phase.&lt;/p&gt;

&lt;p&gt;The second switch is a single-pole selection I-0-II, which can be used to take the power
directly from the selector, or from the inverter output.&lt;/p&gt;

&lt;p&gt;The selector is a rotary phase selection plus reserve I-0-II-0-III-0-IV-0. It allows selecting
one phase from the grid or generator output.&lt;/p&gt;

&lt;p&gt;So there are multiple use cases of the circuit, but let’s consider the most common ones.&lt;/p&gt;

&lt;h3 id=&quot;case-1-power-grid-is-online&quot;&gt;Case 1: power grid is online&lt;/h3&gt;

&lt;p&gt;This is the normal state when cheap and powerful electricity from the grid is available.
The current flows like before my intervention into the panel. It’ll allow
charging the battery by selecting one of the phases and switching on the supply
to the inverter input.&lt;/p&gt;

&lt;p align=&quot;center&quot; width=&quot;100%&quot;&gt;
  &lt;img src=&quot;/assets/2023-05/reserve-power1.svg&quot; width=&quot;75%&quot; /&gt;
&lt;/p&gt;

&lt;h3 id=&quot;case-2-some-phases-are-missing&quot;&gt;Case 2: some phases are missing&lt;/h3&gt;

&lt;p&gt;In case there’s some failure in the grid resulting in at least one phase available, the house
could still be powered from the grid. This is suitable if the load is moderate because of
multiple reasons. But yet, just one switch and a selector notch away from powering the whole
house instead of just a part of the house.&lt;/p&gt;

&lt;p align=&quot;center&quot; width=&quot;100%&quot;&gt;
  &lt;img src=&quot;/assets/2023-05/reserve-power2.svg&quot; width=&quot;75%&quot; /&gt;
&lt;/p&gt;

&lt;h3 id=&quot;case-3-generator-working-but-the-inverter-is-bypassing-the-power&quot;&gt;Case 3: generator working, but the inverter is bypassing the power&lt;/h3&gt;

&lt;p&gt;Although it’s possible to use the gasoline generator directly, it’s more efficient to
store the excess energy into the battery. The reason is that the internal combustion engine
isn’t very scalable, and a significant portion of the fuel is required to just idle it.
Adding more load, of course, will increase fuel consumption, but not necessarily
proportionally. Moreover, the generator requires downtime maintenance: either to just
refuel it or to change the oil and clean the filters.&lt;/p&gt;

&lt;p align=&quot;center&quot; width=&quot;100%&quot;&gt;
  &lt;img src=&quot;/assets/2023-05/reserve-power3.svg&quot; width=&quot;75%&quot; /&gt;
&lt;/p&gt;

&lt;h3 id=&quot;case-4-no-external-power-the-inverter-works-off-the-battery&quot;&gt;Case 4: no external power, the inverter works off the battery&lt;/h3&gt;

&lt;p&gt;So this is the last resort when nothing else works: no grid, no generator. The inverter doesn’t
produce much noise, except for the cooling fans kicking in depending on the actual load.
The limitation here is the capacity of the battery. 120 Ah is more than enough to last for
a couple of hours for powering lights, communication, and pumping water occasionally.&lt;/p&gt;

&lt;p align=&quot;center&quot; width=&quot;100%&quot;&gt;
  &lt;img src=&quot;/assets/2023-05/reserve-power4.svg&quot; width=&quot;75%&quot; /&gt;
&lt;/p&gt;

&lt;h2 id=&quot;showcase&quot;&gt;Showcase&lt;/h2&gt;

&lt;h3 id=&quot;parents-house&quot;&gt;Parent’s house&lt;/h3&gt;

&lt;p&gt;The house is powered by a single phase from the grid.
A DC circuit breaker and Anderson power plug were added later.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023-05/parents0.jpg&quot; alt=&quot;Switching panel&quot; /&gt;
&lt;img src=&quot;/assets/2023-05/parents1.jpg&quot; alt=&quot;Inverter&quot; /&gt;&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/LeAzsSsG7Ps&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h3 id=&quot;parent-in-laws-house&quot;&gt;Parent-in-law’s house&lt;/h3&gt;

&lt;p&gt;Three-phase supply from the grid.
Led batteries were used this time because they require less maintenance than lithium ones,
and the grid electricity was more available at the time. This was the first three-phase
connection and an additional box was needed to host the switches and the selector.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023-05/parents2.jpg&quot; alt=&quot;Overview&quot; /&gt;&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/gOWEL1YNkFs&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h3 id=&quot;our-apartment-in-kyiv&quot;&gt;Our apartment in Kyiv&lt;/h3&gt;

&lt;p&gt;This one is a one-phase supply. The space was very limited, but still enough to add two components.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023-05/appartment.jpg&quot; alt=&quot;The panel&quot; /&gt;
&lt;img src=&quot;/assets/2023-05/appartment2.jpg&quot; alt=&quot;The inverter&quot; /&gt;&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/AhoMDwEC_s8&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h3 id=&quot;cottage-house&quot;&gt;Cottage house&lt;/h3&gt;

&lt;p&gt;This time I enjoyed the benefit of available cable from the switcher panel to the utility room.
The electric heater hasn’t been used and is unlikely to be utilized in the future.
So I just repurposed the wires.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023-05/house1.jpg&quot; alt=&quot;Switch panel&quot; /&gt;
&lt;img src=&quot;/assets/2023-05/house2.jpg&quot; alt=&quot;Inverter&quot; /&gt;&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/PIUN383YU18&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;portable-power-source&quot;&gt;Portable power source&lt;/h2&gt;

&lt;p&gt;After two installations, just one inverter was left for two different places.
And I realized at this point, that it’s possible to reuse both the inverter and the battery
making them portable. Indeed, it only required four wires to connect the inverter:
AC in, AC out, neutral and protective equipment (earthing). I chose an off-the-shelf
16A 4-wire plug assigning pins in the following way:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Pin&lt;/th&gt;
      &lt;th&gt;Wire colour&lt;/th&gt;
      &lt;th&gt;Assignment&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;PE&lt;/td&gt;
      &lt;td&gt;&lt;span style=&quot;color:blue&quot;&gt;█&lt;/span&gt; blue&lt;/td&gt;
      &lt;td&gt;Neutral&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;&lt;span style=&quot;color:black&quot;&gt;█&lt;/span&gt; black&lt;/td&gt;
      &lt;td&gt;AC in&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;&lt;span style=&quot;color:yellow&quot;&gt;█&lt;/span&gt; yellow&lt;/td&gt;
      &lt;td&gt;PE&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;&lt;span style=&quot;color:red&quot;&gt;█&lt;/span&gt; red&lt;/td&gt;
      &lt;td&gt;AC out&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Not only can the inverter be connected to a house via such a plug, but also an extension
cable could be prepared. Now the inverter could power some equipment outdoors
for some time with the charge in the battery, and even allow recharging the battery
from a generator.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023-05/extension.jpg&quot; alt=&quot;Extension cable&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;issues&quot;&gt;Issues&lt;/h2&gt;

&lt;p&gt;Special care should be taken of the generator connection. Usually, generators for F-type
plugs allow connecting the load either way N-L or L-N. And this doesn’t matter
for appliances. But in our case, it’s essential to match neutral and line with the switching
circuit. I haven’t looked into what would happen in the case of an incorrect connection.
My mitigation plan so far was to use asymmetric type F plugs, and double
checking the line status with a one-contact test light or non-contact voltage
detector.&lt;/p&gt;
</description>
        <pubDate>Sun, 07 May 2023 15:00:39 +0300</pubDate>
        <link>https://sakhnik.com/2023/05/07/reserve-power.html</link>
        <guid isPermaLink="true">https://sakhnik.com/2023/05/07/reserve-power.html</guid>
        
        <category>electricity</category>
        
        
      </item>
    
      <item>
        <title>Enter IOT</title>
        <description>&lt;p&gt;Here is one simple and cheap way to automate electric boiler activation. There
are lots of “smart power plug” available on OLX. Especially the ones from
&lt;a href=&quot;https://www.tuya.com/&quot;&gt;tuya&lt;/a&gt;. For example, I was able to purchase one for
300 ₴ (~$8). It came without any written instruction or even an
identification. However, there are lots of guides and manuals on the web, and it
was a matter of error and trial to get it creating a WiFi hotspot. Then after
connecting to it, the
Android application &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.tuya.smartlife&amp;amp;gl=US&quot;&gt;Smart
Life&lt;/a&gt;
can be used to configure plug’s connection to the LAN WiFi. That’s just enough
to start manually controlling the plug like turning it on and off on schedule.&lt;/p&gt;

&lt;p&gt;Now imagine I’d like to do something that’s not a stock functionality. For
example, when the power is cut for some load shedding, and comes back a bit
later, I’d like to make sure the water boiler stays switched off for extra
minutes before turning on. This would allow the grid to ramp up the power supply
without causing immediate overload. An incentive for custom automation!&lt;/p&gt;

&lt;p&gt;I took the first Python library that popped in the search:
&lt;a href=&quot;https://pypi.org/project/tinytuya/&quot;&gt;tinytuya&lt;/a&gt;. It has detailed instructions on
how to register as a developer, discover the device id and get the necessary key
to control the device. I ended up customizing one of the examples to ensuring
the switch is on:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;#!/usr/bin/env python
&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tinytuya&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tinytuya&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;OutletDevice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dev_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;lt;id&amp;gt;&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;address&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;lt;addr&amp;gt;&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;local_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;3.3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;dps&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Turn on&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;turn_on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Exception &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The script then is wrapped into a systemd service:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[Unit]
Description=Boiler power on
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
User=sakhnik
Group=users
ExecStart=/usr/bin/python /home/sakhnik/work/boiler/boiler.py
StandardOutput=journal

[Install]   
WantedBy=default.target
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The service should be started by a timer:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[Unit]
Description=Start boiler deferred after boot

[Timer]
OnBootSec=15min

[Install]
WantedBy=timers.target
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And the timer needs to be enabled:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;systemctl daemon-reload
systemctl enable boiler.timer
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, the switch relay can be configured to start in the switched off state after
powering up in the Smart Life app. When the power is restored, the switch is
initially off and the computer is booting up. After 15 minutes, the timer
kicks off the boiler service running the program, which turns the switch on.
It’s a one-time action, so one can continue managing the switch manually.&lt;/p&gt;

&lt;p&gt;There are disadvantages in this system too. For example, the privacy. Although,
there’s very little harm that can be done, I don’t particularly like to share
my internal network layout with Tuya. I’d rather prefer to have a direct access
to the plug without engaging complex external servers. I’ve just heard that
this might be possible with some other smart plug producers. Looking forward
to explore.&lt;/p&gt;
</description>
        <pubDate>Sun, 01 Jan 2023 10:56:28 +0200</pubDate>
        <link>https://sakhnik.com/2023/01/01/enter-iot.html</link>
        <guid isPermaLink="true">https://sakhnik.com/2023/01/01/enter-iot.html</guid>
        
        <category>linux</category>
        
        <category>iot</category>
        
        <category>python</category>
        
        
      </item>
    
      <item>
        <title>Fixing video/audio synchronization in a downloaded material</title>
        <description>&lt;p&gt;As we’ve started to be impacted by the power blackouts, we resorted to
downloading videos whenever possible to watch them later when convenient.
The browser addon &lt;a href=&quot;https://www.downloadhelper.net/&quot;&gt;Video DownloadHelper&lt;/a&gt;
hits right the spot for that. But it turns out that the video frame rate may
be rogue in some cases. There can be a ridiculous number as high as 16k fps.
My solution is to transcode the file forcing the frame rate to the desired
value, which is actually detected correctly by the addon on the web page. In our
case it was 30 FPS. Here’s the bash script invoking FFmpeg three times to
extract audio and video tracks, and multiplex them back into an mp4 container:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash -e&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$# &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-lt&lt;/span&gt; 1 &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;1&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fi

&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;fname&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;

extract_video&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  ffmpeg &lt;span class=&quot;nt&quot;&gt;-loglevel&lt;/span&gt; error &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$fname&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-map&lt;/span&gt; 0:v &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt;:v copy &lt;span class=&quot;nt&quot;&gt;-bsf&lt;/span&gt;:v h264_mp4toannexb &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; h264 -
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

extract_audio&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  ffmpeg &lt;span class=&quot;nt&quot;&gt;-loglevel&lt;/span&gt; error &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$fname&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-vn&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-acodec&lt;/span&gt; copy &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; adts -
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

ffmpeg &lt;span class=&quot;nt&quot;&gt;-fflags&lt;/span&gt; +genpts &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; 30 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &amp;lt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;extract_video&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &amp;lt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;extract_audio&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-map&lt;/span&gt; 0:v &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt;:v copy &lt;span class=&quot;nt&quot;&gt;-map&lt;/span&gt; 1:a &lt;span class=&quot;nt&quot;&gt;-movflags&lt;/span&gt; faststart &lt;span class=&quot;nv&quot;&gt;$fname&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-fixed&lt;/span&gt;.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Anonymous pipes are used here to pass the video and audio streams into the
multiplexing command.&lt;/p&gt;
</description>
        <pubDate>Mon, 21 Nov 2022 17:04:33 +0200</pubDate>
        <link>https://sakhnik.com/2022/11/21/video-download.html</link>
        <guid isPermaLink="true">https://sakhnik.com/2022/11/21/video-download.html</guid>
        
        <category>linux</category>
        
        <category>ffmpeg</category>
        
        
      </item>
    
      <item>
        <title>Compiling LaTeX documents on texlive.net</title>
        <description>&lt;p&gt;I’ve just realized that I’m not ready to install a multi-gigabyte TexLive
distribution in the 32 GB file system of my Chromebook-based laptop.
And I need to update the CV sometimes. Luckily, there’s a web service available
exactly for that: &lt;a href=&quot;https://texlive.net&quot;&gt;texlive.net&lt;/a&gt;. It turned out capable of
rendering my &lt;a href=&quot;/assets/sakhnik.pdf&quot;&gt;resume&lt;/a&gt; using XeLaTeX via their test web
page, but it didn’t work immediately when I tried using their API directly.
Here’s how I did that eventually.&lt;/p&gt;

&lt;p&gt;According to their
&lt;a href=&quot;https://davidcarlisle.github.io/latexcgi/#http-requests&quot;&gt;documentation&lt;/a&gt;, the
parameters need to be sent in an HTTP POST multipart/form-data request. It’s
convenient to employ &lt;a href=&quot;https://curl.se&quot;&gt;curl&lt;/a&gt; to do the job. And it worked on simple
test documents. But when I tried with the real CV, the compilation failed
consistently while using the package &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hyperref&lt;/code&gt;. Comparison of the Firefox network
capture with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl --trace-ascii&lt;/code&gt; revealed that the test web page sends
bigger &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Content-Length&lt;/code&gt; for practically the same parameter set. Aha! The
service must be sensitive to the line endings format. It expects &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\r\n&lt;/code&gt; for some
reason, and my file was authored using Unix convention with just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So there’s my script to note:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash -e&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Compile xelatex document using the API of https://texlive.net&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;texlive&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://texlive.net&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# The compilation will fail if the document doesn&apos;t use DOS line endings CRLF&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-F&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;filecontents[]=@&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-F&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;filename[]=document.tex&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-F&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;engine=xelatex&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-F&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;return=pdf&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;texlive&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/cgi-bin/latexcgi&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$headers&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-Po&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;(?&amp;lt;=Location: )(/latexcgi/.*?)(?=\r)&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$location&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.pdf &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
    &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-Lo&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;/.tex/.pdf&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;texlive&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else
    &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-L&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;texlive&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
    &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;1
&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Tue, 23 Aug 2022 20:56:15 +0300</pubDate>
        <link>https://sakhnik.com/2022/08/23/texlive-net.html</link>
        <guid isPermaLink="true">https://sakhnik.com/2022/08/23/texlive-net.html</guid>
        
        <category>latex</category>
        
        <category>bash</category>
        
        
      </item>
    
      <item>
        <title>nvim-ui progress</title>
        <description>&lt;p&gt;A year and a half passed since I set off to create a simple
&lt;a href=&quot;https://neovim.io&quot;&gt;Neovim&lt;/a&gt; UI in C++ in my spare time. The project
&lt;a href=&quot;https://github.com/sakhnik/nvim-ui&quot;&gt;nvim-ui&lt;/a&gt; became an exciting journey with
quite a bit of discovery, experience and notable conclusions to
share.&lt;/p&gt;

&lt;h2 id=&quot;spyui-in-nvim-gdb&quot;&gt;SpyUI in nvim-gdb&lt;/h2&gt;

&lt;p&gt;First of all, the idea emerged when
&lt;a href=&quot;https://github.com/sakhnik/nvim-gdb/blob/master/test/spy_ui.py&quot;&gt;SpyUI&lt;/a&gt; was
implemented to support maintenance of my Neovim plugin
&lt;a href=&quot;https://github.com/sakhnik/nvim-gdb&quot;&gt;nvim-gdb&lt;/a&gt;. SpyUI would allow just mirroring and
capturing the UI progression during automatic tests or actual use cases to
simplify bug hunting. Not surprisingly, Neovim itself actively uses a UI client
for automatic testing. The script was embarrassingly simple suggesting that a
full-fledged UI shouldn’t be too difficult either.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;+----------------------------------------+
|                                        |
|~                                       |
|~                                       |
|~                                       |
|[No Name]             0,0-1          All|
|/src/test.cpp:17                        |
|/tmp/nvim-gdb/test/src/test.cpp:17:190:b|
|eg:0x5555555551e6                       |
|(gdb)                                   |
|&amp;lt;port -- gdb -q a.out 21,1           Bot|
|                                        |
|&quot;/tmp/nvim-gdb/test/src/test.cpp&quot;       |
+----------------------------------------+
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The essential summary of the SpyUI was the data model that Neovim maintains
to share the UI state. The grid is a matrix of text cells with their
highlighting ID associated: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[[(text, hl_id)] * width] * height&lt;/code&gt;. So the first idea is to
assemble the grid matrix based on the Neovim messages, and process it further
for the best appearance and performance. For instance, concatenate the
adjacent cells with the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hl_id&lt;/code&gt; into words, and skip the invisible spaces
at all: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[[(stride, word, hl_id)]] * height&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;nvim-ui-in-sdl2&quot;&gt;nvim-ui in SDL2&lt;/h2&gt;

&lt;p&gt;So the first attempt was with &lt;a href=&quot;https://www.libsdl.org/&quot;&gt;SDL2&lt;/a&gt;. Surely, it’d
allow achieving superb performance by with hardware acceleration! Pango and
Cairo from the GNOME stack were used to render the text into bitmaps, and
the bitmaps were transformed into textures. The prepared textures could be reused
multiple times if that specific piece of text didn’t change. One more
way to relieve the CPU usage was throttling of grid flushes. Really, there’s no
need to do rendering more frequently than 25 FPS.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022-07/nvim-ui-sdl2.png&quot; alt=&quot;nvim-ui with SDL2&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It worked decently so the version 0.0.1 was released just to get things started.
Then soon the version 0.0.2 followed with a couple of bugs fixed, a bit of
development infrastructure automatated.&lt;/p&gt;

&lt;p&gt;But there was also a significant drawback discovered (issue
&lt;a href=&quot;https://github.com/sakhnik/nvim-ui/issues/20&quot;&gt;#20&lt;/a&gt;). Let’s call it &lt;em&gt;texture
noise&lt;/em&gt;. And I couldn’t find any way to fix it. Apparently, it’s attributed to
the GPU rendering.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022-07/nvim-ui-sdl2-noise.png&quot; alt=&quot;SDL2 texture noise&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;porting-to-gtk-4&quot;&gt;Porting to GTK 4&lt;/h2&gt;

&lt;p&gt;After a bit of research, I decided to try using
&lt;a href=&quot;https://blog.gtk.org/2020/12/16/gtk-4-0/&quot;&gt;GTK 4&lt;/a&gt; directly. That’s because Pango
and Cairo are already part of GTK, first of all. Then GTK would also allow
using ready widgets for configuration dialogs, for instance, for font selection.
And a lot more. But what’s most important, the toolkit recommends using stock
widgets for all the custom drawing whenever possible. That means that what was a
“texture” in SDL2 could now easily be implemented as a GTK label! Moreover, all
the styling could be conveniently done using CSS, leaving behind very low level
coding.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022-07/nvim-ui-gtk4.png&quot; alt=&quot;nvim-ui in GTK 4&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is the time when the application got the menu, and the ability to connect to
a remote Neovim instance. The performance was OK, except when there was a lot of
little labels to be created at once. For example, when getting a listing of
digraphs with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:digraphs&lt;/code&gt;, around 1,500 labels would be created and placed on
the grid on every PgUp/PgDn. This caused a noticeable delay of 0.3 seconds.&lt;/p&gt;

&lt;h2 id=&quot;enter-gir2cpp&quot;&gt;Enter Gir2cpp&lt;/h2&gt;

&lt;p&gt;That version wasn’t released immediately because I realized that GTK’s C API is
a bit cumbersome and unpleasant. For example, one should know in beforehand that
the class GtkEntry implements the interface Editable:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-C++&quot;&gt;auto t = gtk_editable_get_text(GTK_EDITABLE(entry));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;instead of the conventional&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-C++&quot;&gt;auto t = entry.get_text();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Bringing in gtkmm seemed an overkill for just a handful of widgets I was
intending to use. But one idea seemed particularly appealing:
&lt;a href=&quot;https://gitlab.com/mnauw/cppgir&quot;&gt;cppgir&lt;/a&gt; uses GObject
&lt;a href=&quot;https://gi.readthedocs.io/en/latest/&quot;&gt;introspection&lt;/a&gt; to generate C++ wrappers.
Unfortunately, I failed to build the code with it properly. It didn’t seem too
complicated to create an custom ad-hoc generator of C++ wrappers for
GObject-based libraries. So there’s another auxiliary side project that could
potentially has it’s own application:
&lt;a href=&quot;https://github.com/sakhnik/gir2cpp&quot;&gt;gir2cpp&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It’s just a Python library that needs to be properly configured with the
white and black list of desired symbols to be wrapped in C++. Unfortunately, I
wasn’t able to find a way to seamlessly integrate it into the Meson build
system. Perhaps because it actually isn’t known what exactly files are going
to be generated given the required configuration. So it’s utilized as a
preconfiguration step before the Meson build system is invoked:
&lt;a href=&quot;https://github.com/sakhnik/nvim-ui/blob/ef35eb7a3a0ab40a9f4c8b9071a7bc801eb93955/scripts/prepare-gtkpp.sh&quot;&gt;prepare-gtkpp.sh&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However quick and dirty the approach is, the code improved significantly:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-C++&quot;&gt;Texture t{row, Gtk::Label::new_(&quot;&quot;).g_obj()};
t.label.set_markup(text.c_str());
t.label.set_sensitive(false);
t.label.set_can_focus(false);
t.label.set_focus_on_click(false);
t.label.get_style_context().add_provider(_css_provider.get(), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
_grid.put(t.label, 0, y);
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;dealing-with-the-performance-issues&quot;&gt;Dealing with the performance issues&lt;/h2&gt;

&lt;p&gt;Fine-grained labels/textures are perfect to minimize the redrawing work, but
display a couple of significant drawbacks. The most annoying one is
that there can be lots of labels to update when listing messages (see
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:digraphs&lt;/code&gt;). Another one is the horizontal alignment of separate textures,
especially thin vertical lines in the adjacent lines in
&lt;a href=&quot;https://github.com/nvim-telescope/telescope.nvim&quot;&gt;telescope&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://asciinema.org/a/511855&quot;&gt;&lt;img src=&quot;https://asciinema.org/a/511855.svg&quot; alt=&quot;asciicast&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I tried to simplify the renderer to create only one big label for one grid
row. This makes the data model a bit more complex, but simplifies the code a
lot: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[[(text, hl_id)] * cols] * rows&lt;/code&gt; → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[[(word, hl_id)]] * rows&lt;/code&gt;.
Specifically, the text layout is now done using Pango markup. This turned out to
be a great idea for both the UI appearance and responsiveness and code
maintainability.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022-07/nvim-ui-tt.png&quot; alt=&quot;nvim-ui with Pango markup&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;perspective&quot;&gt;Perspective&lt;/h2&gt;

&lt;p&gt;So here we are after 1.5 years of experimentation.
There’s an ongoing project, a Neovim UI with the following features:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Simplicity of the idea and C++ implementation&lt;/li&gt;
  &lt;li&gt;Decent appearance and performance&lt;/li&gt;
  &lt;li&gt;Solid foundation for implementing more features&lt;/li&gt;
  &lt;li&gt;A good sandbox for further experiments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To be continued.&lt;/p&gt;
</description>
        <pubDate>Sun, 24 Jul 2022 22:39:11 +0300</pubDate>
        <link>https://sakhnik.com/2022/07/24/nvim-ui.html</link>
        <guid isPermaLink="true">https://sakhnik.com/2022/07/24/nvim-ui.html</guid>
        
        <category>cpp</category>
        
        <category>gtk</category>
        
        
      </item>
    
      <item>
        <title>Record and replay debugging</title>
        <description>&lt;p&gt;It was a year ago in a &lt;a href=&quot;https://github.com/sakhnik/nvim-gdb&quot;&gt;nvim-gdb&lt;/a&gt; issue
&lt;a href=&quot;https://github.com/sakhnik/nvim-gdb/issues/151&quot;&gt;#151&lt;/a&gt; that I discovered
&lt;a href=&quot;https://rr-project.org/&quot;&gt;rr&lt;/a&gt;. It promised to allow recording a program
execution once, and debugging that run multiple times exactly the same way.
Finally, there turned up an occasion to try it in the real life, and to
implement the suggestion in the issue.&lt;/p&gt;

&lt;p&gt;The task at hand was to investigate how &lt;a href=&quot;https://ffmpeg.org/&quot;&gt;FFmpeg&lt;/a&gt; calculates
the video frames presentation timestamps for some compressed video stream.
That’s what can be seen by executing ffprobe:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ffprobe &lt;span class=&quot;nt&quot;&gt;-loglevel&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-8&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; test.mp4 &lt;span class=&quot;nt&quot;&gt;-select_streams&lt;/span&gt; v &lt;span class=&quot;nt&quot;&gt;-show_entries&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;packet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;pts | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;pts | &lt;span class=&quot;nb&quot;&gt;head
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;pts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
&lt;span class=&quot;nv&quot;&gt;pts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4
&lt;span class=&quot;nv&quot;&gt;pts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2
&lt;span class=&quot;nv&quot;&gt;pts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3
&lt;span class=&quot;nv&quot;&gt;pts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;7
&lt;span class=&quot;nv&quot;&gt;pts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;5
&lt;span class=&quot;nv&quot;&gt;pts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It looks like the sequence of frames is I-P-B-B-P-B-B etc. It makes perfect
sense. But where does the number 1 for the first frame come from? It can be
traced by following the memory assignment backward in the history. So let’s
record the execution of ffprobe:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;rr record ffprobe &lt;span class=&quot;nt&quot;&gt;-loglevel&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-8&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; stream.mpd &lt;span class=&quot;nt&quot;&gt;-select_streams&lt;/span&gt; v &lt;span class=&quot;nt&quot;&gt;-show_entries&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;packet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;pts                
rr: Saving execution to trace directory &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;/home/sakhnik/.local/share/rr/ffprobe-4&lt;span class=&quot;s1&quot;&gt;&apos;.
[PACKET]
pts=1
[/PACKET]
[PACKET]
pts=3
[/PACKET]
[PACKET]
pts=2
[/PACKET]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then the debugging can be started by just typing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rr replay&lt;/code&gt;. But it’s worth
doing that in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvim-gdb&lt;/code&gt; to enable proper code navigation and highlighting.
There’s a demo of how I did it:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://asciinema.org/a/506942&quot;&gt;&lt;img src=&quot;https://asciinema.org/a/506942.svg&quot; alt=&quot;asciicast&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Fri, 08 Jul 2022 17:49:31 +0300</pubDate>
        <link>https://sakhnik.com/2022/07/08/rr.html</link>
        <guid isPermaLink="true">https://sakhnik.com/2022/07/08/rr.html</guid>
        
        <category>linux</category>
        
        <category>gdb</category>
        
        <category>cpp</category>
        
        
      </item>
    
  </channel>
</rss>
