<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
	<title>spezifisches</title>
	<subtitle>This and that.</subtitle>
	
	<link href="https://spezifisch.codeberg.page/feed/feed.xml" rel="self"/>
	<link href="https://spezifisch.codeberg.page/"/>
	<updated>2023-08-03T00:00:00Z</updated>
	<id>https://spezifisch.codeberg.page/</id>
	<author>
		<name>spezifisch</name>
		<email>spezifisch23@proton.me</email>
	</author>
		
		<entry>
			<title>PSV3 Soldering Iron Hack - Part 2</title>
			<link href="https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/"/>
			<updated>2023-08-03T00:00:00Z</updated>
			<id>https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/</id>
			<content type="html">&lt;p&gt;This is the sequel to &lt;a href=&quot;https://spezifisch.codeberg.page/posts/2023-07-26/psv3-soldering-iron-hack-part-1/&quot;&gt;PSV3 Soldering Iron Hack - Part 1&lt;/a&gt; where I already introduced the result of this whole project: &lt;a href=&quot;https://github.com/spezifisch/T12PenSolder&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;a quite usable replacement firmware&lt;/a&gt;. Now let&#39;s see how we got there (so I guess it&#39;s rather a &lt;em&gt;prequel&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;I already showed my annotated board layout:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-layout-Gu0HX3DsLr-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Photo of the PCB with highlighted and annotated traces&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-layout-Gu0HX3DsLr-orig.jpeg&quot; width=&quot;2338&quot; height=&quot;365&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Traced and annotated PCB layout, top layer (as always, click to enlarge).&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;It was really helpful to create this image to get a first level of understanding about what&#39;s going on in this soldering iron.
I made photos of the top and the bottom of the PCB,&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; added them as semi-transparent image layers in GIMP and traced the signals and parts in additional layers by hand/mouse.&lt;/p&gt;
&lt;p&gt;Based on that image I created a &lt;a href=&quot;https://spezifisch.codeberg.page/assets/posts/pen-solder/pen_solder_v3.pdf&quot;&gt;reverse engineered schematic (PDF)&lt;/a&gt; in KiCad, which has also been a handy reference while developing the new firmware. For purposes of documentation though in this post I believe a kind of top-down block diagram is a better starting point.&lt;/p&gt;
&lt;h2 id=&quot;soldering-tip&quot; tabindex=&quot;-1&quot;&gt;Soldering Tip &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#soldering-tip&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let&#39;s start with a view of the parts around the soldering tip:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-layout_blocks_front-xA1z78H_hj-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Photo of the PCB with highlighted blocks&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-layout_blocks_front-xA1z78H_hj-orig.jpeg&quot; width=&quot;1514&quot; height=&quot;365&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;A closer view at the business end.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;On the left you can see two contacts for the &lt;strong&gt;T12 soldering tip&lt;/strong&gt; and connecting to those, the 20 V high-side heater driver MOSFET, and the operational amplifier for the tip&#39;s thermocouple temperature measurement.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-t12tips-11Qzp1O-OE-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;3 soldering tips in plastic bags&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-t12tips-11Qzp1O-OE-orig.jpeg&quot; width=&quot;943&quot; height=&quot;478&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;My choice of T12-compatible soldering tips: T12-K (knife shape), T12-BC2 (beveled shape), T12-D24 (chisel shape)&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://www.hakko.com/english/support/doc/result.php?s_cat%5B1%5D=1&amp;amp;s_keyword=T12&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Hakko T12&lt;/a&gt; compatible soldering tips are used by many cheap soldering irons and soldering stations (and by a few good ones). It&#39;s an &lt;a href=&quot;https://eleshop.eu/knowledgebase/active-or-passive/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;active&lt;/a&gt; soldering tip so the heat is generated close to the tip leading to fast heat up times. Temperature measurement is also already integrated using a thermocouple. Both are connected in series between two pins (here marked &lt;em&gt;-/+&lt;/em&gt; while &lt;em&gt;E&lt;/em&gt; is for Earth and not connected in this low voltage soldering iron):&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-tip-schema-9DuIDNRNZd-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Cross-section of the soldering tip&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-tip-schema-9DuIDNRNZd-orig.jpeg&quot; width=&quot;740&quot; height=&quot;315&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Inside a T12 soldering tip. Credit: Marius Taciuc&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3 id=&quot;heating&quot; tabindex=&quot;-1&quot;&gt;Heating &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#heating&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To heat the soldering tip we just apply our supply voltage to the pins (&lt;em&gt;-/+&lt;/em&gt; in the image above, &lt;em&gt;TIP_A/B&lt;/em&gt; in the schematic below).
This happens to always be 20 V in our case.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-sch-driver-49uxwVF05Z-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Schematic showing two MOSFETs driving the tip&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-sch-driver-49uxwVF05Z-orig.png&quot; width=&quot;1154&quot; height=&quot;648&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Soldering tip heating circuit&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;On the left we have a pin of the microcontroller (signal &lt;em&gt;TIPHEAT_DRV&lt;/em&gt;) controlling a simple driver stage with a small N-MOSFET (&lt;em&gt;Q2&lt;/em&gt; here, its marking is &lt;code&gt;72K&lt;/code&gt;) which controls a P-MOSFET (&lt;em&gt;Q1&lt;/em&gt; here) that switches 20 V (&lt;em&gt;VBUS&lt;/em&gt;) going to the tip on or off.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;During the heating phase we can model the soldering tip as a simple resistor. With a multimeter I measured its resistance to be ~8 Ohm (on my &lt;em&gt;T12-B2&lt;/em&gt; tip).
So we can expect a theoretical maximum output of 50 W given the fixed supply voltage.&lt;/p&gt;
&lt;p&gt;To regulate the heating element to get a stable temperature level we&#39;re switching the tip power with PWM to achieve a finer grained average output power. The desired output power is calculated by a PID controller (adapted from &lt;a href=&quot;https://github.com/Ralim/IronOS&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;IronOS&lt;/a&gt;) that runs at 10 Hz which coincidentally is the same as the PWM frequency we&#39;re using.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h3 id=&quot;temperature-measurement&quot; tabindex=&quot;-1&quot;&gt;Temperature measurement &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#temperature-measurement&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Inside the tip there&#39;s a thermocouple connected in series with the heating element and thanks to the Seebeck effect, we can measure a voltage between the &lt;em&gt;-&lt;/em&gt; and &lt;em&gt;+&lt;/em&gt; pins which is related to the tip&#39;s temperature. But this works only when the heating element is turned off — otherwise you would just measure the 20 V that the power MOSFET connects to those pins.&lt;/p&gt;
&lt;p&gt;Therefore we need to turn the heat off regularly for a fixed minimal amount of time; we need to wait for the temperature to stabilize a bit before taking ADC voltage samples. IronOS implements a bit more finesse at this point by filtering the measurements but it seems to work well enough to just increase the ADC sample time by a bit (like the stock firmware does, too).&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-sch-temperature-YPeiog0faX-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Schematic showing an op amp circuit&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-sch-temperature-YPeiog0faX-orig.png&quot; width=&quot;1292&quot; height=&quot;589&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Thermocouple amplifier, from tip (left, TIP_A) to ADC input (right, TIPTEMP_MEAS).&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;With only e.g. ~11.3 mV at 450 °C the voltage of the thermocouple in our operating range is too small to measure directly with the ADC of our microcontroller. That&#39;s why we have a non-inverting amplifier op amp circuit to boost this voltage (e.g. to ~2.5 V at 450 °C).&lt;/p&gt;
&lt;h3 id=&quot;temperature-conversion&quot; tabindex=&quot;-1&quot;&gt;Temperature conversion &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#temperature-conversion&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Funnily enough there is &lt;a href=&quot;https://hackaday.io/project/94905-hakko-revenge/log/144548-hakko-t12-thermocouple-is-not-type-k&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;a bit&lt;/a&gt; &lt;a href=&quot;https://www.eevblog.com/forum/projects/measuring-temperature-from-a-t12-soldering-tip/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;of a&lt;/a&gt; &lt;a href=&quot;http://dangerousprototypes.com/forum/index.php?topic=5264.0&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;disagreement&lt;/a&gt; whether the type of the thermocouple in T12 tips is Type K, C, N, or whatever. I can&#39;t say which one is correct but either way it is going to be a linear temperature relationship between voltage and temperature in our operating range. So I made a round of rough measurements to get some input data for the PID controller.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-calibvideo-cZs47e0Uuy-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Multimeter and soldering iron&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-calibvideo-cZs47e0Uuy-orig.jpeg&quot; width=&quot;1920&quot; height=&quot;1080&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Still from calibration video&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;I used the temperature mode of a &lt;em&gt;Voltcraft VC-523&lt;/em&gt; multimeter, held it to the soldering tip, printed the raw ADC values in big numbers on the OLED display and filmed a video showing both the multimeter&#39;s and the iron&#39;s displays. Then I noted a bunch of temperature/ADC value pairs and did a linear regression in GNU Octave (&lt;a href=&quot;https://spezifisch.codeberg.page/assets/posts/pen-solder/soldering_tip_linear_reg.m&quot;&gt;soldering_tip_linear_reg.m&lt;/a&gt;, &lt;a href=&quot;https://spezifisch.codeberg.page/assets/posts/pen-solder/szf-tip1-calib1.txt&quot;&gt;raw data&lt;/a&gt;), which leads to the following graph with values converted to microvolts:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-tipcalibration-jZOje2wGFu-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Graph showing Temperature over Tip Voltage&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-tipcalibration-jZOje2wGFu-orig.png&quot; width=&quot;873&quot; height=&quot;655&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Quick and dirty first calibration run (blue: measurements, red: regression curve)&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;To implement my conversion function in the firmware I skipped the intermediate step and used ADC values directly to get the temperature in degrees Celsius with: &lt;code&gt;T/°C = 0.2037 * x/(ADC units) - 73.8&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This worked for a start and using this I was able to get pretty consistent temperature control, i.e. I set the soldering iron to hold 320 °C and the multimeter agreed within a few degrees with that.&lt;/p&gt;
&lt;p&gt;But there are a few problems to consider with my calibration:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Specialized devices to calibrate soldering iron temperatures exist (e.g. search on Aliexpress for &amp;quot;soldering iron calibration&amp;quot;) — I don&#39;t know how using my multimeter&#39;s thermocouple compares to those.&lt;/li&gt;
&lt;li&gt;It matters where on the tip you measure. I tried to hold the sensor in a consistent position on the side of the tip where the solder would be but results in a different place will not be the same. (Maybe &lt;a href=&quot;https://hackaday.io/project/94905-hakko-revenge/log/144548-hakko-t12-thermocouple-is-not-type-k&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;putting it in oil&lt;/a&gt; would be better?)&lt;/li&gt;
&lt;li&gt;I didn&#39;t consider cold junction compensation. So I probably have at least a fixed offset in my temperature control depending on the room temperature, thus I should probably subtract room temperature from the values. But then again this soldering iron doesn&#39;t have a separate sensor to measure ambient (or handle) temperature. Maybe this could be a setting in the GUI, though.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Finally, comparing these results with &lt;a href=&quot;https://github.com/Ralim/IronOS/blob/v2.21/source/Core/BSP/Pinecilv2/ThermoModel.cpp&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;IronOS&#39; temperature curve of a T12-style tip&lt;/a&gt; they&#39;re pretty similar in the range I care about most (i.e. above 300 °C) except for a static offset, which may be due to my missing cold junction compensation or due to measuring errors on my side. On the low end (below 200 °C) there are bigger disagreements. In any case I would trust their measurements more than mine, so I&#39;m going with their interpolation table instead of my regression function for now.&lt;/p&gt;
&lt;h2 id=&quot;usb-supply-control&quot; tabindex=&quot;-1&quot;&gt;USB supply control &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#usb-supply-control&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On the right half of the board the &lt;em&gt;CH224K Low-cost USB-PD Sink Controller&lt;/em&gt; (&lt;a href=&quot;https://www.laskakit.cz/user/related_files/ch224ds1.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;datasheet&lt;/a&gt;) runs the show and configures the USB power supply over the USB-C pins DM/DP/CC1/CC2. USB Bus Voltage &lt;code&gt;VBUS&lt;/code&gt; then powers the soldering tip and it&#39;s also the input voltage for an &lt;em&gt;XL1509&lt;/em&gt; 3.3 V buck regulator (&lt;a href=&quot;https://www.xlsemi.com/datasheet/XL1509-EN.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;datasheet&lt;/a&gt;) that powers everything else on the board.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-layout_blocks_back-Sk1gnLamgp-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;PCB with ICs and USB-C jack&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-layout_blocks_back-Sk1gnLamgp-orig.jpeg&quot; width=&quot;792&quot; height=&quot;365&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Hardware blocks around the USB connector.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3 id=&quot;usb-pd-controller&quot; tabindex=&quot;-1&quot;&gt;USB-PD controller &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#usb-pd-controller&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;em&gt;CH224K&lt;/em&gt; gives two options to configure the voltage it requests from the USB power supply: A resistor connected to pin &lt;code&gt;CFG1&lt;/code&gt; or high/low logic levels setting pins &lt;code&gt;CFG1/2/3&lt;/code&gt;, which would be quite interesting if they were connected to a microcontroller.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-pdoptions-xjLBAROc49-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;tables showing which resistor values or logic levels give which VUSB voltage&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-pdoptions-xjLBAROc49-orig.png&quot; width=&quot;847&quot; height=&quot;496&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;CH224K voltage configuration options (source: datasheet)&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Sadly, on this board the &lt;code&gt;CFGx&lt;/code&gt; pins are not connected to anything, so 20 V is always chosen for &lt;code&gt;VUSB&lt;/code&gt;. I&#39;m thinking about connecting a 56 kΩ resistor to &lt;code&gt;CFG1&lt;/code&gt; though to select 15 V and give the P-MOSFET a longer life.&lt;/p&gt;
&lt;h2 id=&quot;microcontroller&quot; tabindex=&quot;-1&quot;&gt;Microcontroller &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#microcontroller&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The microcontroller is an STM32 clone named &lt;code&gt;CHIPSEA 32F030P6F6&lt;/code&gt;. It seems to behave pretty well and I didn&#39;t have any problems with the ADC or otherwise.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-sch-uc-FCcV9O9xaP-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;A schematic showing one IC&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-sch-uc-FCcV9O9xaP-orig.png&quot; width=&quot;1343&quot; height=&quot;747&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Schematic around the microcontroller&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;This design has some annoying quirks though:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The OLED I2C display isn&#39;t connected to the also available hardware I2C pins but to random I/O pins, so we have to use bit-banging to control the display.&lt;/li&gt;
&lt;li&gt;The tip heat driver (&lt;em&gt;TIPHEAT_DRV&lt;/em&gt; in my schematic) is connected to &lt;code&gt;PA3&lt;/code&gt; which doesn&#39;t have PWM — the only timer channel on this F030 line is &lt;em&gt;TIM15_CH2&lt;/em&gt; and this particular footprint doesn&#39;t have Timer 15.&lt;/li&gt;
&lt;li&gt;There is no way to reset the &amp;quot;STM32&amp;quot;, e.g. for SWD debugging and also no UART/etc. output, so we need to test by pin wiggling until the display works&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;oled-display&quot; tabindex=&quot;-1&quot;&gt;OLED display &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#oled-display&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To find out how to talk with the OLED display I hooked up a logic analyzer to its I2C bus. As we can quickly see from all the I2C writes to address &lt;code&gt;0x3c&lt;/code&gt; it probably uses a SSD1306-compatible protocol, so in the best case we only need to replicate the used initialization sequence and can then use the &lt;a href=&quot;https://github.com/olikraus/u8g2&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;u8g2 display library&lt;/a&gt; to render our display content and do the rest of the communication.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-layout_bottom_lcd-HvDugRhHtk-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;An unpowered OLED display&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-layout_bottom_lcd-HvDugRhHtk-orig.jpeg&quot; width=&quot;1050&quot; height=&quot;365&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;The bottom side of the PCB pretty much only contains this display (and the 3 buttons).&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3 id=&quot;bad-code&quot; tabindex=&quot;-1&quot;&gt;Bad code &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#bad-code&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I had already found the display initialization code in the dumped stock firmware (of course I could have also used the logic analyzer to dump it, but I didn&#39;t trust it due to the weird NACK behavior mentioned below):&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-ghidra-oled-QQS4TIBfC4-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Pseudocode output from Ghidra&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-ghidra-oled-QQS4TIBfC4-orig.png&quot; width=&quot;438&quot; height=&quot;823&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;In the stock firmware: First part of the OLED initialization&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Funnily enough the stock firmware sends each of the bytes during the initialization in a separate I2C transmission (including start + stop condition, address byte, register byte) which takes needlessly long. You can just send the whole sequence in one transmission instead (and &lt;em&gt;u8g2&lt;/em&gt; does this btw.).&lt;/p&gt;
&lt;p&gt;Another weird behaviour is that, going by the logic analyzer traces, the display doesn&#39;t seem to send ACKs — after every byte the bus master (i.e. the microcontroller) should let go of the SDA line and the slave should pull it low to indicate acknowledgement of that byte.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-la-i2c-khdtWWYgfy-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-la-i2c-khdtWWYgfy-orig.png&quot; width=&quot;1182&quot; height=&quot;360&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Logic analyzer trace of I2C traffic with stock firmware: NACKs everywhere! (Red &#39;N&#39; markers in the bottom row.)&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;It turns out that the reason why the OLED display doesn&#39;t seem to send ACKs is because it can&#39;t — the stock firmware leaves the I2C SDA and SDL pins in push-pull mode the whole time. This means the &lt;em&gt;high&lt;/em&gt; state on SDA is actively driven high by the microcontroller. When the OLED display tries to drive it &lt;em&gt;low&lt;/em&gt; during the ACK/NACK period, we get some in-between voltage on the SDA line that can be seen with an oscilloscope (which my logic analyzer decoded as &lt;em&gt;high&lt;/em&gt;).&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Also, as you can see in the initialization code above, they toggle &lt;code&gt;PA9&lt;/code&gt; before starting the display initialization sequence. While this pin is probably meant to be hooked up to the display reset line, it isn&#39;t connected to anything on this board.&lt;/p&gt;
&lt;h3 id=&quot;better-code&quot; tabindex=&quot;-1&quot;&gt;Better code &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#better-code&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;After some searching in the excellent &lt;em&gt;u8g2&lt;/em&gt; sources (and a &lt;em&gt;lot&lt;/em&gt; of trial &amp;amp; error) I found the &lt;a href=&quot;https://github.com/olikraus/u8g2/blob/08f70e80b44aa0673d6fde9901566ad3360923f6/csrc/u8x8_d_ssd1306_128x32.c#L41&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;ssd1306 128x32 univision initialization&lt;/a&gt; which is pretty much the same as the one used on our soldering iron (plus display inversion/rotation settings which are done elsewhere in the stock firmware).&lt;/p&gt;
&lt;p&gt;The bit-banging driver was quite slow though on an STM32. One reason is that it uses Arduino I/O functions like &lt;code&gt;digitalWrite()&lt;/code&gt; which seem to be very slow in &lt;a href=&quot;https://github.com/stm32duino/Arduino_Core_STM32&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;stm32duino&lt;/a&gt; (there are some pin and port lookups which probably take a few cycles each time). Once I switched to using the &lt;a href=&quot;https://github.com/stm32duino/Arduino_Core_STM32/blob/987519a166179e3ada198364f79d388d05f75d3b/cores/arduino/stm32/digital_io.h#L57&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;low-level I/O functions&lt;/a&gt; though I was able to get from ~26 kHz to ~89 kHz I2C clock speed.&lt;/p&gt;
&lt;p&gt;With some further optimizations I finally got around 170 kHz. All together the whole display content is now rendered and transmitted in around 49 ms, which is well within the 100 ms PID controller and measurement period, so they don&#39;t interfere too much with each other (and due to the way I2C works, it shouldn&#39;t matter anyway if a clock cycle here or there is a bit longer).&lt;/p&gt;
&lt;h2 id=&quot;stock-firmware&quot; tabindex=&quot;-1&quot;&gt;Stock Firmware &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#stock-firmware&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I already showed in the previous post how to dump the stock firmware using an STLink USB stick.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#fn5&quot; id=&quot;fnref5&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt; For analysis in Ghidra I found &lt;a href=&quot;https://github.com/Diverto/ghidra-stm32f0&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;ghidra-stm32f0&lt;/a&gt; quite useful — it&#39;s a loader that sets up memory regions and register names so you don&#39;t see reads and writes with addresses like &lt;code&gt;0x04001000&lt;/code&gt; everywhere. (Be aware though that it labels many interrupt vector table entries that don&#39;t exist on our low-spec STM32F0 and are actually already code. Also it&#39;s missing a lot of timer and ADC peripherals that we&#39;re using but it&#39;s better than nothing.)&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-ghidra-main-MT8w6zvr_i-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Pseudocode excerpt from Ghidra&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-ghidra-main-MT8w6zvr_i-orig.png&quot; width=&quot;595&quot; height=&quot;803&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Stock firmware main function: hardware setup, welcome screen, main screen&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Initially I wanted to know how they&#39;re implementing their temperature controller but it wasn&#39;t that interesting anymore after the ported code from IronOS worked so well. There are always bits and pieces of display code or peripheral setup in between so the decompiled code is actually quite a slog to get through.&lt;/p&gt;
&lt;h2 id=&quot;custom-firmware&quot; tabindex=&quot;-1&quot;&gt;Custom Firmware &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#custom-firmware&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For my custom firmware I started with a &lt;a href=&quot;https://github.com/spezifisch/T12PenSolder/tree/11f69184440a310940f4706372f33aca9d6d7e08/firmware/custom_variants/T12F030&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;custom PlatformIO board&lt;/a&gt; that I derived from an existing one using another MCU of the F030 line. A &lt;a href=&quot;https://github.com/spezifisch/T12PenSolder/blob/11f69184440a310940f4706372f33aca9d6d7e08/firmware/boards/t12f030.json&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;board&lt;/a&gt; with this specific &lt;code&gt;STM32F030F6P6&lt;/code&gt;  with 32 kB Flash and 4 kB RAM didn&#39;t exist so I had to create one. Peripheral initialization was a bit tricky because contrary to my expectations, &lt;em&gt;stm32duino&lt;/em&gt; doesn&#39;t handle most of the clock setup of GPIOs/etc., so I ended up using &lt;a href=&quot;https://www.st.com/en/ecosystems/stm32cube.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;STM32Cube&lt;/a&gt; to generate setup code for &lt;a href=&quot;https://github.com/spezifisch/T12PenSolder/blob/11f69184440a310940f4706372f33aca9d6d7e08/firmware/custom_variants/T12F030/generic_clock.c&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;the system clocks&lt;/a&gt; and also the &lt;a href=&quot;https://github.com/spezifisch/T12PenSolder/blob/11f69184440a310940f4706372f33aca9d6d7e08/firmware/src/cube_init.cpp&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;ADC and GPIO setup&lt;/a&gt; for me.&lt;/p&gt;
&lt;p&gt;The core of my custom firmware is pretty simple: UI stuff (buttons + display) happens in the Arduino &lt;code&gt;loop()&lt;/code&gt; function every 100 ms:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker post-card-bgwhite&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-dia-loop-TYYfVepz1d-orig.svg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;flow diagram&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-dia-loop-TYYfVepz1d-orig.svg&quot; width=&quot;397&quot; height=&quot;397&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Main loop routine&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And soldering tip control is interrupt-driven by Timer 3 (&lt;code&gt;TIM3&lt;/code&gt;), which resets every 100 ms (that interrupt turns the heat on) and is using 3 &lt;em&gt;compare interrupts&lt;/em&gt; (1. heat off, 2. temperature measurement, 3. PID calculation):&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker post-card-bgwhite&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-dia-timer3-owzNOZ0uZs-orig.svg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;timing diagram&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-dia-timer3-owzNOZ0uZs-orig.svg&quot; width=&quot;1531&quot; height=&quot;425&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;TIM3 Interrupt Timing: Heating PWM, temperature measurement, PID update&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The time at which the heat turns off is given by the desired duty cycle that the PID routine calculates. As discussed before, after turning off the heat we wait a fixed time for the temperature reading to stabilize (currently 10 ms, but this could be made shorter), and then take the ADC temperature reading.&lt;/p&gt;
&lt;p&gt;In the current state the GUI is pretty simple. It only consists of one screen, you can press &lt;em&gt;SET&lt;/em&gt; to turn the soldering iron on or off, then it goes to stand-by after 60 s. And you can set the target temperature using the &lt;em&gt;+&lt;/em&gt; and &lt;em&gt;-&lt;/em&gt; buttons from 150 to 450 °C.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-heating-xsb48Wa5Ix-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Soldering iron display&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-heating-xsb48Wa5Ix-orig.jpeg&quot; width=&quot;4000&quot; height=&quot;3000&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Custom firmware on full power on the reassembled soldering iron, SWD wires peeking out.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Of course, as already noted, I was able to take the PID controller code from IronOS with a few little changes and it gives really good results without even tweaking the parameters (using the ones from the Pinecil V2).&lt;/p&gt;
&lt;h2 id=&quot;results-and-further-improvements&quot; tabindex=&quot;-1&quot;&gt;Results and further improvements &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#results-and-further-improvements&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&#39;m reasonably happy with the firmware at this point. It takes around 15 seconds to get from room temperature to 320 °C (and this, according to my limited measurement capabilities, really is 320 °C), while the stock firmware was way off with its temperature control, giving only around 280 °C when 320 °C are set. Temperature control during soldering is also rock solid.&lt;/p&gt;
&lt;p&gt;Additionally, it might be a good idea to connect the USB-PD controller to a resistor to select a USB voltage of 15 V in order to improve the life-time of the P-MOSFET which is currently operating at its limit &lt;code&gt;V_GSmax&lt;/code&gt; of 20 V. But this would come at the cost of longer heat-up times. Alternatively, you could replace that MOSFET with one of the few that withstand a &lt;code&gt;V_GS&lt;/code&gt; of 25 or even 30 V.&lt;/p&gt;
&lt;p&gt;Furthermore, cold junction compensation is currently being ignored and we can&#39;t probably do much without modifying the hardware. But we should at least be able to configure a fixed temperature calibration offset in the GUI like in IronOS.&lt;/p&gt;
&lt;p&gt;Finally, it would be nice to get a soldering iron temperature calibration tool to make a more trustworthy calibration curve.&lt;/p&gt;
&lt;h3 id=&quot;links&quot; tabindex=&quot;-1&quot;&gt;Links &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#links&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/posts/2023-07-26/psv3-soldering-iron-hack-part-1/&quot;&gt;Flashing Instructions&lt;/a&gt; (previous post)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/spezifisch/T12PenSolder&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Firmware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/spezifisch/T12PenSolder/blob/main/pen_solder_v3.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Schematic&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;h2 class=&quot;mt-3&quot; id=&quot;footnotes&quot; tabindex=&quot;-1&quot;&gt;Footnotes &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#footnotes&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;The bottom side of the PCB isn&#39;t very interesting so I&#39;m skipping it here. You can &lt;a href=&quot;https://github.com/spezifisch/T12PenSolder/blob/11f69184440a310940f4706372f33aca9d6d7e08/layout_bottom.jpg&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;look at it here&lt;/a&gt;. It pretty much only contains 3 buttons, the OLED display (we don&#39;t need to know more than that it&#39;s connected by I2C), and a small N-MOSFET driving the bigger P-MOSFET that switches the heat. &lt;a href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;The N-FET is needed because the microcontroller can&#39;t switch the gate of Q1 directly. &lt;a href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;IronOS implements another trick to get faster heat up times, but we don&#39;t (yet): They&#39;re using a lower 5 Hz PID rate and PWM frequency during the initial heating phase to get higher power output by allowing for a higher PWM duty cycle. The duty cycle can be higher because overhead of the time needed for temperature measurement to settle is constant (see next section). The trade-off for this higher output power is less accurate temperature control. &lt;a href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;The proper way to do handle this would be for the microcontroller to either configure the pin as open-drain (instead of push-pull) or to switch the pin from &lt;em&gt;output&lt;/em&gt; to &lt;em&gt;input&lt;/em&gt;. The latter one is also useful if you actually want to know what the slave responded. &lt;a href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn5&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I haven&#39;t yet decided whether I want to publish that dump but if you&#39;re interested in (probably also shoddy?) soldering station firmware dumps, you can find some at &lt;a href=&quot;https://github.com/deividAlfa/stm32_soldering_iron_controller/tree/master/Original_FW&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;https://github.com/deividAlfa/stm32_soldering_iron_controller/tree/master/Original_FW&lt;/a&gt;. &lt;a href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/#fnref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
		</entry>
		
		<entry>
			<title>PSV3 Soldering Iron Hack - Part 1</title>
			<link href="https://spezifisch.codeberg.page/posts/2023-07-26/psv3-soldering-iron-hack-part-1/"/>
			<updated>2023-07-26T00:00:00Z</updated>
			<id>https://spezifisch.codeberg.page/posts/2023-07-26/psv3-soldering-iron-hack-part-1/</id>
			<content type="html">&lt;h2 id=&quot;intro&quot; tabindex=&quot;-1&quot;&gt;Intro &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-07-26/psv3-soldering-iron-hack-part-1/#intro&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are some good and inexpensive soldering irons available nowadays. Thanks to USB power supplies with Quick Charge 2.0/3.0 or Power Delivery support you basically only need a microcontroller for temperature control, a FET switching the heating element, a way to measure the soldering tip&#39;s temperature and a way to communicate with the USB supply.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-with-ts80p-4OSmQ9Bf0m-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Two soldering irons side by side&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-with-ts80p-4OSmQ9Bf0m-orig.jpeg&quot; width=&quot;3992&quot; height=&quot;1436&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;The one I call &#39;Pen Solder V3&#39; (top, cable attached for flashing) and a Miniware TS80P for comparison&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The cheapest ones&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/posts/2023-07-26/psv3-soldering-iron-hack-part-1/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; with a reasonable shipping time to Germany can be had for around 20€ (incl. taxes) on Aliexpress. Inspired by &lt;a href=&quot;https://hackaday.com/2023/03/07/hacking-a-e15-8051-based-portable-soldering-iron-with-custom-firmware/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;a Hackaday post&lt;/a&gt; about an iron containing &amp;quot;an STC8H3K62S2, [and] an 8051-based MCU running at a blistering 11 MHz&amp;quot; I sought to buy a similar looking device that was advertised like this:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-ali_product-Hx2n8Lrks9-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Aliexpress product page showing my soldering iron&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-ali_product-Hx2n8Lrks9-orig.jpeg&quot; width=&quot;1078&quot; height=&quot;569&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Article title: Portable T12 Electric Soldering Iron PD 65W DC 72W CNC Metal Body Temperature Adjustable Solder Welding Station Fast Heating&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Thanks to a sale coupon the total price including free shipping came down to &lt;strong&gt;18.46€&lt;/strong&gt;. Never mind that the article&#39;s description is a bit weird considering the two different shown wattages neither of which are achievable with the supplied soldering tip (&lt;em&gt;T12-B2&lt;/em&gt;) — it&#39;s an 8 Ohm tip so with 20V supply its maximum is actually 50W. Also I think by &lt;em&gt;DC 72W&lt;/em&gt; they might mean &lt;em&gt;when supplied through a DC barrel jack with more than 20V&lt;/em&gt; which other models have but this one doesn&#39;t.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-fwdescription-vx7Edp4aAa-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Soldering iron display with annotations&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-fwdescription-vx7Edp4aAa-orig.jpeg&quot; width=&quot;928&quot; height=&quot;522&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Main screen (and only screen) of the custom firmware&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; The hardware turned out to be different than expected from the Hackaday post (no 8051 but an STM32 clone) but after a bit of reverse engineering the PCB layout and parts of the original firmware I came up with a well working replacement firmware which performs quite similar to my &lt;em&gt;Miniware TS80P&lt;/em&gt; with &lt;a href=&quot;https://github.com/Ralim/IronOS&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;IronOS&lt;/a&gt; soldering-wise — The &amp;quot;secret&amp;quot; is that thanks to the power of Open Source I could adapt some of IronOS&#39; temperature measurement and controller code with settings from the &lt;a href=&quot;https://wiki.pine64.org/wiki/Pinecil&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Pinecil&lt;/a&gt; which uses similar TS100 soldering tips.&lt;/p&gt;
&lt;p&gt;My replacement firmware is available at: &lt;a href=&quot;https://github.com/spezifisch/T12PenSolder&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;https://github.com/spezifisch/T12PenSolder&lt;/a&gt; (GPLv3 license). Read on to see how you can flash it and, in the next post how this soldering iron works.&lt;/p&gt;
&lt;h2 id=&quot;flashing&quot; tabindex=&quot;-1&quot;&gt;Flashing &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-07-26/psv3-soldering-iron-hack-part-1/#flashing&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Okay, you have this soldering iron and want to use this firmware?&lt;/p&gt;
&lt;p&gt;The simplest way is to download a &lt;a href=&quot;https://github.com/spezifisch/T12PenSolder/releases/tag/v1.0&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;release build from my repository&lt;/a&gt; and flash it using an STLink adapter (the cheapest kind with a colourful aluminium case will suffice).&lt;/p&gt;
&lt;h3 id=&quot;opening-the-case&quot; tabindex=&quot;-1&quot;&gt;Opening the case &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-07-26/psv3-soldering-iron-hack-part-1/#opening-the-case&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To open the soldering iron you need to unscrew the tip and remove 2 Torx screws: one in the back holding the rear cap and one in the front under the silicone grip holding the front cap. Remove both caps.&lt;/p&gt;
&lt;p&gt;To get the PCB out you need to press two pins on opposing sides down at the same time and pull the USB-C side of the PCB out of the case. I found it easier to push the two pins down using the shown IC extractor tool:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-opening-14swUvJbYw-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Showing the front of the soldering iron and the mentioned pins&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-opening-14swUvJbYw-orig.jpeg&quot; width=&quot;4000&quot; height=&quot;2308&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;IC puller pointing at one of the mentioned pins&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3 id=&quot;connecting-swd&quot; tabindex=&quot;-1&quot;&gt;Connecting SWD &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-07-26/psv3-soldering-iron-hack-part-1/#connecting-swd&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Once you&#39;re in you need to connect some cables to the test pads labeled on the board: VSS, CLK, DAT. These correspond to GND, SWCLK, and SWDIO on your STLink adapter, respectively. Do not connect VDD (the 4th test pad). It&#39;s cleaner to supply the board in a more stable manner using an USB-C supply instead.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-swd-iTJvTUST6p-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Showing the SWD test pads below the MCU&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-swd-iTJvTUST6p-orig.jpeg&quot; width=&quot;893&quot; height=&quot;502&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;SWD pins&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Once that&#39;s hooked up you should make a backup of the stock firmware with:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;st-flash --hot-plug &lt;span class=&quot;token builtin class-name&quot;&gt;read&lt;/span&gt; t12-f0.bin 0x8000000 &lt;span class=&quot;token number&quot;&gt;32768&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The program &lt;code&gt;st-flash&lt;/code&gt; is available in &lt;a href=&quot;https://manpages.debian.org/bookworm/stlink-tools/st-flash.1.en.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;the stlink-tools Debian package&lt;/a&gt; or more generally &lt;a href=&quot;https://github.com/stlink-org/stlink&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;on Github&lt;/a&gt;. The flag &lt;code&gt;--hot-plug&lt;/code&gt; is needed here because the reset pin isn&#39;t connected to your adapter.&lt;/p&gt;
&lt;p&gt;Then flash the custom firmware with:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;st-flash --hot-plug &lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt; pensolderv3_v1.0.bin 0x8000000&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively you can build the firmware yourself by cloning &lt;a href=&quot;https://github.com/spezifisch/T12PenSolder&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;my repository&lt;/a&gt;, opening that directory with VSCode with &lt;a href=&quot;https://docs.platformio.org/en/latest/platforms/ststm32.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;PlatformIO&lt;/a&gt; installed, and just pressing Upload.&lt;/p&gt;
&lt;p&gt;Finally power cycle the iron and this screen should greet you if all went well:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-splash-EQCmKRxebg-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Soldering iron showing test &#39;Pen Solder V3 1.0&#39; and a github link&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-splash-EQCmKRxebg-orig.jpeg&quot; width=&quot;2156&quot; height=&quot;1156&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Custom firmware showing splash screen on startup&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;outro&quot; tabindex=&quot;-1&quot;&gt;Outro &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-07-26/psv3-soldering-iron-hack-part-1/#outro&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;That&#39;s all for this post. In the &lt;a href=&quot;https://spezifisch.codeberg.page/posts/2023-08-03/psv3-soldering-iron-hack-part-2/&quot;&gt;next part&lt;/a&gt; I will write a bit more about hardware details and the stock firmware. In the meantime have a look at my &lt;a href=&quot;https://github.com/spezifisch/T12PenSolder/blob/11f69184440a310940f4706372f33aca9d6d7e08/pen_solder_v3.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;reverse engineered schematic&lt;/a&gt; and its annotated PCB layout:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/solder-layout-Gu0HX3DsLr-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Photo of the PCB with highlighted and annotated traces&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/solder-layout-Gu0HX3DsLr-orig.jpeg&quot; width=&quot;2338&quot; height=&quot;365&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Annotated PCB (click to enlarge)&lt;/figcaption&gt;&lt;/figure&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;h2 class=&quot;mt-3&quot; id=&quot;footnotes&quot; tabindex=&quot;-1&quot;&gt;Footnotes &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-07-26/psv3-soldering-iron-hack-part-1/#footnotes&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Quick aside: Of course I can&#39;t write this without a short mention of the &lt;a href=&quot;https://pine64.com/product/pinecil-smart-mini-portable-soldering-iron/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Pine64 Pinecil&lt;/a&gt;. Its price is around 3x higher (40-60€, see below) but still inexpensive compared to something like the Ersa SMD soldering stations I&#39;m used to. The Pinecil V2 is listed for $26 but including shipping to Germany it&#39;s $38 with &amp;quot;custom tax and VAT not included&amp;quot;.&lt;/p&gt;
&lt;p&gt;So with an &lt;a href=&quot;https://www.zoll.de/EN/Private-individuals/Postal_consignments_internet_order/Shipments-from-a-non-EU-country/Duties-and-taxes/Assessment-of-taxes-and-duties/assessment-of-taxes-and-duties_node.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;import VAT of 19%&lt;/a&gt; it&#39;s around 41€ and you need to drive to your customs office or pay Deutsche Post service fees if they handle the import taxes. So let&#39;s say it comes out to around 45€ and possibly a road trip to customs. Another option would be a local German reseller where I saw it for 58€ incl. shipping.&lt;/p&gt;
&lt;p&gt;Another aside: I&#39;m very happy with the Miniware TS80P (the other soldering iron you see in the photo). It&#39;s handy, light, and awesome for SMD work but its tips are pretty expensive (~20€) compared to the cheapest T12 tips (~4€). &lt;a href=&quot;https://spezifisch.codeberg.page/posts/2023-07-26/psv3-soldering-iron-hack-part-1/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
		</entry>
		
		<entry>
			<title>GT-ACQI-22WO digital alarm clock hack</title>
			<link href="https://spezifisch.codeberg.page/posts/2023-06-20/gt-acqi-22wo-digital-alarm-clock-hack/"/>
			<updated>2023-06-20T00:00:00Z</updated>
			<id>https://spezifisch.codeberg.page/posts/2023-06-20/gt-acqi-22wo-digital-alarm-clock-hack/</id>
			<content type="html">&lt;p&gt;Nowadays when opening a cheap clock you will probably be greeted by the dreaded epoxy blob with a lot of traces going from it to the display or LEDs.
Thankfully the designers of the Globaltronics GT-ACQI-22WO used a discrete display controller and no epoxy at all, so we have an easy starting point for our soon to be ESP32-enhanced clock! (As soon as I saw this, I went back to the store and bought the other one they had left in the clearance sale.)&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-breadboard-qM3yYH5O8y-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;A black clock display with white 7-segment LEDs showing 19:35 and 27°C summer temperatures next to a breadboard.&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-breadboard-qM3yYH5O8y-orig.jpeg&quot; width=&quot;4000&quot; height=&quot;3000&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Clock display attached to ESP32 board, RTC and temperature/humidity sensor&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;display&quot; tabindex=&quot;-1&quot;&gt;Display &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-06-20/gt-acqi-22wo-digital-alarm-clock-hack/#display&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The display board named &lt;code&gt;DIY-1941LED&lt;/code&gt; pretty much contains the whole clock: The main PCB has white LEDs on its front-facing side which
are separated by plastic dividers. They shine through a mask to form the displayed symbols and segments.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-display-mask-2amvSUQjoL-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;All display segments light up at the same time showing PM, alarm, snooze, etc. symbols&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-display-mask-2amvSUQjoL-orig.jpeg&quot; width=&quot;2808&quot; height=&quot;750&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Here I shone a flashlight through the mask&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The back side of the main PCB has an unidentified sanded off microcontroller, a display controller (AIP1640),
a passive buzzer, a coin cell holder to keep the RTC running, and connections from the power supply (flex cable socket) and to the button PCB (white/red cables).&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-displaypcb-ic-qbAKRGu3y0-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;A PCB with white solder resist on a blue mat&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-displaypcb-ic-qbAKRGu3y0-orig.jpeg&quot; width=&quot;4000&quot; height=&quot;3000&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Display PCB (from left to right): Buzzer, CR2032 cell holder, display controller, 32 kHz crystal, unidentified microcontroller, display/power supply connector&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;All the LEDs (except one) are connected to the display controller, which is a &lt;code&gt;Wuxi I-core Elec AiP1640&lt;/code&gt; (&lt;a href=&quot;https://www.velleman.eu/downloads/29/infosheets/aip1640_datasheet.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;English&lt;/a&gt;/&lt;a href=&quot;https://datasheet.lcsc.com/lcsc/1810241443_Wuxi-I-core-Elec-AiP1640_C82650.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Chinese datasheet&lt;/a&gt;).
It can control up to 16 grids with 8 LEDs each (7 segments + 1 decimal dot) - on this board only the first 11 grids are used.&lt;/p&gt;
&lt;p&gt;It has an SPI interface and luckily I read somewhere that it&#39;s compatible with the &lt;a href=&quot;https://dl.icdst.org/pdfs/files3/1a3723ca081e8824039eb846c3e42050.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;TM1640&lt;/a&gt; which is pretty common and supported in some libraries. This was almost plug &amp;amp; play.
By going through all the segments and dots in sequence (controlled by an ESP32) I got this mapping between grid/segments and the display:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-displaypcb-led-2tlb9K2mz3-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;A composite image showing the display&#39;s front with a lot of white 0603 LEDs and a sheet of paper showing which grids on the display controller they are connected to&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-displaypcb-led-2tlb9K2mz3-orig.jpeg&quot; width=&quot;3153&quot; height=&quot;2365&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Display PCB front. Labels corresponding to TM1640 datasheet. Within a &#39;grid&#39; the segments are in the usual order for 7-segment displays except for the segments marked above. (Bolt LED not shown on paper.)&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;I mentioned one LED before that isn&#39;t connected to the display controller. This is the status indicator of the wireless charging IC,
which is a bolt symbol that is located below the &amp;quot;zZ&amp;quot; snooze indicator. The bolt LED is connected between Ground and the middle pin (labelled &amp;quot;LED&amp;quot;) of the 5 pin flex cable that goes to the power supply board. Its 1 kΩ series resistor sits on the power board and I think it&#39;s supplied by the charging IC&#39;s internal (around) 5 V regulator.&lt;/p&gt;
&lt;p&gt;One notable mention is the transistor which drives the passive buzzer, located below the microcontroller. This means we can just hook it up directly to our ESP32.&lt;/p&gt;
&lt;p&gt;Another less notable mention for me is the input for a thermistor because I will not be using it. Anyway the thermistor&#39;s 2 pins are connected with the 5 pin flex cable,
go through a voltage divider and to the microcontroller.&lt;/p&gt;
&lt;h2 id=&quot;buttons&quot; tabindex=&quot;-1&quot;&gt;Buttons &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-06-20/gt-acqi-22wo-digital-alarm-clock-hack/#buttons&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The button PCB with the label &lt;code&gt;DIY-1941K&lt;/code&gt; contains 6 push buttons which are arranged into a small scan matrix. I traced the connections of the PCB
from photos and this is what I came up with:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-button-traces-n3dfSJWzul-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;An elongated green PCB with 6 buttons on it&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-button-traces-n3dfSJWzul-orig.jpeg&quot; width=&quot;2492&quot; height=&quot;662&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Button PCB with crudely hand-drawn markings. Lines with same color are connected.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Which leads to the following schematic:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-button-sch-OBJlS18Lnb-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;See caption&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-button-sch-OBJlS18Lnb-orig.png&quot; width=&quot;614&quot; height=&quot;553&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Button scan matrix schematic&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;power-supply&quot; tabindex=&quot;-1&quot;&gt;Power Supply &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-06-20/gt-acqi-22wo-digital-alarm-clock-hack/#power-supply&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The power supply board has labels &lt;code&gt;DIY-1941C&lt;/code&gt;/&lt;code&gt;LZX16-1001 V1.1&lt;/code&gt; and does multiple jobs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A thermistor is located at the back of the clock which is connected by two wires to the supply board.&lt;/li&gt;
&lt;li&gt;On the back it has connections for a USB-C power input and a USB-A (5V/2A) power output.&lt;/li&gt;
&lt;li&gt;It controls the Qi wireless charging coil near the top of the clock.&lt;/li&gt;
&lt;li&gt;It connects to the display&#39;s main board with a 5 pin flex cable.&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-power-1-p9MV16i2Ql-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;A green PCB with some big capacitors and an IC in the middle&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-power-1-p9MV16i2Ql-orig.jpeg&quot; width=&quot;1983&quot; height=&quot;2004&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Power Supply PCB while still connected to its big coil inside the case.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The easiest part are the 2 pins of the thermistor which are simply routed through to 2 pins of the display connector.
These pins are labelled &lt;code&gt;RT+/-&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The USB-C&#39;s VBUS line goes to the wireless charging IC and to a switching regulator. I think there is some USB Power Delivery thing
going on here where the input voltage isn&#39;t always 5V. The D+/D- data lines are connected to the IC but I didn&#39;t look into that further.&lt;/p&gt;
&lt;p&gt;The switching regulator puts out 5 V which go to the USB-A socket and to the display connector.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-power-2-nHOqHlbmBy-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Similar image to the previous one but with labels&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-power-2-nHOqHlbmBy-orig.jpeg&quot; width=&quot;2940&quot; height=&quot;2205&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Labelled power supply board. The bottom edge is located at the back of the clock.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Most of the rest of the PCB is dedicated to the wireless charging feature around an &lt;code&gt;Injoinic IP6806&lt;/code&gt; (&lt;a href=&quot;http://www.injoinic.com/wwwroot/uploads/files/20200214/9b084a8f3aa8acb9fbe63764b45054f2.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;datasheet&lt;/a&gt;). It looks pretty similar to the &lt;em&gt;Typical Application Schematic&lt;/em&gt; in section 6 of the datasheet. Except for the 3 possible LED outputs of which only &lt;code&gt;LED1&lt;/code&gt; is used which goes to the display connector as we already &lt;a href=&quot;https://spezifisch.codeberg.page/posts/2023-06-20/gt-acqi-22wo-digital-alarm-clock-hack/#display&quot;&gt;saw above&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Side note: The IP6806 has a thermal shutdown feature (datasheet, p. 8) using an NTC. But they put the NTC next to the display connector
on the PCB in a seemingly useless position. I would have assumed that it should be measuring the parts with the most power losses like probably the H bridge in the charging IC or the charging coil.&lt;/p&gt;
&lt;h2 id=&quot;hardware-modifications&quot; tabindex=&quot;-1&quot;&gt;Hardware Modifications &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-06-20/gt-acqi-22wo-digital-alarm-clock-hack/#hardware-modifications&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The goal of my modifications is to control the display from a custom ESP32 board so I can get features like NTP time synchronization and
better display modes (e.g. night mode, events from and temperature/humidity reporting to HomeAssistant).&lt;/p&gt;
&lt;p&gt;Also I want to remove wireless charging because I don&#39;t need it and in fact think it&#39;s quite annoying that the bolt symbol LED keeps blinking if
I put something on top of the clock because of the foreign object detection.&lt;/p&gt;
&lt;h3 id=&quot;display-board-mods&quot; tabindex=&quot;-1&quot;&gt;Display Board Mods &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-06-20/gt-acqi-22wo-digital-alarm-clock-hack/#display-board-mods&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-display-mods-2-vIkKa3TvGY-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Some orange cables are now connected to various parts of the display board&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-display-mods-2-vIkKa3TvGY-orig.jpeg&quot; width=&quot;3308&quot; height=&quot;2481&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Modified display PCB with attached cables going to my ESP board (click to make larger)&lt;/figcaption&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;I unsoldered the nameless microcontroller to get access to the two SPI pins (&lt;code&gt;DIN/SCLK&lt;/code&gt;) of the display controller and the buzzer driver. Those signals now go through a cable to my ESP32 board.&lt;/li&gt;
&lt;li&gt;I also connected cables to Ground and the anode of the bolt LED.&lt;/li&gt;
&lt;li&gt;I salvaged the 32 kHz crystal and the coin cell holder.&lt;/li&gt;
&lt;li&gt;I unsoldered the button cables, extended them and put a connector on it for my board.&lt;/li&gt;
&lt;li&gt;Also after taking the photos I shortened the legs of the two electrolytic capacitors.&lt;/li&gt;
&lt;li&gt;Note that while I attached DIN, SCLK and BUZ to the microcontroller&#39;s pads I later noticed that you can more easily use test pads instead which are marked in the image above.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-display-mods-1-ywfCHEucRv-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Showing the whole display assembly with two attached bundles of custom cables.&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-display-mods-1-ywfCHEucRv-orig.jpeg&quot; width=&quot;4000&quot; height=&quot;3000&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Display assembly with crimped connectors.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3 id=&quot;power-supply-board-mods&quot; tabindex=&quot;-1&quot;&gt;Power Supply Board Mods &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-06-20/gt-acqi-22wo-digital-alarm-clock-hack/#power-supply-board-mods&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-power-mod-74TOxtm1Q7-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;The power supply has also been graced by orange cables.&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-power-mod-74TOxtm1Q7-orig.jpeg&quot; width=&quot;3132&quot; height=&quot;2349&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Power supply modifications.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;I removed the big coil and capacitor so I have more space for my ESP32 board. I hoped that the &lt;code&gt;IP6806&lt;/code&gt; would detect the open lines and shut down but probing with an oscilloscope I found that it still puts a 100-something kHz square signal on the coil pads.&lt;/li&gt;
&lt;li&gt;Thus not wanting to create unnecessary RF emissions I unsoldered the 1206-sized ferrite to cut power to the charging IC. The ferrite sits between USB-C VBUS and the IC and removing it doesn&#39;t affect the 5V switching regulator which powers our display and the USB-A port.&lt;/li&gt;
&lt;li&gt;With the &lt;code&gt;IP6806&lt;/code&gt; now completely disabled I scratched the solder resist off the USB-C connector&#39;s D+/D- traces and soldered tiny 32 AWG wires to them which go to the USB-UART converter on my ESP32 board. To my surprise this just worked and now I can flash the ESP32 using the USB-C socket.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here a close-up of the mods:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-power-mod-2-GfSy4omhcz-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Some markings showing which parts on the PCB need to be changed.&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-power-mod-2-GfSy4omhcz-orig.jpeg&quot; width=&quot;1747&quot; height=&quot;1310&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Power supply modifications, close-up.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;esp32-board&quot; tabindex=&quot;-1&quot;&gt;ESP32 Board &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-06-20/gt-acqi-22wo-digital-alarm-clock-hack/#esp32-board&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here is the schematic of my perfboard:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-espboard-sch-x8KvYX3qIs-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;See caption&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-espboard-sch-x8KvYX3qIs-orig.png&quot; width=&quot;1448&quot; height=&quot;834&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;ESP32 perfboard schematic&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The board looks like this:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-espboard-wiring-nFqHIRymDC-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;See caption&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-espboard-wiring-nFqHIRymDC-orig.jpeg&quot; width=&quot;4000&quot; height=&quot;3000&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Power supply (left), ESP board (right), and display unit (above). You can see the thin twisted orange cable between supply and ESP board for USB.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;It consists of a bunch of standard components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.az-delivery.de/en/products/esp32-developmentboard&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;ESP32 board with CP2102 USB-serial IC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.aliexpress.com/item/1005001621707646.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;DS3231&lt;/a&gt; RTC with I2C. &lt;strong&gt;Note&lt;/strong&gt; that you&#39;ll have to &lt;a href=&quot;https://lastminuteengineers.com/ds3231-rtc-arduino-tutorial/#technical-specifications&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;remove a resistor when using this board with a non-rechargeable coin cell&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.aliexpress.com/item/1005001621672964.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;AHT21&lt;/a&gt; temperature/humidity sensor with I2C.&lt;/li&gt;
&lt;li&gt;1 kΩ resistor for our special bolt LED&lt;/li&gt;
&lt;li&gt;10-µF-or-so electrolytic capacitor&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can find KiCAD schematics in &lt;a href=&quot;https://github.com/spezifisch/esp32-acqi-clock/tree/main/espboard&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;my repository&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-espboard-attached-E2TSuIbADy-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;A U-shaped plastic bracket next to a perfboard, connected by a small black screw&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-espboard-attached-E2TSuIbADy-orig.jpeg&quot; width=&quot;4000&quot; height=&quot;3000&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;ESP perfboard attached to plastic frame.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;I screwed the perfboard to the plastic frame holding the power supply board using a self-tapping M2 screw.
This is already pretty stable but for good measure I put in some foam to rest the right side of the board on.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-espboard-inside-wgl4HaaT0F-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;A look inside the opened clock case in plastic with wood optic&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-espboard-inside-wgl4HaaT0F-orig.jpeg&quot; width=&quot;4000&quot; height=&quot;3000&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;The power supply board and ESP board are now installed inside the case.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The black plastic frame looks a bit twisted on the picture but it&#39;s actually held in place really solidly through the round pin which creates a bridge to the display unit when put together.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-temperature-S3bzyR2WcY-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;A look inside the opened case with a small blue board attached to the back&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-temperature-S3bzyR2WcY-orig.jpeg&quot; width=&quot;3000&quot; height=&quot;2250&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;AHT21 temperature/humidity sensor board is sitting in the back of the case. I taped the wires of the stock thermistors to the side.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;For the temperature/humidity sensor I made a small cutout at the back so it touches outside air.
That board is also attached using a self-tapping M2 screw in an existing hole.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-back-bSwlmK3rxj-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Backside of clock showing USB-C and USB-A sockets and screw holes.&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-back-bSwlmK3rxj-orig.jpeg&quot; width=&quot;4000&quot; height=&quot;3000&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Backside of the clock. Note the small square cutout with the AHT21 sensor.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;software&quot; tabindex=&quot;-1&quot;&gt;Software &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-06-20/gt-acqi-22wo-digital-alarm-clock-hack/#software&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I used ESPHome for this project to simplify integration into HomeAssistant. realthk &lt;a href=&quot;https://github.com/esphome/feature-requests/issues/1417&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;noted in an issue&lt;/a&gt; that he implemented &lt;a href=&quot;https://github.com/realthk/esphome_tm1640&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;TM1640 support&lt;/a&gt; based on &lt;a href=&quot;https://esphome.io/components/display/tm1637.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;ESPHome&#39;s TM1637 code&lt;/a&gt; and it works pretty well with the AiP1640 on this clock.&lt;/p&gt;
&lt;p&gt;Some special handling for all the symbols and pseudo-segments on the display needed to be added.
The rest is pretty much writing an ESPHome configuration.&lt;/p&gt;
&lt;p&gt;Source code of my project can be found at: &lt;a href=&quot;https://github.com/spezifisch/esp32-acqi-clock&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;https://github.com/spezifisch/esp32-acqi-clock&lt;/a&gt;.
To build and flash it you can use &lt;a href=&quot;https://esphome.io/guides/getting_started_command_line.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;ESPHome&#39;s build instructions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The first things I added was playing the mario theme using the &lt;a href=&quot;https://esphome.io/components/rtttl.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;RTTTL module&lt;/a&gt; of ESPHome
and add a very low light night mode for the clock without blinking dots:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-night-wZcS4GEWBI-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-night-wZcS4GEWBI-orig.jpeg&quot; width=&quot;4000&quot; height=&quot;3000&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;First version of distraction-free night mode. The display controller actually has 4 brightness levels. A higher brightness is shown here.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And of course a mode to show humidity which we can now measure, too:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/acqi-hum-H34JMcNflQ-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/acqi-hum-H34JMcNflQ-orig.jpeg&quot; width=&quot;4000&quot; height=&quot;3000&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Mode showing humidity (in %RH) instead of day and month.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The software is still work in progress but basic clock display works and I can use it on my night stand.
My next steps will be modding the other clock and I think it should be possible to use the ESP32&#39;s hardware SPI instead of the bit-banging implementation in the current TM1640 module.&lt;/p&gt;
</content>
		</entry>
		
		<entry>
			<title>Pokemon Go Plus autocatcher DIY</title>
			<link href="https://spezifisch.codeberg.page/posts/2023-04-03/pokemon-go-plus-autocatcher-diy/"/>
			<updated>2023-04-03T00:00:00Z</updated>
			<id>https://spezifisch.codeberg.page/posts/2023-04-03/pokemon-go-plus-autocatcher-diy/</id>
			<content type="html">&lt;p&gt;This is a post about the software and hardware side of my recent project, building a mobile device that works like a Pokemon Go Plus with extra features.
It is based on an &lt;a href=&quot;https://github.com/yohanes/pgpemu&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Yohanes&#39; ESP32 implementation&lt;/a&gt; of its protocol which uses the results of the reverse engineering efforts done by several people since 2017.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/pgpemu-shot-closed-green-dM3CUiu9d_-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;A 3D printed gadget with translucent plastic illuminated with a green LED&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/pgpemu-shot-closed-green-dM3CUiu9d_-orig.jpeg&quot; width=&quot;4000&quot; height=&quot;3000&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;A Pokemon was just caught as indicated by the green LED&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; &lt;a href=&quot;https://github.com/spezifisch/pgpemu&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;My fork of pgpemu&lt;/a&gt; is released under &lt;a href=&quot;https://spdx.org/licenses/BSD-3-Clause.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;BSD-3-Clause&lt;/a&gt; license. My 3D printable case is available at my &lt;a href=&quot;https://github.com/spezifisch/pgpemu-case&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;pgpemu-case repository&lt;/a&gt; with its FreeCAD sources under &lt;a href=&quot;https://creativecommons.org/licenses/by-nc-sa/4.0/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;CC BY-NC-SA 4.0&lt;/a&gt;. This post is based on my READMEs for those repositories.&lt;/p&gt;
&lt;h2 id=&quot;a-short-history&quot; tabindex=&quot;-1&quot;&gt;A Short History &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-04-03/pokemon-go-plus-autocatcher-diy/#a-short-history&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Pokemon Go Plus (&lt;a href=&quot;https://support.pokemon.com/hc/en-us/articles/360000954814-About-Pok%C3%A9mon-GO-Plus&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;support page&lt;/a&gt;) was released in 2016 and its selling point was basically that you can play a mobile game without looking at a mobile phone. It is a low power Bluetooth LE device that operates from a coin cell.
You pair it with your Pokemon Go app on your phone and then it vibrates and blinks when an in-game action can be taken. You then have to react manually by pressing a button.&lt;/p&gt;
&lt;p&gt;To avoid having to press that button some people built &lt;a href=&quot;https://www.thingiverse.com/thing:3045278&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;clips that just always press it&lt;/a&gt; or just tied a coin with some rubber band to the device to achieve the same. Others &lt;a href=&quot;https://www.youtube.com/watch?v=S1vM-SUegzM&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;wired the vibration motor output to the button&lt;/a&gt; so it presses itself automatically.&lt;/p&gt;
&lt;p&gt;Meanwhile compatible devices cropped up like Datel/Codejunkies&#39; &lt;a href=&quot;https://www.codejunkies.com/Products/Go-tcha-Classic__EF001336V.aspx&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Go-tcha series&lt;/a&gt; which can automatically simulate a button press. They emulate a Go Plus on their own custom hardware with extra features.&lt;/p&gt;
&lt;p&gt;Also notable are a range of clone devices like the &lt;a href=&quot;https://www.amazon.de/dp/B07T2H7WV8&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Pocket Egg/Pair&lt;/a&gt; which contain the same Bluetooth SoC as the original Go Plus (twice for the &lt;em&gt;Pair&lt;/em&gt;) with the same firmware as an original. The pins which are used for button and LEDs in the original are wired to a microcontroller which then automatically presses the button for you.&lt;/p&gt;
&lt;h2 id=&quot;reverse-engineering&quot; tabindex=&quot;-1&quot;&gt;Reverse Engineering &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-04-03/pokemon-go-plus-autocatcher-diy/#reverse-engineering&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Public reverse engineering was done by &lt;a href=&quot;https://reddit.com/r/pokemongodev/comments/5ovj04/pokemon_go_plus_reverse_engineering_write_up/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;BobThePigeon_&lt;/a&gt; (2017), &lt;a href=&quot;https://github.com/yohanes&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Yohanes Nugroho&lt;/a&gt; (2018: &lt;a href=&quot;https://tinyhack.com/2018/11/21/reverse-engineering-pokemon-go-plus/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;one&lt;/a&gt;, &lt;a href=&quot;https://tinyhack.com/2019/05/01/reverse-engineering-pokemon-go-plus-part-2-ota-signature-bypass/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;two&lt;/a&gt;), and &lt;a href=&quot;https://coderjesus.com/blog/pgp-suota/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Jesus Bamford&lt;/a&gt; (2019) who also implemented &lt;a href=&quot;https://github.com/Jesus805/Suota-Go-Plus&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;an OTA bootloader exploit&lt;/a&gt; (2019) to dump a Go Plus without needing to modify its hardware.&lt;/p&gt;
&lt;p&gt;The bottom line is that the Bluetooth connection between Pokemon Go Plus (the wearable) and Pokemon Go (the app) is authenticated with a handshake protocol using mutual challenges and responses based on shared secrets. Due to the mentioned reverse engineering efforts the protocol for this is known to the extent that you can implement it on your own hardware using &lt;a href=&quot;https://github.com/yohanes/pgpemu&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;pgpemu&lt;/a&gt; which my project is based on.&lt;/p&gt;
&lt;p&gt;However, it is currently not known if you can generate valid secrets yourself from scratch. That&#39;s why to my knowledge every clone (like Pocket Eggs and fake Go Pluses) and every emulator (like Go-tchas and pgpemu) uses secrets dumped from a genuine Go Plus. Since the secrets seem to be tied to the wearable&#39;s MAC address, the clones and emulators have a limited set of MAC addresses or even only one each with known dumped secrets. This sharing of MAC addresses &lt;a href=&quot;https://reddit.com/r/pokemongodev/comments/8544ol/my_1_gotcha_is_connecting_and_spinning_catching_2/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;leads to some interesting issues&lt;/a&gt; as Yohanes already noted in his article.&lt;/p&gt;
&lt;p&gt;Note that if the secrets can&#39;t be generated from the MAC address alone the app needs to get it from somewhere. If that is done using the usual RPC mechanism in Pokemon Go, it should be contained in their &lt;a href=&quot;https://github.com/Furtif/POGOProtos&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;protobuf files&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I haven&#39;t disassembled any Go-tchas yet but all the hardware clones I examined shared the same MAC address but different ones for each product line.&lt;/p&gt;
&lt;p&gt;It will be interesting to see whether the newly announced &lt;a href=&quot;https://www.pokemongoplusplus.com/en/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Pokemon Go Plus+&lt;/a&gt; will use a new protocol. The current security mechanisms worked well enough to deter widely available/usable Open Source emulators long enough and considering that Datel will probably crack it quickly anyway, there don&#39;t &lt;em&gt;need&lt;/em&gt; to be a lot of changes.
Curiously enough the &lt;a href=&quot;https://i.imgur.com/7oWjMNu.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;current protocol&lt;/a&gt; already has enough reserved bits left in their button messages to implement the new functions like throwing another type of Pokeball in a backward compatible manner.&lt;/p&gt;
&lt;h2 id=&quot;software&quot; tabindex=&quot;-1&quot;&gt;Software &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-04-03/pokemon-go-plus-autocatcher-diy/#software&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Starting from Yohanes&#39; latest pgpemu commit of 2020 I reworked the code quite a bit to make it more easily maintainable.
My goal was to make the emulated device more realistic and harder to detect. You can read about the details in &lt;a href=&quot;https://github.com/spezifisch/pgpemu/blob/master/README.md&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;my README&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The most notable features are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;connect up to 4 devices simultaneously&lt;/li&gt;
&lt;li&gt;parse LED patterns to auto-press the button only when needed&lt;/li&gt;
&lt;li&gt;button press delay and duration are randomized&lt;/li&gt;
&lt;li&gt;the handshake nonces are now randomized and saved per connected device&lt;/li&gt;
&lt;li&gt;serial menu to reconfigure autocatch/autospin/etc. on the fly&lt;/li&gt;
&lt;li&gt;secrets are saved in their own partition to make development easier&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some limitations still are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The battery reading is always the same value. I might add a real measurement of my battery voltage to address this.&lt;/li&gt;
&lt;li&gt;The ESP32 can only have one MAC address at a time. So you get problems connecting it to multiple accounts on the same phone at the same time in split screen or windowed mode.&lt;/li&gt;
&lt;li&gt;You need a Go Plus to dump the secrets to begin with.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Currently I&#39;m using the button to start Bluetooth advertising when I want to connect an additional phone.&lt;/p&gt;
&lt;p&gt;The RGB LED doesn&#39;t show the same patterns as a normal Go Plus but a simplified version which is less blinky:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;green - Pokemon was caught&lt;/li&gt;
&lt;li&gt;yellow - New species of Pokemon was caught&lt;/li&gt;
&lt;li&gt;pink - Pokemon fled&lt;/li&gt;
&lt;li&gt;blue - got items from Pokestop&lt;/li&gt;
&lt;li&gt;red - Pokemon box full or item bag full&lt;/li&gt;
&lt;li&gt;steady blue - Bluetooth is advertising&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;electronics&quot; tabindex=&quot;-1&quot;&gt;Electronics &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-04-03/pokemon-go-plus-autocatcher-diy/#electronics&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;esp32&quot; tabindex=&quot;-1&quot;&gt;ESP32 &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-04-03/pokemon-go-plus-autocatcher-diy/#esp32&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As ESP32 board I used a &lt;a href=&quot;https://www.azdelivery.de/en/collections/nodemcu/products/esp32-developmentboard&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Devkit C compatible&lt;/a&gt; one I had lying around.
Another choice would have been something like the &lt;a href=&quot;https://www.dfrobot.com/product-1590.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;DFRobot FireBeetle&lt;/a&gt; which already integrates a LiPo charger or a &lt;a href=&quot;https://www.dfrobot.com/product-1798.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;DFRobot Beetle&lt;/a&gt; which is way more compact.&lt;/p&gt;
&lt;p&gt;Note that the Devkit-like boards sometimes have &lt;a href=&quot;https://arduino.stackexchange.com/a/76692&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;brownout issues&lt;/a&gt; due to their capacitor placement and count. I added a 0805 10µF ceramic capacitor directly to the ESP32 module like mentioned in the linked post.&lt;/p&gt;
&lt;h3 id=&quot;lipo-charger-and-battery&quot; tabindex=&quot;-1&quot;&gt;LiPo Charger and Battery &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-04-03/pokemon-go-plus-autocatcher-diy/#lipo-charger-and-battery&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I used a &lt;a href=&quot;https://dlnmh9ip6v2uc.cloudfront.net/datasheets/Prototyping/TP4056.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;TP4056&lt;/a&gt; charger module with DW01A battery protection IC and a USB-C connector.&lt;/p&gt;
&lt;p&gt;Note that TP4056s are &lt;a href=&quot;https://www.best-microcontroller-projects.com/tp4056.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;a bit tricky to handle safely&lt;/a&gt;. In short, you need to disconnect any load (like the ESP32 board) when charging or it will potentially charge the battery forever, creating a fire hazard. That (and saving power) is what my toggle switch is for.&lt;/p&gt;
&lt;p&gt;Also note that the &lt;a href=&quot;https://www.best-microcontroller-projects.com/dw01a.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;DW01A is only providing overcurrent protection&lt;/a&gt; on this charger board.&lt;/p&gt;
&lt;p&gt;In order to still make it reasonably safe I chose a &lt;a href=&quot;https://www.aliexpress.com/item/1005003318811101.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;1800mAh battery with its own protection circuit&lt;/a&gt; which also provides overcharge and over-discharge protection I would otherwise not have safeguards against.&lt;/p&gt;
&lt;p&gt;Another choice for battery management would have been an &lt;a href=&quot;https://emariete.com/en/co2-meter-with-battery-well-done/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;MCP73871-based board as described in this article&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;supply&quot; tabindex=&quot;-1&quot;&gt;Supply &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-04-03/pokemon-go-plus-autocatcher-diy/#supply&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The ESP32 has an operating range from 3.0V - 3.6V (see &lt;a href=&quot;https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;datasheet p.46&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The LiPo battery that I chose has a safety circuit which ensures the voltage is &amp;gt;=3.0V and &amp;lt;=4.28V (see &lt;a href=&quot;https://ru.eemb.com/public/Download/Rechargeable-Lithium-Battery/Li-Polymer-Battery/Standard-Version/LP963450.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;battery datasheet section 6.1.0&lt;/a&gt;) but the TP4056 charger only charges until 4.263V anyway (see &lt;a href=&quot;https://dlnmh9ip6v2uc.cloudfront.net/datasheets/Prototyping/TP4056.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;TP4056 datasheet p.2&lt;/a&gt;). Also note that we&#39;re charging with 1A (the charger uses &lt;code&gt;R_prog = 1k2&lt;/code&gt; by default) and the battery supports this according to the datasheet.&lt;/p&gt;
&lt;p&gt;Now I considered generating the ESP32&#39;s voltage using either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a boost regulator: battery -&amp;gt; boost 5V -&amp;gt; 3.3V with LDO on the ESP32 board -&amp;gt; ESP32&lt;/li&gt;
&lt;li&gt;a modern LDO with 0.3V dropout voltage directly: battery -&amp;gt; LDO 3.3V -&amp;gt; ESP32&lt;/li&gt;
&lt;li&gt;a standard silicon diode to get the battery&#39;s range into the ESP32&#39;s range: battery -&amp;gt; diode with 0.7V forward voltage -&amp;gt; ESP32&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I went with the diode to reduce part count so I get between 2.3V and 3.5V which is safe for the ESP32 and it&#39;s working fine.&lt;/p&gt;
&lt;p&gt;Note: As explained above, you &lt;em&gt;can&#39;t&lt;/em&gt; have the device switched on while charging. However, you &lt;em&gt;can&lt;/em&gt; connect the ESP32&#39;s Micro USB while charging with the switch in the &lt;em&gt;off&lt;/em&gt; position. Looking at the &lt;a href=&quot;https://cdn.shopify.com/s/files/1/1509/1638/files/ESP-32_NodeMCU_Developmentboard_Schematic_korr.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;ESP32 board schematic&lt;/a&gt; the &lt;code&gt;AMS1117&lt;/code&gt; output would be connected with your diode&#39;s cathode so you won&#39;t get backward currents into the battery or USB port.&lt;/p&gt;
&lt;h2 id=&quot;case&quot; tabindex=&quot;-1&quot;&gt;Case &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-04-03/pokemon-go-plus-autocatcher-diy/#case&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At first I tried designing a case for my battery-powered gadget in Tinkercad. I did some light work with Tinkercad before and was amazed how well it actually works for a CAD running in your browser.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/pgpemu-old-6qpTgijeAH-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;3D model of a previous case in Tinkercad&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/pgpemu-old-6qpTgijeAH-orig.png&quot; width=&quot;1580&quot; height=&quot;1436&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;The previous case which I remixed in Tinkercad&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;But having used SolidWorks before I was quite confused after I looked up &lt;a href=&quot;https://www.youtube.com/watch?v=6C4AAsqmHAw&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;how cumbersome it is to just split a part into two&lt;/a&gt; and I still can&#39;t quite wrap my head around &lt;a href=&quot;https://www.youtube.com/watch?v=Ru742pUG_Bc&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;how to align different parts&lt;/a&gt; on the first try.&lt;/p&gt;
&lt;p&gt;Long story short, I went on using FreeCAD with the &lt;a href=&quot;https://github.com/Zolko-123/FreeCAD_Assembly4&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Assembly4&lt;/a&gt; addon which after a day of adjustment provided a very familiar experience.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/pgpemu-case-whole-PegpOsVBqQ-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;3D model of the gadget in FreeCAD&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/pgpemu-case-whole-PegpOsVBqQ-orig.png&quot; width=&quot;1327&quot; height=&quot;995&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;The device in FreeCAD&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3 id=&quot;assembly&quot; tabindex=&quot;-1&quot;&gt;Assembly &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-04-03/pokemon-go-plus-autocatcher-diy/#assembly&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;At the bottom the TP4056 is tied in place using a cable tie. There is a small stub above the USB-C port to keep it from going up. On the side with the cables I added a small block that keeps the charger from being pushed into the case.&lt;/p&gt;
&lt;p&gt;The battery is glued to the bottom of the case with a small piece of double-sided adhesive tape to keep it in place. I also added a small piece of foam above the battery (not pictured) and some isolating tape covering the pointy parts of the perfboard. There should be enough space for possible LiPo bloat.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/pgpemu-assembly-bottom-x4MYaFe_ar-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;assembly bottom&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/pgpemu-assembly-bottom-x4MYaFe_ar-orig.jpeg&quot; width=&quot;4000&quot; height=&quot;3000&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;The bottom layer with battery and charging board&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The perfboard rests on top of that on the small holders at the sides. I used 45° angles so the holders don&#39;t sag down when printing. This turned out quite well.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/pgpemu-assembly-top-86HpB1vj45-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;assembly top&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/pgpemu-assembly-top-86HpB1vj45-orig.jpeg&quot; width=&quot;4000&quot; height=&quot;3000&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;The top layer of the case with the perfboard&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The lid uses a clip mechanism that I reused from &lt;a href=&quot;https://www.thingiverse.com/thing:5361695&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;the design I based my previously mentioned old case on&lt;/a&gt;, so the gadget can be almost fully disassembled without tools (except for the cable tie around the charger). The lid pushes down tightly on the corners of the perfboard to keep it from moving up.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/pgpemu-shot-charging-NgeTLHR5W--orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;charging and glowing red with USB-C plugged in&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/pgpemu-shot-charging-NgeTLHR5W--orig.jpeg&quot; width=&quot;4000&quot; height=&quot;3000&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;The finished device, charging&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;I had to pay special attention to the thin wall around the Micro USB port when printing. The wall needed to be quite thin there so plugs can fit. But my 3D printer software decided to slice away this part &lt;a href=&quot;https://support.tiertime.com/hc/en-us/articles/360023393674-Printing-with-Thin-Wall-Option&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;because the wall was thinner than the extrusion width&lt;/a&gt; but the &amp;quot;thin wall&amp;quot; option there remedied this problem. From now on I&#39;ll make sure to look at the slicer&#39;s output before starting the print.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/pgpemu-shot-micro-K79Sqou8It-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Micro USB port&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/pgpemu-shot-micro-K79Sqou8It-orig.jpeg&quot; width=&quot;4000&quot; height=&quot;3000&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Closeup of the ESP32 development board&#39;s Micro USB port&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;There also was a recession around the charger&#39;s USB-C in my first print but I realized that it wasn&#39;t necessary because USB-C plugs seem to be longer leaving more space.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/pgpemu-shot-usbc-Hy4siSyMpT-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;USB-C port&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/pgpemu-shot-usbc-Hy4siSyMpT-orig.jpeg&quot; width=&quot;4000&quot; height=&quot;3000&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Closeup of the charger board&#39;s USB-C port&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And that&#39;s it! I can now throw this device into my backpack and run around playing a mobile game without having to play it. When Pokemon Go gets old for me I will just need to grind away the Pokeball shape on the lid.&lt;/p&gt;
</content>
		</entry>
		
		<entry>
			<title>Govee RGB Strip (H619A) Part 1 - Investigation</title>
			<link href="https://spezifisch.codeberg.page/posts/2023-03-13/govee-rgb-strip-h619a-part-1-investigation/"/>
			<updated>2023-03-13T00:00:00Z</updated>
			<id>https://spezifisch.codeberg.page/posts/2023-03-13/govee-rgb-strip-h619a-part-1-investigation/</id>
			<content type="html">&lt;p&gt;I recently got a Govee H619A RGB LED strip. It is 5 meters long and its LEDs are grouped into segments
which can be controlled individually. The main components are a controller box with 3 buttons, a 24V/1A wall wart, and the strip itself.
The strip is divided into 20 segments with 6 RGB LEDs each. And of course there is a mobile app for remote control.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/govee-controller-probed-ZFdAoycRri-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;a part of the RGB strip showing 6 LEDs shining white, and the opened controller box with 2 probes connected to it&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/govee-controller-probed-ZFdAoycRri-orig.jpeg&quot; width=&quot;3718&quot; height=&quot;2788&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;top: the first segment of the RGB strip with its IC (left) and 6 RGB LEDs showing #2d2d2d; bottom: logic analyzer probes on data output (and GND)&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; The RGB strip is controlled with the &lt;a href=&quot;https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;WS2812&lt;/a&gt; protocol using 5V logic level and 24V supply. The Bluetooth and WiFi hardware is cheap closed source stuff and quite boring.&lt;/p&gt;
&lt;h2 id=&quot;motivation&quot; tabindex=&quot;-1&quot;&gt;Motivation &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-03-13/govee-rgb-strip-h619a-part-1-investigation/#motivation&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You control the RGB strip with the app using Bluetooth (directly) or WiFi (through &lt;s&gt;cloud&lt;/s&gt; other people&#39;s servers).
But of course you have to create a Govee account first. Then you just enter your WiFi network key inside the app (thankfully optional though pushed onto you).
And then the thing - which also contains a microphone, did I mention? - is controllable from anywhere. Great! &lt;em&gt;What could go wrong?&lt;/em&gt;&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/govee-controller-closed-SLttToRjNo-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;a white box with 3 buttons and 2 cables, one going to the RGB strip&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/govee-controller-closed-SLttToRjNo-orig.jpeg&quot; width=&quot;4000&quot; height=&quot;3000&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;The target&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;In any case that controller board needs to go and be replaced by something like an ESP32 with a Free firmware.&lt;/p&gt;
&lt;p&gt;I then want to &lt;a href=&quot;https://community.home-assistant.io/t/govee-integration/228516&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;use it via HomeAssistant without the Govee Cloud API&lt;/a&gt; and as such without needing to trust some random company and hope that their servers are maintained forever.
Well, there is the &lt;a href=&quot;https://www.home-assistant.io/integrations/govee_ble/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Govee Bluetooth integration&lt;/a&gt; but it doesn&#39;t support their RGB strips
and I really don&#39;t want to go down that road working with their proprietary BLE high level protocol.&lt;/p&gt;
&lt;h2 id=&quot;internals&quot; tabindex=&quot;-1&quot;&gt;Internals &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-03-13/govee-rgb-strip-h619a-part-1-investigation/#internals&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The controller is easily opened as the back plate is just clipped in place:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/govee-controller-open--OxhbB96k_-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;the opened controller case reveals a PCB&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/govee-controller-open--OxhbB96k_-orig.jpeg&quot; width=&quot;2127&quot; height=&quot;1595&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Highlighted: Beken BK3269 Bluetooth SoC, Supply (goes to a barrel jack), Data output (goes to the RGB strip)&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The 24V supply is connected through a high-side shunt (around 0.1 - 0.2 Ohm as far as my multimeter can measure) to the RGB strip.
The ground line between supply and strip is connected directly.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/govee-bits-os-KO0i1g57dK-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;an oscilloscope screenshot showing a rectangular digital signal with about 5V level&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/govee-bits-os-KO0i1g57dK-orig.png&quot; width=&quot;640&quot; height=&quot;468&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Oscilloscope probe on the data output pin (to the RGB strip)&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Probing around with the oscilloscope the most action happens on the data output pin (labelled in the previous image).
The bit timing should be pretty easily recognized if you know about the WS2812 protocol. (I didn&#39;t.)&lt;/p&gt;
&lt;p&gt;Other than that there is some communication on the test pads labelled &lt;code&gt;RX&lt;/code&gt; and &lt;code&gt;TX&lt;/code&gt; between the Bluetooth and the WiFi module when you change the LED pattern using the app.&lt;/p&gt;
&lt;h2 id=&quot;rgb-strip-data-output&quot; tabindex=&quot;-1&quot;&gt;RGB strip data output &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-03-13/govee-rgb-strip-h619a-part-1-investigation/#rgb-strip-data-output&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I hooked my &lt;a href=&quot;http://sigrok.org/wiki/Fx2lafw&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;fx2lafw Logic Analyzer&lt;/a&gt; to the data pin and captured some communication. The signal is quite fast so you need a sample rate on the upper end of what the analyzer supports (16 MHz gives good results).&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/govee-la-frames-uUJFAbshXm-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;a signal trace showing some bursts of bits&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/govee-la-frames-uUJFAbshXm-orig.png&quot; width=&quot;1966&quot; height=&quot;326&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Data frame timing&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Every 20 ms there is a burst of bits. Occasionally there is a longer delay though.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/govee-la-bytes-uzEb2QrhMa-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;a signal trace showing a train of bits in 20 groups&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/govee-la-bytes-uzEb2QrhMa-orig.png&quot; width=&quot;2878&quot; height=&quot;322&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Data byte timing&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;I added the &lt;a href=&quot;https://github.com/vooon/sigrok-rgb_led_ws281x&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;WS281x protocol decoder in PulseView&lt;/a&gt; for the screenshot so you can already see the color values for the individual segments of the strip. Here all segments except for the 18th are off and the other one is showing a dimmed white.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/govee-la-bits-mdrRfg6XTo-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;a signal trace of 2 bits showing different duty cycles&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/govee-la-bits-mdrRfg6XTo-orig.png&quot; width=&quot;2196&quot; height=&quot;336&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Data bit timing&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;At this point I saw what looked like &lt;a href=&quot;https://en.wikipedia.org/wiki/On%E2%80%93off_keying&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;On-Off keying&lt;/a&gt; and it dawned on me that this might be the WS2812 (or rather WS281x, I guess) protocol so I looked it up and sure enough the bit timing matched within tolerance (0.8 µs high, 0.4 µs low; and 0.4 µs high, 1 µs low). And then I finally found its protocol decoder is already included in PulseView.&lt;/p&gt;
&lt;p&gt;Compare the measured timings to the &lt;a href=&quot;https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;WS2812B&lt;/a&gt; datasheet:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/govee-wsds-y5JML5Bmky-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;an excerpt from the WS2812B datasheet showing similar bit timing to the one mentioned&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/govee-wsds-y5JML5Bmky-orig.png&quot; width=&quot;1996&quot; height=&quot;606&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Bit timings from WS2812B datasheet&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Funnily enough, once I knew what protocol I was looking for I searched online for &amp;quot;govee ws2812&amp;quot; and I found &lt;a href=&quot;https://libreddit.eu.org/r/WLED/comments/ioc8t5/turns_out_those_govee_rgbic_lights_on_amazon_are/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;a Reddit thread from Sep. 2020 with photos of WS2811 ICs in a Govee light&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;side-note-bluetooth-soc&quot; tabindex=&quot;-1&quot;&gt;Side note: Bluetooth SoC &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-03-13/govee-rgb-strip-h619a-part-1-investigation/#side-note-bluetooth-soc&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Bluetooth SoC is a Beken Corporation BK3269. The nearest thing Beken have on their website is the product description of the &lt;a href=&quot;http://www.bekencorp.com/en/goods/detail/cid/27.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;BK3266&lt;/a&gt; which is described as a device for audio applications.&lt;/p&gt;
&lt;p&gt;It&#39;s connected to the microphone, the 3 front buttons, the RGB strip, and the WiFi module.&lt;/p&gt;
&lt;h2 id=&quot;side-note-wifi-module&quot; tabindex=&quot;-1&quot;&gt;Side note: WiFi module &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2023-03-13/govee-rgb-strip-h619a-part-1-investigation/#side-note-wifi-module&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The WiFi module seems to play only an auxiliary role. From a quick check with the oscilloscope it seems to be talking with the Bluetooth SoC using 3.3V level UART.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/govee-label-_ZdPwKhfzb-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;a label lasered into a white case showing model and regulatory information&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/govee-label-_ZdPwKhfzb-orig.jpeg&quot; width=&quot;989&quot; height=&quot;742&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Govee H619A, FCC ID: 2AQA6-H619E&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;According to the &lt;a href=&quot;https://fcc.report/FCC-ID/2AQA6-H619E/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;FCC documents&lt;/a&gt; the WiFi controller should be a Realtek RTL8720CF (&lt;a href=&quot;https://www.lcsc.com/product-detail/RF-Transceiver-ICs_Realtek-Semicon-RTL8720CF_C398721.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;datasheet available here&lt;/a&gt;) which is also shown in the internal photos in FCC document &lt;code&gt;5627324.pdf&lt;/code&gt; (p. 4) with the correct markings on the chip, containing the crab and all. But this doesn&#39;t match the markings on my model which are &lt;code&gt;W701 L3198I&lt;/code&gt;.&lt;/p&gt;
</content>
		</entry>
		
		<entry>
			<title>Extracting audio samples from Rave Generator 2 VST plugin</title>
			<link href="https://spezifisch.codeberg.page/posts/2022-11-01/extracting-audio-samples-from-rave-generator-2-vst-plugin/"/>
			<updated>2022-11-01T00:00:00Z</updated>
			<id>https://spezifisch.codeberg.page/posts/2022-11-01/extracting-audio-samples-from-rave-generator-2-vst-plugin/</id>
			<content type="html">&lt;p&gt;Rave Generator 2 is a widely used free VST plugin that&#39;s basically a sampler/rompler with a bunch of
included classic rave and hardcore samples. These instantly take you back to an era of dirty pitched
up samples from orchestra stabs and piano chords. The FAQ file already sets the scene:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Q: i like the sound of RaveGenerator but it aliases like hell, what can i do ?&lt;br /&gt;
A: enjoy the dirty sound of the 90&#39;s rave.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can get the plugin on the &lt;a href=&quot;https://blog.wavosaur.com/rave-generator-2-vst-audiounit-the-stab-machine-is-back-in-the-house/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Wavosaur blog&lt;/a&gt; for Linux/Windows/Mac.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/ravegenerator2-qfusfTCnU3-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;a screenshot of the Rave Generator window showing a Juno Hoover sample waveform&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/ravegenerator2-qfusfTCnU3-orig.png&quot; width=&quot;820&quot; height=&quot;536&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Who needs an Alpha Juno if you have this?&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;motivation&quot; tabindex=&quot;-1&quot;&gt;Motivation &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2022-11-01/extracting-audio-samples-from-rave-generator-2-vst-plugin/#motivation&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One problem with the Linux version is that it requires Qt4 libraries which are so old that they&#39;re
not included anymore in most distros. In case of Debian Bullseye you can &lt;a href=&quot;https://discourse.ardour.org/t/rave-generator-2-vst-no-gui-amd-graphics/107099/3&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;work around this by copying them from old packages&lt;/a&gt; or build them yourself.&lt;/p&gt;
&lt;p&gt;But if you&#39;re using &lt;a href=&quot;https://github.com/flathub/com.bitwig.BitwigStudio&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Bitwig&#39;s Flatpak version&lt;/a&gt;
you&#39;re out of luck because you can&#39;t easily install Qt4 (if you don&#39;t want to build a custom Flatpak package of Bitwig containing Qt4).&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/ravegenerator-bitwig-oUYKACriJK-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;a screenshot of the plugin in Bitwig showing a list of presets&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/ravegenerator-bitwig-oUYKACriJK-orig.png&quot; width=&quot;303&quot; height=&quot;265&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;The half-working thing&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Adding the plugin in Flatpak-Bitwig leaves you with a half-working thing where you can select
presets of combinations of samples but you can&#39;t access or rearrange individual samples.
The main UI is inaccessible because trying to open it just gives the following log messages about
missing libraries:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;UIRaveGenerator2VST: error while loading shared libraries: libQtCore.so.4: cannot open shared object file: No such file or directory&lt;br /&gt;VST plugin did not create it&#39;s window inside the plugin window&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The mentioned workarounds seem like a hassle. I waved my fist at a cloud and opened &lt;a href=&quot;https://github.com/NationalSecurityAgency/ghidra&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Ghidra&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;finding-the-samples&quot; tabindex=&quot;-1&quot;&gt;Finding the samples &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2022-11-01/extracting-audio-samples-from-rave-generator-2-vst-plugin/#finding-the-samples&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/ravegenerator-pathtobuffer-sfgJDzq5q4-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;a screenshot of Ghidra showing an assembler and C code listing of the pathToBuffer function&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/ravegenerator-pathtobuffer-sfgJDzq5q4-orig.png&quot; width=&quot;1647&quot; height=&quot;734&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Start of Sample::pathToBuffer()&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;When searching for known sample names I quickly found this function (&lt;em&gt;Sample::pathToBuffer&lt;/em&gt;) which
takes a resource name string like &lt;code&gt;:/Resources/sounds/stabs2/foo.wav&lt;/code&gt; and returns a pointer to WAVE
data containing the wanted sample.&lt;/p&gt;
&lt;p&gt;The string&#39;s syntax reminded me of &lt;a href=&quot;https://doc.qt.io/qt-5/resources.html#using-resources-in-a-library&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Qt&#39;s QResource&lt;/a&gt; so I ran &lt;a href=&quot;https://github.com/dgchurchill/extract-qt-resources&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;dgchurchill/extract-qt-resources&lt;/a&gt; on the plugin file (&lt;em&gt;RaveGenerator2VST-x64.so&lt;/em&gt;). But it couldn&#39;t detect any files in there.&lt;/p&gt;
&lt;p&gt;There are 83 individual samples in there which would have been too many to do manually so I wrote a
script using &lt;a href=&quot;https://github.com/GhidraJupyter/ghidra-jupyter-kotlin&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Ghidra-Jupyter-Kotlin&lt;/a&gt; for quick prototyping
(and so I could do some Kotlin).&lt;/p&gt;
&lt;h2 id=&quot;the-notebook&quot; tabindex=&quot;-1&quot;&gt;The notebook &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2022-11-01/extracting-audio-samples-from-rave-generator-2-vst-plugin/#the-notebook&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I &lt;a href=&quot;https://gist.github.com/spezifisch/3e92aeca446fe8b17d07d8ad53df46fa&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;uploaded the Jupyter Notebook&lt;/a&gt; to Github Gist (&lt;a href=&quot;https://spezifisch.codeberg.page/assets/posts/ravegen_dump.ipynb&quot;&gt;mirror&lt;/a&gt;).
You can just open it and follow the instructions at its top and ignore the rest in here if you don&#39;t
care about a description of what it does.&lt;/p&gt;
&lt;p&gt;So first we have a container for information we want to gather for each sample:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;resourceName&lt;/em&gt; - the identifier used in the binary, e.g. &amp;quot;:/Resources/sounds/instrus/Pump Bass.wav&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;fileName&lt;/em&gt; - a valid file name like &amp;quot;sounds/instrus/Pump Bass.wav&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;waveAddr&lt;/em&gt; - Ghidra&#39;s pointer to the WAVE data&lt;/li&gt;
&lt;li&gt;&lt;em&gt;rootNote&lt;/em&gt; - the note which the sample plays when playing it back with the original speed, e.g. D3 (i.e. the note D in the 3rd octave)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-kt&quot;&gt;&lt;code class=&quot;language-kt&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;RGSample&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; fileName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; resourceName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; waveAddr&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GenericAddress&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; rootNote&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;valid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Boolean &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; fileName &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; resourceName &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; waveAddr &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;pathtobuffer-parser&quot; tabindex=&quot;-1&quot;&gt;pathToBuffer parser &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2022-11-01/extracting-audio-samples-from-rave-generator-2-vst-plugin/#pathtobuffer-parser&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In a loop we then go through each line (i.e. assembler instruction) of the pathToBuffer function body:&lt;/p&gt;
&lt;pre class=&quot;language-kt&quot;&gt;&lt;code class=&quot;language-kt&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; samples &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mutableListOf&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;RGSample&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; currentSample &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;RGSample&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;codeUnit &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; currentProgram&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;listing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCodeUnits&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pathToBuffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;monitor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isCancelled&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; mnem &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; codeUnit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mnemonicString&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mnem &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;LEA&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// load resource name&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; resourceNameAddr &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; codeUnit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAddress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; resourceName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getDataAt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resourceNameAddr&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; String&lt;br /&gt;        currentSample&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;resourceName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; resourceName&lt;br /&gt;        currentSample&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fileName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; resourceName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removePrefix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;:/Resources/&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;debugPathToBuffer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;codeUnit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;address&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;resourceNameAddr&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;resourceName&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mnem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;MOV&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; codeUnit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot; RAX,&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// set function return value&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; wavePtr &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mnem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;CMOV&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;getDataAt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;codeUnit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAddress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            codeUnit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAddress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; GenericAddress&lt;br /&gt;        &lt;br /&gt;        currentSample&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;waveAddr &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; wavePtr&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// [... rootNote handling excluded for clarity ...]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// this sample is done&lt;/span&gt;&lt;br /&gt;        samples&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;currentSample&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;debugPathToBuffer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;codeUnit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;address&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;: wave address &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;wavePtr&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// next sample&lt;/span&gt;&lt;br /&gt;        currentSample &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;RGSample&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;LEA&lt;/code&gt; instructions here always load a resource name string to compare against the function&#39;s
input parameter.&lt;/p&gt;
&lt;p&gt;The comparison&#39;s result is checked and if successful the address of the wanted WAVE data is copied
into the &lt;code&gt;RAX&lt;/code&gt; register as a return value. These instructions are &lt;code&gt;MOV&lt;/code&gt;s, or &lt;code&gt;CMOVNZ&lt;/code&gt; in the last
occurence.&lt;/p&gt;
&lt;p&gt;Luckily the compiler produced pretty much the same code for each of the cases so we can get away
with a very simple parser: store the resource name of the checked sample in &lt;code&gt;currentSample.resourceName&lt;/code&gt;
and the next &lt;code&gt;MOV RAX,...&lt;/code&gt; line will be the corresponding data pointer which we store in &lt;code&gt;currentSample.waveAddr&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In the end we have a list of sample names and data pointers in &lt;code&gt;samples&lt;/code&gt; which we can use to write
the data to external files.&lt;/p&gt;
&lt;h3 id=&quot;adjustrootnote-parser&quot; tabindex=&quot;-1&quot;&gt;adjustRootNote parser &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2022-11-01/extracting-audio-samples-from-rave-generator-2-vst-plugin/#adjustrootnote-parser&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Okay, we now know where the samples are but I saw that some sample sounds are recorded in a note
different from C3 which I assume to be the default root note (because it usually is). It would be
nice to have the root note in the file name so I can set it accordingly in my sampler. And so that
I actually get a C when I&#39;m pressing C on my keyboard.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/ravegenerator-rootnote-LgPNOlWJBw-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;a screenshot of Ghidra showing listings of setRootNote and adjustRootNote&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/ravegenerator-rootnote-LgPNOlWJBw-orig.png&quot; width=&quot;1507&quot; height=&quot;748&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;start of Sample::adjustRootNote() (left) and C code for Sample::setRootNote() (right)&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Also in the Sample class there&#39;s an appropiately named &lt;em&gt;adjustRootNote()&lt;/em&gt; function which handles the
cases for the 22 samples which have different root notes.&lt;/p&gt;
&lt;p&gt;The parser code here is pretty similar to the one for &lt;em&gt;pathToBuffer()&lt;/em&gt; so here&#39;s just an essential
differing part:&lt;/p&gt;
&lt;pre class=&quot;language-kt&quot;&gt;&lt;code class=&quot;language-kt&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/*...*/&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mnem &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;JZ&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;debugAdjustRootNote&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;codeUnit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;address&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;codeUnit&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// follow the jump target for the case of the resource name matching&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; jmpTarget &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; codeUnit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAddress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nestedCU &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; currentProgram&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;listing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCodeUnits&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jmpTarget&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;debugAdjustRootNote&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;NEST &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;nestedCU&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;address&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;nestedCU&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nestedCU&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mnemonicString&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;J&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;// stop at the jump back to avoid cycles&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nestedCU&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;MOV ESI,&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;currentResource &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ERROR: we missed the root note name&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;// ex: MOV ESI,0x38&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;// get argument to call of Sample::setRootNote()&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; rootNoteRaw &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; nestedCU&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getScalar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; rootNote &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getRootNoteString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rootNoteRaw&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;// write root note&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;debugAdjustRootNote &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; infoAdjustRootNote&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;got root note: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;currentResource&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; -&gt; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;rootNote&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;            rootNotes&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;currentResource&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rootNote&lt;br /&gt;            currentResource &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;em&gt;switch&lt;/em&gt; statement in this function is structured a bit differently than the previous one. While
in &lt;em&gt;pathToBuffer()&lt;/em&gt; the instructions for the case of a matching resource string came immediately
after the comparison, here the unsuccessful case and the check for the next case follow.&lt;/p&gt;
&lt;p&gt;In case of a successful match of the resource string we have a jump to a small set of instructions
which call &lt;em&gt;Sample::setRootNote(int note)&lt;/em&gt; with the appropriate note value (explained later).&lt;/p&gt;
&lt;p&gt;That&#39;s where the code shown above comes to play which follows this jump, extracts the 2nd argument
to the &lt;code&gt;MOV ESI, ...&lt;/code&gt; instruction which contains the argument to the function call to &lt;em&gt;setRootNote()&lt;/em&gt;,
and stores it in the HashMap &lt;em&gt;rootNotes&lt;/em&gt; which gets added to the &lt;em&gt;samples&lt;/em&gt; list from before.&lt;/p&gt;
&lt;h3 id=&quot;setrootnote-analysis&quot; tabindex=&quot;-1&quot;&gt;setRootNote analysis &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2022-11-01/extracting-audio-samples-from-rave-generator-2-vst-plugin/#setrootnote-analysis&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;So what does this &lt;em&gt;note&lt;/em&gt; parameter in &lt;em&gt;setRootNote(int note)&lt;/em&gt; mean? I translated the function&#39;s
decompiled C code to Python to show what&#39;s going on:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Sample::setRootNote() rewritten in Python&lt;/span&gt;&lt;br /&gt;factor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.0&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; note &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; i &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;note &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# pitch down one semitone&lt;/span&gt;&lt;br /&gt;        factor &lt;span class=&quot;token operator&quot;&gt;/=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.059463094359&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;elif&lt;/span&gt; note &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; i &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; note&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# up by one semitone&lt;/span&gt;&lt;br /&gt;        factor &lt;span class=&quot;token operator&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.059463094359&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So if the root note is above 60 (C3) the &amp;quot;factor&amp;quot; gets scaled down by &lt;em&gt;1.059463094359&lt;/em&gt; for each
semitone that it&#39;s above 60. The symmetric case is done for a note lower than 60.&lt;/p&gt;
&lt;p&gt;You can easily search the Internet for the term &lt;em&gt;1.059463094359&lt;/em&gt; and see that it&#39;s the &lt;em&gt;12th root of 2&lt;/em&gt;
which is the frequency ratio between two adjacent notes (e.g. C and C# or B and C) in the
equal-tempered scale in western music.&lt;/p&gt;
&lt;p&gt;We can therefore conclude that the &amp;quot;factor&amp;quot; modifies the playback speed of the sample so that a
sample recorded in C# is played at &lt;em&gt;1/1.059463094359&lt;/em&gt; the original speed when you&#39;re pressing C on
the keyboard.&lt;/p&gt;
&lt;p&gt;Using this background we can write a function to translate these note values to readable strings:&lt;/p&gt;
&lt;pre class=&quot;language-kt&quot;&gt;&lt;code class=&quot;language-kt&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getRootNoteString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;arg&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Long&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Sample::setRootNote(int) pitches the note up or down by the given number of semitones away from 60 (= C3)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// convert that number to a readable note, assuming C3=60, B2=59, C#3=61,...&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; octave &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;arg &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; semitone &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;arg &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; notes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;arrayOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;C&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;C#&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;D&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;D#&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;E&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;F&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;F#&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;G&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;G#&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;A&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;A#&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;B&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;notes&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;semitone&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;octave&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2022-11-01/extracting-audio-samples-from-rave-generator-2-vst-plugin/#conclusion&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now we have everything in place to export the samples with meaningful file names for use in another
sampler.&lt;/p&gt;
&lt;p&gt;In case you missed it above &lt;a href=&quot;https://gist.github.com/spezifisch/3e92aeca446fe8b17d07d8ad53df46fa&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;here is the Jupyter Notebook&lt;/a&gt; (Github)
(&lt;a href=&quot;https://spezifisch.codeberg.page/assets/posts/ravegen_dump.ipynb&quot;&gt;mirror&lt;/a&gt;) you need to get the WAVE files.&lt;/p&gt;
&lt;p&gt;Also I found no full list of samples contained in Rave Generator 2 on the Internet, so here it is in
case someone looks for those:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;:/Resources/sounds/instrus/Get Up Bass.wav&lt;br /&gt;:/Resources/sounds/instrus/Hardcore Hoover.wav&lt;br /&gt;:/Resources/sounds/instrus/Hi Voltage.wav&lt;br /&gt;:/Resources/sounds/instrus/Juno Bass.wav&lt;br /&gt;:/Resources/sounds/instrus/Juno Hoover.wav&lt;br /&gt;:/Resources/sounds/instrus/m1organ.wav&lt;br /&gt;:/Resources/sounds/instrus/Maniak Techno.wav&lt;br /&gt;:/Resources/sounds/instrus/Mayday Dream.wav&lt;br /&gt;:/Resources/sounds/instrus/Omnibus.wav&lt;br /&gt;:/Resources/sounds/instrus/Pizzicato Dance.wav&lt;br /&gt;:/Resources/sounds/instrus/PsychotropiCZ.wav&lt;br /&gt;:/Resources/sounds/instrus/Pump Bass.wav&lt;br /&gt;:/Resources/sounds/instrus/Rave CutBass.wav&lt;br /&gt;:/Resources/sounds/instrus/Rave Cycle.wav&lt;br /&gt;:/Resources/sounds/instrus/Square Bass.wav&lt;br /&gt;:/Resources/sounds/jx1/JX1 C4+C5.wav&lt;br /&gt;:/Resources/sounds/stabs2/Awesome3.wav&lt;br /&gt;:/Resources/sounds/stabs2/BizarreInc1.wav&lt;br /&gt;:/Resources/sounds/stabs2/Black Riot 2.wav&lt;br /&gt;:/Resources/sounds/stabs2/Cubic22.wav&lt;br /&gt;:/Resources/sounds/stabs2/DeeLiteOrgan.wav&lt;br /&gt;:/Resources/sounds/stabs2/Dist Brazil.wav&lt;br /&gt;:/Resources/sounds/stabs2/DJ Professor.wav&lt;br /&gt;:/Resources/sounds/stabs2/Enjoy.wav&lt;br /&gt;:/Resources/sounds/stabs2/FidelFattiPiano.wav&lt;br /&gt;:/Resources/sounds/stabs2/HappyHardcoreStab.wav&lt;br /&gt;:/Resources/sounds/stabs2/Hit House 2.wav&lt;br /&gt;:/Resources/sounds/stabs2/Landlord 2.wav&lt;br /&gt;:/Resources/sounds/stabs2/Poltergeist.wav&lt;br /&gt;:/Resources/sounds/stabs2/Powerrr.wav&lt;br /&gt;:/Resources/sounds/stabs2/Rave Action.wav&lt;br /&gt;:/Resources/sounds/stabs2/RedTwo.wav&lt;br /&gt;:/Resources/sounds/stabs2/Short Rave.wav&lt;br /&gt;:/Resources/sounds/stabs2/SpeedSoul.wav&lt;br /&gt;:/Resources/sounds/stabs2/Sweat.wav&lt;br /&gt;:/Resources/sounds/stabs2/Take On Higher.wav&lt;br /&gt;:/Resources/sounds/stabs2/Toxic Two.wav&lt;br /&gt;:/Resources/sounds/stabs2/Wave Of Future.wav&lt;br /&gt;:/Resources/sounds/stabs2/Wild Child1.wav&lt;br /&gt;:/Resources/sounds/stabs2/Wild Child2.wav&lt;br /&gt;:/Resources/sounds/stabs2/Wild Child3.wav&lt;br /&gt;:/Resources/sounds/stabs2/Zentral.wav&lt;br /&gt;:/Resources/sounds/stabs3/Belgian Rave.wav&lt;br /&gt;:/Resources/sounds/stabs3/Break Boys.wav&lt;br /&gt;:/Resources/sounds/stabs3/CLS.wav&lt;br /&gt;:/Resources/sounds/stabs3/Cool Stab.wav&lt;br /&gt;:/Resources/sounds/stabs3/CZorgan Alex Party.wav&lt;br /&gt;:/Resources/sounds/stabs3/Distorgan2.wav&lt;br /&gt;:/Resources/sounds/stabs3/Expansion.wav&lt;br /&gt;:/Resources/sounds/stabs3/Gabbers.wav&lt;br /&gt;:/Resources/sounds/stabs3/Hardcore Hoover.wav&lt;br /&gt;:/Resources/sounds/stabs3/Hound Stab.wav&lt;br /&gt;:/Resources/sounds/stabs3/House Nation.wav&lt;br /&gt;:/Resources/sounds/stabs3/Magic Feet.wav&lt;br /&gt;:/Resources/sounds/stabs3/Mayday.wav&lt;br /&gt;:/Resources/sounds/stabs3/Organonox.wav&lt;br /&gt;:/Resources/sounds/stabs3/Party Children.wav&lt;br /&gt;:/Resources/sounds/stabs3/Piano Chord.wav&lt;br /&gt;:/Resources/sounds/stabs3/Rotterdam Hoover.wav&lt;br /&gt;:/Resources/sounds/stabs3/SL2.wav&lt;br /&gt;:/Resources/sounds/stabs3/Synth15 Stab.wav&lt;br /&gt;:/Resources/sounds/stabs3/Techno8.wav&lt;br /&gt;:/Resources/sounds/stabs3/Tremoradelterra.wav&lt;br /&gt;:/Resources/sounds/stabs3/Twilight Bep.wav&lt;br /&gt;:/Resources/sounds/stabs3/Wavez.wav&lt;br /&gt;:/Resources/sounds/stabs3/Week End.wav&lt;br /&gt;:/Resources/sounds/voices/Acieed.wav&lt;br /&gt;:/Resources/sounds/voices/Ayeaaaa First Choice.wav&lt;br /&gt;:/Resources/sounds/voices/Charly.wav&lt;br /&gt;:/Resources/sounds/voices/Dish you.wav&lt;br /&gt;:/Resources/sounds/voices/Energize.wav&lt;br /&gt;:/Resources/sounds/voices/Fast.wav&lt;br /&gt;:/Resources/sounds/voices/Go 2.wav&lt;br /&gt;:/Resources/sounds/voices/Godftaher The Joint.wav&lt;br /&gt;:/Resources/sounds/voices/jieeeeeeeaaaaaaaah.wav&lt;br /&gt;:/Resources/sounds/voices/Kick Out The Jam.wav&lt;br /&gt;:/Resources/sounds/voices/Loon.wav&lt;br /&gt;:/Resources/sounds/voices/Neneh Cherry.wav&lt;br /&gt;:/Resources/sounds/voices/ooooooooh2.wav&lt;br /&gt;:/Resources/sounds/voices/ooooooooh.wav&lt;br /&gt;:/Resources/sounds/voices/Party.wav&lt;br /&gt;:/Resources/sounds/voices/Patti.wav&lt;br /&gt;:/Resources/sounds/voices/Paw.wav&lt;/code&gt;&lt;/pre&gt;
</content>
		</entry>
		
		<entry>
			<title>CO2 Sensor Reverse Engineering - Part II</title>
			<link href="https://spezifisch.codeberg.page/posts/2022-08-25/co2-sensor-reverse-engineering-part-ii/"/>
			<updated>2022-08-25T00:00:00Z</updated>
			<id>https://spezifisch.codeberg.page/posts/2022-08-25/co2-sensor-reverse-engineering-part-ii/</id>
			<content type="html">&lt;p&gt;After finding out how to decode the CO2 sensor measurements in the previous post I wanted to look at other salvageable components of the &amp;quot;BreeRainz DM1308A&amp;quot;.&lt;/p&gt;
&lt;h2 id=&quot;battery-management&quot; tabindex=&quot;-1&quot;&gt;Battery Management &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2022-08-25/co2-sensor-reverse-engineering-part-ii/#battery-management&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On the bottom side of the main PCB there is a battery management IC I didn&#39;t mention: &lt;a href=&quot;https://www.alldatasheet.com/datasheet-pdf/pdf/1300028/HMSEMI/HM4052.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;HM4052&lt;/a&gt; from Shenzhen Huazhimei Semiconductor Co., Ltd. It&#39;s quite interesting because it combines a 3.3V buck regulator with battery charging and safety functions without the need for an external microcontroller for configuration. It just works: You plug in USB and the battery is charged with a rate configured with an external resistor and your device is powered by USB. You unplug it and your 3.3V come from the battery.&lt;/p&gt;
&lt;h2 id=&quot;temperature-humidity-sensor&quot; tabindex=&quot;-1&quot;&gt;Temperature/Humidity Sensor &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2022-08-25/co2-sensor-reverse-engineering-part-ii/#temperature-humidity-sensor&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The gadget also shows the temperature and humidity on the display. What kind of sensor is it using?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; It&#39;s a &lt;a href=&quot;https://www.adafruit.com/product/2857&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Sensirion SHT31&lt;/a&gt; connected via I2C.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/co2sensor-dis-4-k8HfyCFyKn-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;a PCB with a white print screen and a small black IC with a white blob on it&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/co2sensor-dis-4-k8HfyCFyKn-orig.jpeg&quot; width=&quot;1000&quot; height=&quot;750&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;h3 id=&quot;logging-the-communication&quot; tabindex=&quot;-1&quot;&gt;Logging the communication &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2022-08-25/co2-sensor-reverse-engineering-part-ii/#logging-the-communication&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Again the following PulseView traces show the communication between the microcontroller and the sensor:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/co2-i2cth-first-cmd-GcKXCTVLDt-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;PulseView trace of I2C command&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/co2-i2cth-first-cmd-GcKXCTVLDt-orig.png&quot; width=&quot;804&quot; height=&quot;290&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;Initially after power-on the microcontroller sends &lt;code&gt;22 36&lt;/code&gt; to I2C address &lt;code&gt;0x44&lt;/code&gt;.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/co2-i2cth-cmd-response-ei1xKc0l74-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;PulseView trace of I2C command with response&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/co2-i2cth-cmd-response-ei1xKc0l74-orig.png&quot; width=&quot;1472&quot; height=&quot;284&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;Then it waits around 500 ms and sends another command (&lt;code&gt;E0 00&lt;/code&gt;) to &lt;code&gt;0x44&lt;/code&gt; and reads a response from the same address. Then it sends the first command again.&lt;/p&gt;
&lt;h3 id=&quot;the-protocol&quot; tabindex=&quot;-1&quot;&gt;The Protocol &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2022-08-25/co2-sensor-reverse-engineering-part-ii/#the-protocol&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Luckily with I2C the addresses are pretty telling: There are &lt;a href=&quot;https://i2cdevices.org/addresses/0x44&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;a few known devices using this address&lt;/a&gt; and of those the &lt;a href=&quot;https://i2cdevices.org/devices/sht31&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Sensirion SHT 31&lt;/a&gt; fits the bill perfectly. The &lt;a href=&quot;https://sensirion.com/media/documents/213E6A3B/61641DC3/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;datasheet&lt;/a&gt; contains all the information we need.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/co2-temphum-ds1--yBeLV0bIC-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;table from the datasheet showing measurement modes&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/co2-temphum-ds1--yBeLV0bIC-orig.png&quot; width=&quot;367&quot; height=&quot;490&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Datasheet: Measurement command&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;So the &lt;code&gt;22 36&lt;/code&gt; from the command means that 2 measurements per second should be taken with the &amp;quot;high&amp;quot; repeatability setting.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/co2-temphum-ds2-gFMurnJ769-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;diagram from the datasheet showing the packet structure&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/co2-temphum-ds2-gFMurnJ769-orig.png&quot; width=&quot;559&quot; height=&quot;448&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Datasheet: Data fetch command&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And this shows that &lt;code&gt;E0 00&lt;/code&gt; is the &amp;quot;data fetch command&amp;quot; which gives us 16 bit temperature and humidity measurements, each one with a CRC checksum.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/co2-temphum-ds3-0Q3vCJxZ5k-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;formulas from the datasheet showing conversion between protocol bytes and humidity and temperature values&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/co2-temphum-ds3-0Q3vCJxZ5k-orig.png&quot; width=&quot;431&quot; height=&quot;474&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Datasheet: Humidity and temperature value conversion&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And this is how we get human-readable measurements after reassembling the bytes in the right endianness into 16 bit values.&lt;/p&gt;
&lt;h3 id=&quot;decoding-our-logs&quot; tabindex=&quot;-1&quot;&gt;Decoding our logs &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2022-08-25/co2-sensor-reverse-engineering-part-ii/#decoding-our-logs&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We can now decode the measurements in our logic analyzer traces above.&lt;/p&gt;
&lt;p&gt;In the screenshot we got the following response bytes:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;44 67 86 A6  93 CA 7D&lt;br /&gt;^^       ^^        ^^&lt;br /&gt;AR ^^^^^ CRC ^^^^^ CRC&lt;br /&gt;   temp.     humid.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Converting them this gives us:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# temperature in °C&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;45&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;175&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0x67&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;256&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0x86&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token number&quot;&gt;25.769054703593497&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# humidity in %RH&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0x93&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;256&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xca&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token number&quot;&gt;57.73098344396124&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don&#39;t you love this summer.&lt;/p&gt;
&lt;p&gt;We now have a temperature and humidity sensor for our own gadget! But we should really check the checksums, too. Their CRC parameters are given in the datasheet.&lt;/p&gt;
</content>
		</entry>
		
		<entry>
			<title>CO2 Sensor Reverse Engineering</title>
			<link href="https://spezifisch.codeberg.page/posts/2022-08-23/co2-sensor-reverse-engineering/"/>
			<updated>2022-08-23T00:00:00Z</updated>
			<id>https://spezifisch.codeberg.page/posts/2022-08-23/co2-sensor-reverse-engineering/</id>
			<content type="html">&lt;p&gt;I recently bought the cheapest CO2 measurement device with an &lt;a href=&quot;https://en.wikipedia.org/wiki/Nondispersive_infrared_sensor&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;NDIR&lt;/a&gt; sensor that I could find: &amp;quot;BreeRainz DM1308A&amp;quot; for 25€ on Amazon.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/co2sensor-product-bf8xzRz1x0-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;product photo of a gadget with a color display showing the measured CO2 air concentration&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/co2sensor-product-bf8xzRz1x0-orig.jpeg&quot; width=&quot;522&quot; height=&quot;522&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Credit: BreeRainz on Amazon&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;I hoped to find something like the popular &lt;a href=&quot;https://www.arduino.cc/reference/en/libraries/mh-z19/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;MH-Z19&lt;/a&gt; inside which currently costs around 35€. My plan was to buy the cheaper device, save some bucks and to unsolder its CO2 sensor and use it in a DIY gadget with an ESP32.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; The device contains a CO2 sensor which is pin-compatible to MH-Z19 but with a different serial protocol. It&#39;s a bit obscure but you can find a datasheet and protocol description. The module is named HC8 (not to be confused with the Bluetooth module HC08) and it also has a PWM output (which isn&#39;t used in this gadget).&lt;/p&gt;
&lt;h2 id=&quot;disassembly&quot; tabindex=&quot;-1&quot;&gt;Disassembly &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2022-08-23/co2-sensor-reverse-engineering/#disassembly&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/co2sensor-dis-1-0Y4IeYsmKL-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;opened CO2 sensor gadget revealing some PCBs and a battery&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/co2sensor-dis-1-0Y4IeYsmKL-orig.jpeg&quot; width=&quot;1000&quot; height=&quot;750&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;I totally wrecked the enclosure prying it open because I didn&#39;t know there were screws behind the front glass of the display.&lt;/p&gt;
&lt;p&gt;In the back you can see a 3.7V/1600mAh LiPo battery, a Micro USB charging port (actually you can also connect a PC to flash the display controller), and a temperature/humidity sensor (Sensirion SHT3x, or at least compatible).&lt;/p&gt;
&lt;p&gt;In the front there&#39;s the main PCB on top of the display.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/co2sensor-dis-2-pww5E8-gxQ-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;main PCB with annotations explained in the caption&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/co2sensor-dis-2-pww5E8-gxQ-orig.jpeg&quot; width=&quot;1500&quot; height=&quot;1125&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Main PCB: 1=on/off switch; 2=connector for temperature/humidity sensor; 3=CO2 sensor; 4=Levetop LT268A display controller/microcontroller; 5=W25Q32JV 32MBit SPI flash; 6=bootloader switch; 7=LiPo battery connection (removed); 8=display connector&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;All the peripherals are connected to the display controller (Levetop LT268A) which is quite an interesting device. &lt;a href=&quot;https://www.levetop.tw/en/product1_268.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;From the manufacturer&#39;s page&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;LT268x/269 are Serial-Uart TFT Controller designed for small size MCU panels, integrated Cortex M4 32bit MCU core architecture. The main function is to provide Uart serial communication, so that the host computer MCU can easily transfer the content to be displayed to the TFT driver through simple command. [...]&lt;/p&gt;
&lt;p&gt;The internal frequency of the LT268x/269 is 150MHz, including 512Kbytes Flash and 256Kbytes SRAM, provide Uart serial communication and SPI Flash interface. The external SPI Flash can be used to store pictures, animations, fonts and other information.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Pretty nice for a thing whose only job it is to receive a CO2 concentration via UART, temperature and humidity data via I2C and to display those.&lt;/p&gt;
&lt;p&gt;Out of curiosity I dumped the SPI flash with a CH341A (&lt;code&gt;flashrom -p ch341a_spi -r dump.bin&lt;/code&gt;) but this shouldn&#39;t be my focus here as there isn&#39;t much inside: Only roughly 1 MByte is used, probably for the displayed images as the display controller datasheet suggests. &lt;code&gt;binwalk&lt;/code&gt; also couldn&#39;t find anything useful.&lt;/p&gt;
&lt;h2 id=&quot;finding-out-the-co2-sensor-s-protocol&quot; tabindex=&quot;-1&quot;&gt;Finding out the CO2 sensor&#39;s protocol &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2022-08-23/co2-sensor-reverse-engineering/#finding-out-the-co2-sensor-s-protocol&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Back to the CO2 sensor. How do we get its measurements? Fortunately RX, TX, and GND were labelled on the PCB, so I hooked up a logic analyzer (FX2 evaluation board with sigrok firmware and PulseView) to find out.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/co2sensor-cmd-auto-xCouDtZ8ib-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Pulseview trace of UART communication #1&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/co2sensor-cmd-auto-xCouDtZ8ib-orig.png&quot; width=&quot;1422&quot; height=&quot;190&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;The CO2 sensor communicates using UART with 9600 Baud, 8N1. After switching the device on it initially sends a packet starting with &lt;code&gt;42 4D&lt;/code&gt; and some bytes containing the CO2 measurement and a checksum.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/co2sensor-cmd-D62Lzy1yk2-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Pulseview trace of UART communication #2&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/co2sensor-cmd-D62Lzy1yk2-orig.png&quot; width=&quot;1108&quot; height=&quot;489&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Command packet (look at RXD/TXD and ignore the other traces)&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The display controller then sends &lt;code&gt;64 69 03 5E 4E&lt;/code&gt; to request a measurement. This also causes the sensor to leave the default mode where it automatically sends measurement data every second.&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow post-card-darker&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/co2sensor-cmd-response-e3b_nIMTTr-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Pulseview trace of UART communication #3&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/co2sensor-cmd-response-e3b_nIMTTr-orig.png&quot; width=&quot;1335&quot; height=&quot;476&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;Response packet&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The CO2 sensor then responds with a packet starting with &lt;code&gt;64 69 03&lt;/code&gt; and the CO2 measurement in a similar format.&lt;/p&gt;
&lt;p&gt;This doesn&#39;t look like the MH-Z19 I hoped for.&lt;/p&gt;
&lt;h3 id=&quot;what-is-it&quot; tabindex=&quot;-1&quot;&gt;What is it? &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2022-08-23/co2-sensor-reverse-engineering/#what-is-it&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;So here&#39;s how I found the datasheet for the CO2 sensor:&lt;/p&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/co2sensor-cmd-google--bAmh-SzQj-orig.png&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Google result of a search for &#39;64 69 03 5E 4E&#39; showing a single result in Chinese&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/co2sensor-cmd-google--bAmh-SzQj-orig.png&quot; width=&quot;877&quot; height=&quot;555&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;I just searched for the sequence of bytes (&lt;code&gt;64 69 03 5E 4E&lt;/code&gt;) the display controller sends. There was a single search result with a PDF. And its server was not reachable. Luckily Google still had &lt;a href=&quot;http://webcache.googleusercontent.com/search?q=cache%3Ahttp%3A%2F%2Fwww.icmkw.com%2Ffile-download-158219-left.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;a cached version of its content&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;By the way you can also see part of the sensor&#39;s pinout in the image results in the screenshot above.&lt;/p&gt;
&lt;p&gt;So here is the answer: The CO2 sensor is called &lt;strong&gt;HC8&lt;/strong&gt; and it&#39;s manufactured by &lt;strong&gt;广州海谷电子科技有限公司 (Guangzhou Haigu Electronic Technology Co., Ltd.)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The datasheet contains this nice explanation (translated with DeepL, slightly edited for clarity):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The working principle of the non-dispersive infrared absorption (NDIR) gas sensor is based on the &lt;em&gt;[Lambert-Beer law.]&lt;/em&gt; The absorption characteristics of infrared spectrum are calculated and confirmed through the relationship between gas concentration and absorption intensity (Lambert-Beer law). [...] Molecules composed of heterogeneous atoms such as CO2 and CO have absorption spectra in the infrared wavelength region. Its absorption intensity follows the Lambert-Beer law. When the light wave corresponding to the characteristic absorption wavelength of a certain gas passes through the measured gas [...] its intensity will be significantly weakened, and the degree of intensity attenuation is related to the concentration of the gas, and the relationship between the two follows [...] the Lambert-Beer law. The basic principle structure of the NDIR sensor is shown in the figure below.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;specs&quot; tabindex=&quot;-1&quot;&gt;Specs &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2022-08-23/co2-sensor-reverse-engineering/#specs&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Some interesting parts of the specifications which will be useful when implementing a DIY gadget around this sensor:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Measuring range: 400~5000 ppm&lt;br /&gt;Resolution: 1 ppm&lt;br /&gt;precision: ±(50ppm+5% *reading)&lt;br /&gt;Response time T90: &lt;120 seconds (s)&lt;br /&gt;data update time: &lt;3 (standard 1s) seconds (s)&lt;br /&gt;Preheat time:&lt;br /&gt;    &lt;25s (operational)&lt;br /&gt;    &lt;2min (90% accuracy)&lt;br /&gt;    &lt;10min (maximum accuracy)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;protocol&quot; tabindex=&quot;-1&quot;&gt;Protocol &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2022-08-23/co2-sensor-reverse-engineering/#protocol&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Some relevant parts of the datasheet:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;The sensor has two output modes, active output and query output. After one query, the active output stops.&lt;br /&gt;It needs to be powered on again to restore.&lt;br /&gt;After power-on, the sensor actively outputs data: the output frequency is 1S once.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Data format in &amp;quot;active output&amp;quot; mode:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;The output format is 16BYTE.&lt;br /&gt;Data header: BYTE0 = 0X42; BYTE1=4D&lt;br /&gt;BYTE6 data is high, BYTE7 data is low, indicating CO2 concentration.&lt;br /&gt;BYTE15, data checksum. BYTE15= BYTE0+BYTE1+…….+BYTE13;&lt;br /&gt;&lt;br /&gt;Example: 42 4D 0C 51 09 A2 07 2B 01 35 05 81 20 08 20 AD;&lt;br /&gt;CO2 concentration = BYTE6 X 256 + BYTE7 = 07X256 + 2B = 1853;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&#39;s interesting here that the checksum in this mode is just the sum of the previous bytes which isn&#39;t as robust as CRC.&lt;/p&gt;
&lt;p&gt;Data format in &amp;quot;query output&amp;quot; mode:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Query reads:&lt;br /&gt;Read CO2ppm value, return 522ppm&lt;br /&gt;Send: 64 69 03 5E 4E&lt;br /&gt;Return: 64 69 03 01 0A 02 00 00 00 00 00 00 9B F0&lt;br /&gt;&lt;br /&gt;14 BYTEs are returned.&lt;br /&gt;BYTE4, BYTE5 represent the concentration of CO2, converted into decimal not only the concentration value, BYTE5 is the upper 8 digits, BYTE4&lt;br /&gt;for the lower 8 bits.&lt;br /&gt;0x0A is the low byte of the 16bit integer, 0x02 is the high byte, and together it is 522&lt;br /&gt;BYTE12, BYTE13 are CRC check data&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And this one uses CRC. The datasheet also contains example code for the CRC check. So I&#39;m going with this mode for my own gadget.&lt;/p&gt;
&lt;p&gt;We can also now decode the response packet I showed in the screenshot before. Its bytes were:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;64 69 03 01 B6 02 00 00 00 00 00 00 90 D1&lt;br /&gt;^^^^^       ^^^^^                   ^^^^^&lt;br /&gt;header       ppm                     CRC&lt;br /&gt;      ^^^^^       ^^^^^^^^^^^^^^^^^&lt;br /&gt;        ?         seems always zero&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To get the CO2 air concentration in parts per million (ppm) we do:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&gt; 0x02 * 255 + 0xb6&lt;br /&gt;  (2 × 255) + 182 = 692&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So it&#39;s 692 ppm!&lt;/p&gt;
&lt;h2 id=&quot;salvaging-the-co2-sensor&quot; tabindex=&quot;-1&quot;&gt;Salvaging the CO2 sensor &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2022-08-23/co2-sensor-reverse-engineering/#salvaging-the-co2-sensor&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;figure class=&quot;post-card post-card-shadow&quot;&gt;&lt;a href=&quot;https://spezifisch.codeberg.page/assets/img/co2sensor-dis-3-6LpSc-Y7Gt-orig.jpeg&quot; title=&quot;Open image&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;CO2 sensor with a shiny plastic box sitting on a breadboard connected with some wires to a USB device&quot; decoding=&quot;async&quot; src=&quot;https://spezifisch.codeberg.page/assets/img/co2sensor-dis-3-6LpSc-Y7Gt-orig.jpeg&quot; width=&quot;1500&quot; height=&quot;1125&quot; /&gt;&lt;/a&gt;&lt;figcaption&gt;The CO2 sensor&#39;s pinout of the top row is from left to right: +5V, GND, NC (not connected), PWM; bottom row: NC, RX, TX, NC&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Now that I knew enough I unsoldered the CO2 sensor, connected it to my PC using an FT232 board with 3.3V and supplied the sensor itself with 5V.&lt;/p&gt;
&lt;p&gt;As a quick test I wrote a small Python script which uses pyserial and uses the &amp;quot;active output&amp;quot; mode of the sensor:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;#!/usr/bin/python&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; datetime&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; serial&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; binascii &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; hexlify&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; serial&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Serial&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/dev/ttyUSB0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9600&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timeout&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; ser&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        b &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;read&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        ts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; datetime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;datetime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;now&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isoformat&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        b_hex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;            b_hex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hexlify&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;            header &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;            ppm &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;            csum &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;            &lt;br /&gt;            header_hex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hexlify&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;header&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            ppm_hex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hexlify&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ppm&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            csum_hex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hexlify&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;csum&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            ppm_value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ppm&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;256&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ppm&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;ts&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; packet(&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;b_hex&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;) header=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;header_hex&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; csum=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;csum_hex&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; ppm=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;ppm_hex&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; = &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;ppm_value&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; ppm co2&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;ts&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; packet(&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;b_hex&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;)&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I left it running for a bit and it settled at around 950 ppm CO2 inside the room. I then started soldering something else next to the CO2 sensor and it quickly picked up:&lt;/p&gt;
&lt;pre class=&quot;language-log&quot;&gt;&lt;code class=&quot;language-log&quot;&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:08.014641&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b0a093f03b2012e0000e20623db&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23db&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03b2&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;946&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:09.037387&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b0a093f03b2012e0000e20623db&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23db&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03b2&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;946&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:10.044122&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b07093b03b2012e0000e20623d4&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23d4&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03b2&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;946&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:11.050926&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b07093b03b2012e0000e20623d4&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23d4&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03b2&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;946&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:12.057797&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b07093b03b2012e0000e20623d4&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23d4&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03b2&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;946&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:13.064491&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b02093a03b4012e0000e20623d0&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23d0&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03b4&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;948&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:14.071280&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b02093a03b4012e0000e20623d0&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23d0&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03b4&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;948&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:15.077977&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b02093a03b4012e0000e20623d0&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23d0&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03b4&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;948&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:16.084777&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0afb093003b8012e0000e20623c2&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23c2&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03b8&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;952&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:17.107521&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0afb093003b8012e0000e20623c2&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23c2&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03b8&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;952&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:18.114331&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0afb093003b8012e0000e20623c2&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23c2&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03b8&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;952&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:19.121116&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0afb093003b8012e0000e20623c2&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23c2&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03b8&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;952&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:20.127911&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0aeb092d03c1012e0000e20623b8&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23b8&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03c1&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;961&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:21.134689&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0aeb092d046c012e0000e2062364&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;2364&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;046c&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1132&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:22.141476&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0aeb092d046c012e0000e2062364&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;2364&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;046c&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1132&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:23.148319&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0ada092504de012e0000e20623bd&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23bd&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;04de&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1246&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:24.171109&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0ada092504de012e0000e20623bd&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23bd&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;04de&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1246&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:25.177861&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0ada092504de012e0000e20623bd&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23bd&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;04de&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1246&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:26.184629&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0ac30924057d012e0000e2062345&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;2345&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;057d&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1405&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:27.191431&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0ac30924057d012e0000e2062345&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;2345&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;057d&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1405&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:28.198240&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0ac30924057d012e0000e2062345&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;2345&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;057d&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1405&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:29.205051&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0ab60925061d012e0000e20623da&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23da&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;061d&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1565&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:30.211891&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0ab60925061d012e0000e20623da&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23da&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;061d&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1565&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:31.218762&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0ab60925061d012e0000e20623da&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23da&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;061d&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1565&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:32.241478&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0ab60925061d012e0000e20623da&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23da&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;061d&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1565&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:33.248238&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0aac092406b6012e0000e2062368&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;2368&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;06b6&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1718&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:34.254979&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0aac092406b6012e0000e2062368&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;2368&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;06b6&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1718&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:35.261803&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0aac092406b6012e0000e2062368&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;2368&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;06b6&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1718&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:36.268576&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0aab09220710012e0000e20623c0&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23c0&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;0710&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1808&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:37.275443&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0aab09220710012e0000e20623c0&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23c0&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;0710&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1808&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:38.282322&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0aab09220710012e0000e20623c0&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23c0&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;0710&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1808&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:39.305054&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0aa909240743012e0000e20623f3&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23f3&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;0743&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1859&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:40.311911&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0aa909240743012e0000e20623f3&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23f3&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;0743&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1859&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:41.318726&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0aa909240743012e0000e20623f3&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23f3&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;0743&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1859&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:42.325456&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0aaa0925074a012e0000e20623fc&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23fc&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;074a&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1866&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:43.332344&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0aaa0925074a012e0000e20623fc&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23fc&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;074a&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1866&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:44.339140&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0aaa0925074a012e0000e20623fc&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23fc&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;074a&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1866&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:45.346040&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0aaa0925074a012e0000e20623fc&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;23fc&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;074a&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1866&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:46.352791&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0aaa0927074e012e0000e2062302&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;2302&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;074e&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1870&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:25:47.375527&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0aaa0927074e012e0000e2062302&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;2302&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;074e&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1870&lt;/span&gt; ppm co2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Around 10 minutes later the soldering fumes (do they actually contain CO2?) seemed to have dispersed:&lt;/p&gt;
&lt;pre class=&quot;language-log&quot;&gt;&lt;code class=&quot;language-log&quot;&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:33:53.650576&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b08094003e7012e0000e206230f&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;230f&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03e7&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;999&lt;/span&gt; ppm co2 &lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:33:54.657334&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b08094003e7012e0000e206230f&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;230f&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03e7&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;999&lt;/span&gt; ppm co2 &lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:33:55.664082&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b08094003e7012e0000e206230f&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;230f&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03e7&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;999&lt;/span&gt; ppm co2 &lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:33:56.670883&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b08094003e7012e0000e206230f&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;230f&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03e7&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;999&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:33:57.677658&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b08093f03e7012e0000e206230e&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;230e&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03e7&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;999&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:33:58.700458&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b08093f03e7012e0000e206230e&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;230e&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03e7&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;999&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:33:59.707267&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b08093f03e7012e0000e206230e&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;230e&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03e7&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;999&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:34:00.713988&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b08094003e7012e0000e206230f&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;230f&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03e7&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;999&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:34:01.720828&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b08094003e7012e0000e206230f&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;230f&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03e7&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;999&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:34:02.727688&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b08094003e7012e0000e206230f&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;230f&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03e7&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;999&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:34:03.734467&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b08094103e7012e0000e2062310&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;2310&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03e7&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;999&lt;/span&gt; ppm co2&lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:34:04.741267&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b08094103e7012e0000e2062310&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;2310&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;03e7&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;999&lt;/span&gt; ppm co2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I then opened the window with a good draft and it quickly approached the outside CO2 air concentration:&lt;/p&gt;
&lt;pre class=&quot;language-log&quot;&gt;&lt;code class=&quot;language-log&quot;&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:41:47.796995&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b4e095401a3012e0000e2062323&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;2323&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;01a3&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;419&lt;/span&gt; ppm co2 &lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:41:48.803683&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b4e095401a0012e0000e2062320&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;2320&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;01a0&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;416&lt;/span&gt; ppm co2 &lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:41:49.810507&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b4e095401a0012e0000e2062320&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;2320&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;01a0&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;416&lt;/span&gt; ppm co2 &lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:41:50.817180&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b4e095401a0012e0000e2062320&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;2320&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;01a0&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;416&lt;/span&gt; ppm co2 &lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:41:51.823981&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b4e0954019f012e0000e206231f&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;231f&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;019f&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;415&lt;/span&gt; ppm co2 &lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:41:52.846689&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b4e0954019f012e0000e206231f&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;231f&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;019f&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;415&lt;/span&gt; ppm co2 &lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:41:53.853479&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b4e0954019f012e0000e206231f&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;231f&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;019f&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;415&lt;/span&gt; ppm co2 &lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:41:54.860302&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b4e0954019e012e0000e206231e&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;231e&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;019e&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;414&lt;/span&gt; ppm co2 &lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:41:55.867120&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b4e0954019e012e0000e206231e&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;231e&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;019e&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;414&lt;/span&gt; ppm co2 &lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:41:56.873917&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b4e0954019e012e0000e206231e&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;231e&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;019e&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;414&lt;/span&gt; ppm co2 &lt;br /&gt;&lt;span class=&quot;token date number&quot;&gt;2022-08-21T&lt;/span&gt;&lt;span class=&quot;token time number&quot;&gt;13:41:57.880692&lt;/span&gt; packet&lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d0b4e0954019e012e0000e206231e&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt; header&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;424d&#39;&lt;/span&gt; csum&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;231e&#39;&lt;/span&gt; ppm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;b&lt;span class=&quot;token string&quot;&gt;&#39;019e&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;414&lt;/span&gt; ppm co2 &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nice!&lt;/p&gt;
&lt;p&gt;Also since I was wondering, usual CO2 levels are:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;400 ppm: average outdoor air level.&lt;br /&gt;
400–1,000 ppm: typical level found in occupied spaces with good air exchange.&lt;br /&gt;
1,000–2,000 ppm: level associated with complaints of drowsiness and poor air.&lt;br /&gt;
2,000–5,000 ppm: level associated with headaches, sleepiness, and stagnant, stale, stuffy air.&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://www.dhs.wisconsin.gov/chemical/carbondioxide.htm&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Wisconsin Department of Health Services&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content>
		</entry>
		
		<entry>
			<title>A blog with Eleventy on Codeberg</title>
			<link href="https://spezifisch.codeberg.page/posts/2022-08-22/a-blog-with-eleventy-on-codeberg/"/>
			<updated>2022-08-22T00:00:00Z</updated>
			<id>https://spezifisch.codeberg.page/posts/2022-08-22/a-blog-with-eleventy-on-codeberg/</id>
			<content type="html">&lt;p&gt;I was looking for a quick way to write down some project notes. This inevitably led me to Static Site Generators and I especially looked at &lt;a href=&quot;https://www.netlifycms.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Netlify CMS&lt;/a&gt; and &lt;a href=&quot;https://jekyllrb.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Jekyll&lt;/a&gt;. But I didn&#39;t want to rely on 3rd party services and CDNs to serve some simple pages with occasional images.&lt;/p&gt;
&lt;p&gt;In the end I picked a very simple &lt;a href=&quot;https://www.11ty.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Eleventy&lt;/a&gt; configuration based on &lt;a href=&quot;https://github.com/11ty/eleventy-base-blog&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;https://github.com/11ty/eleventy-base-blog&lt;/a&gt; which doesn&#39;t include any resources from 3rd parties at all.&lt;/p&gt;
&lt;h2 id=&quot;codeberg-caveats&quot; tabindex=&quot;-1&quot;&gt;Codeberg Caveats &lt;a class=&quot;direct-link&quot; href=&quot;https://spezifisch.codeberg.page/posts/2022-08-22/a-blog-with-eleventy-on-codeberg/#codeberg-caveats&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://codeberg.org/Codeberg/org/src/branch/main/TermsOfUse.md&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;Codeberg&#39;s Terms of Service&lt;/a&gt; are quite restrictive compared to Github or Gitlab in allowing only free software licensed projects and excluding &amp;quot;stale mirrors&amp;quot; and reserving the right to remove inactive repositories. (Completely understandable if you don&#39;t have unlimited resources like Microsoft/Github.) But they explicitly allow &amp;quot;really small &amp;amp; personal stuff like your journal, config files, ideas or notes&amp;quot; even for private repositories so I think this site is covered by that policy.&lt;/p&gt;
&lt;p&gt;Using the base template the output files are generated in the &lt;code&gt;_site&lt;/code&gt; directory:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// These are all optional (defaults are shown):&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token literal-property property&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;_includes&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;_data&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;_site&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.codeberg.org/codeberg-pages/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;According to codeberg.pages&lt;/a&gt; the files are expected to be either in the root directory of the &lt;code&gt;pages&lt;/code&gt; repository or in a &lt;code&gt;pages&lt;/code&gt; branch of any other repository.&lt;/p&gt;
&lt;p&gt;I didn&#39;t like the idea of polluting my source repository with generated data so I came up with this solution using a Git submodule:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;rm&lt;/span&gt; -rf _site&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; submodule &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; git@codeberg.org:spezifisch/pages.git _site&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; build&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now all the generated data is located inside another repository which I can purge as I like if it gets too big.&lt;/p&gt;
&lt;p&gt;To use this repository conveniently I added a custom script to my &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;  &lt;span class=&quot;token string-property property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;/* ... */&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string-property property&quot;&gt;&quot;publish&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;eleventy &amp;amp;&amp;amp; git submodule foreach &#39;git add . &amp;amp;&amp;amp; git commit -am deploy &amp;amp;&amp;amp; git push origin main&#39;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using &lt;code&gt;pnpm run publish&lt;/code&gt; I can now rebuild my site, commit whatever changed and push it to my &lt;code&gt;pages&lt;/code&gt; repository so it is immediately publicly accessible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; The command has since gotten &lt;a href=&quot;https://codeberg.org/spezifisch/spezifisch-pages/src/commit/ca3e3739226d8fc4c70c977d1c8938e6af508827/package.json#L12&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer external&quot;&gt;a bit more complicated&lt;/a&gt;. It would be nice to have a cleaner solution than this.&lt;/p&gt;
</content>
		</entry>
</feed>