<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Technology &#8211; Thejesh GN</title>
	<atom:link href="https://thejeshgn.com/category/technology/feed/" rel="self" type="application/rss+xml" />
	<link>https://thejeshgn.com</link>
	<description>A container for all my views with excerpts from technology, travel, films, books, kannada, friends and other interests. I am Thejesh GN, friends call me Thej.</description>
	<lastBuildDate>Mon, 20 Apr 2026 09:45:23 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://thejeshgn.com/wp-content/uploads/2015/08/cropped-thejeshgn_icon1-150x150.png</url>
	<title>Technology &#8211; Thejesh GN</title>
	<link>https://thejeshgn.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">9645742</site>	<item>
		<title>30 Days of DXing</title>
		<link>https://thejeshgn.com/2026/04/20/30-days-of-dxing/</link>
					<comments>https://thejeshgn.com/2026/04/20/30-days-of-dxing/#respond</comments>
		
		<dc:creator><![CDATA[Thejesh GN]]></dc:creator>
		<pubDate>Mon, 20 Apr 2026 09:45:23 +0000</pubDate>
				<category><![CDATA[Life]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[30DayChallenges]]></category>
		<category><![CDATA[30DaysOfDXing]]></category>
		<category><![CDATA[Amateur (ham) Radio]]></category>
		<category><![CDATA[DXing]]></category>
		<category><![CDATA[SWL]]></category>
		<guid isPermaLink="false">https://thejeshgn.com/?p=38666</guid>

					<description><![CDATA[#30DaysOfDXing is where I am trying to receive various radio wave transmissions and listen to them using either my Radio, SDR, etc. I am currently using ShortwaveSchedule, Short-Wave Info, ShorWave DB, QSL.net, etc. as my sources. Look at the project page for more details. I am a radio enthusiast and also an E&#38;C&#46;&#46;&#46;]]></description>
										<content:encoded><![CDATA[
<p><a href="https://thejeshgn.com/projects/30-days-of-dxing/" target="_blank" rel="noreferrer noopener">#30DaysOfDXing</a> is where I am trying to receive various radio wave transmissions and listen to them using either my <a href="https://thejeshgn.com/2022/03/14/swl-shortwave-listening-using-eton-elite-traveler/" target="_blank" rel="noreferrer noopener">Radio</a>, <a href="https://thejeshgn.com/2018/10/22/getting-started-with-software-defined-radio-using-rtl-sdr/" target="_blank" rel="noreferrer noopener">SDR</a>, etc. I am currently using <a href="https://shortwaveschedule.com" target="_blank" rel="noreferrer noopener">ShortwaveSchedule</a>, <a href="https://www.short-wave.info" target="_blank" rel="noreferrer noopener">Short-Wave Info</a>, <a href="https://shortwavedb.org/" target="_blank" rel="noreferrer noopener">ShorWave DB</a>, <a href="https://admin.qsl.net/index.php" target="_blank" rel="noreferrer noopener">QSL.net</a>, etc. as my sources. Look at the project page for more details.</p>



<p>I am a radio enthusiast and also an E&amp;C engineer. Though fields and waves, or antenna theory, were not my favorite subjects in engineering, I love radio waves and listening to them. I think if I had more practice-oriented classes during my engineering studies, I would have loved those subjects much more than I do now that I practice. May be something for me to remember when I teach or share.</p>



<p class="has-luminous-vivid-amber-background-color has-background">DXing, taken from DX, the telegraphic shorthand for &#8220;distance&#8221; or &#8220;distant&#8221;, is the hobby of receiving and identifying distant radio or television signals, or making two-way radio contact with distant stations in amateur radio, citizens band radio or other two-way radio communications. &#8211; <a href="https://en.wikipedia.org/wiki/DXing">Wikipedia</a></p>



<p>If you are new to Short Wave Listening (SWL) or the HAM radio world, <a href="https://www.dxing.info/introduction.dx" target="_blank" rel="noreferrer noopener">DXing</a> generally means listening to distant signals. Still, I am not limiting myself to only the &#8220;distant&#8221; signals in this project, nor to radio or television signals. I am going to try local signals as well, for example, <a href="https://en.wikipedia.org/wiki/Non-directional_beacon" target="_blank" rel="noreferrer noopener">Non-Directional Beacon (NDB)</a> from Bangalore Airport is a fair game. I might also do local FM stations, just to learn. The focus is on learning and trying new things.</p>



<figure class="wp-block-audio aligncenter"><audio controls src="https://thejeshgn.com/wp-content/uploads/2026/04/sw-13710khz-20260415-1834gmt-bengaluru.mp3"></audio><figcaption class="wp-element-caption"><em>SW 13710kHz on 20260415 at 1834 GMT heard in Bengaluru. Transmission by China Radio International, Kunming Anning, China. Received using Eton Elite Traveler.</em></figcaption></figure>



<p>My plan is not to spend more than 30 minutes a day on this. I should be able to achieve it. I have already started logging them at <a href="https://thejeshgn.com/projects/30-days-of-dxing/" target="_blank" rel="noreferrer noopener">#30DaysOfDXing</a>, along with all the details. If it interests you, contact me. Maybe we can do it together.</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><a href="https://thejeshgn.com/wp-content/uploads/2021/01/wp-16118952817434718948263944756454-scaled.jpg" rel="lightbox[38666]"><img fetchpriority="high" decoding="async" width="1024" height="768" data-id="19700" src="https://thejeshgn.com/wp-content/uploads/2021/01/wp-16118952817434718948263944756454-1024x768.jpg" alt="NESDR and RH 795" class="wp-image-19700" srcset="https://thejeshgn.com/wp-content/uploads/2021/01/wp-16118952817434718948263944756454-1024x768.jpg 1024w, https://thejeshgn.com/wp-content/uploads/2021/01/wp-16118952817434718948263944756454-300x225.jpg 300w, https://thejeshgn.com/wp-content/uploads/2021/01/wp-16118952817434718948263944756454-768x576.jpg 768w, https://thejeshgn.com/wp-content/uploads/2021/01/wp-16118952817434718948263944756454-1536x1152.jpg 1536w, https://thejeshgn.com/wp-content/uploads/2021/01/wp-16118952817434718948263944756454-2048x1536.jpg 2048w, https://thejeshgn.com/wp-content/uploads/2021/01/wp-16118952817434718948263944756454-720x540.jpg 720w, https://thejeshgn.com/wp-content/uploads/2021/01/wp-16118952817434718948263944756454-520x390.jpg 520w, https://thejeshgn.com/wp-content/uploads/2021/01/wp-16118952817434718948263944756454-320x240.jpg 320w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><figcaption class="wp-element-caption">NESDR and  RH 795 with SMA Male to BNC Female cable.</figcaption></figure>



<figure class="wp-block-image size-large"><a href="https://thejeshgn.com/wp-content/uploads/2022/03/wp-1647254739433-scaled.jpg" rel="lightbox[38666]"><img decoding="async" width="1024" height="768" data-id="21656" src="https://thejeshgn.com/wp-content/uploads/2022/03/wp-1647254739433-1024x768.jpg" alt="Eton Elite Traveler Radio" class="wp-image-21656" srcset="https://thejeshgn.com/wp-content/uploads/2022/03/wp-1647254739433-1024x768.jpg 1024w, https://thejeshgn.com/wp-content/uploads/2022/03/wp-1647254739433-300x225.jpg 300w, https://thejeshgn.com/wp-content/uploads/2022/03/wp-1647254739433-768x576.jpg 768w, https://thejeshgn.com/wp-content/uploads/2022/03/wp-1647254739433-1536x1152.jpg 1536w, https://thejeshgn.com/wp-content/uploads/2022/03/wp-1647254739433-2048x1536.jpg 2048w, https://thejeshgn.com/wp-content/uploads/2022/03/wp-1647254739433-720x540.jpg 720w, https://thejeshgn.com/wp-content/uploads/2022/03/wp-1647254739433-520x390.jpg 520w, https://thejeshgn.com/wp-content/uploads/2022/03/wp-1647254739433-320x240.jpg 320w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><figcaption class="wp-element-caption">Eton Elite Traveler Radio</figcaption></figure>
</figure>



<hr class="wp-block-separator has-text-color has-vivid-purple-color has-alpha-channel-opacity has-vivid-purple-background-color has-background is-style-dots"/>



<div class="wp-block-columns has-background is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex" style="background:linear-gradient(137deg,rgb(255,206,236) 0%,rgb(152,150,240) 62%)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:5px"></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p></p>



<p>You can read this blog using <a href="https://feeds.thejeshgn.com/thejeshgn" target="_blank" rel="noreferrer noopener">RSS Feed</a>. But if you are the person who loves getting emails, then you can join my readers by <a href="https://thejeshgn.com/subscribe/" target="_blank" rel="noreferrer noopener">signing</a> up.</p>


<div class="wp-block-jetpack-subscriptions__supports-newline wp-block-jetpack-subscriptions__show-subs wp-block-jetpack-subscriptions">
		<div>
			<div>
				<div>
					<p style="width: 25%;max-width: 100%;">
						<a href="https://thejeshgn.com/?post_type=post&#038;p=38666" style="width: calc(100% - 10px);font-size: 16px;padding: 15px 23px 15px 23px;margin: 0; margin-left: 10px;border-color: black;border-radius: 6px;border-width: 2px; background-color: #000000; color: #FFFFFF; text-decoration: none; white-space: nowrap; margin-left: 0">Subscribe</a>
					</p>
				</div>
			</div>
		</div>
	</div></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:5px"></div>
</div>



<p> </p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://thejeshgn.com/2026/04/20/30-days-of-dxing/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		<enclosure url="https://thejeshgn.com/wp-content/uploads/2026/04/sw-13710khz-20260415-1834gmt-bengaluru.mp3" length="927952" type="audio/mpeg" />

		<post-id xmlns="com-wordpress:feed-additions:1">38666</post-id>	</item>
		<item>
		<title>Running Bash programs on Moodle CodeRunner</title>
		<link>https://thejeshgn.com/2026/03/09/running-bash-programs-on-moodle-coderunner/</link>
					<comments>https://thejeshgn.com/2026/03/09/running-bash-programs-on-moodle-coderunner/#respond</comments>
		
		<dc:creator><![CDATA[Thejesh GN]]></dc:creator>
		<pubDate>Mon, 09 Mar 2026 06:09:45 +0000</pubDate>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[CodeRunner]]></category>
		<category><![CDATA[Free and Open Source]]></category>
		<category><![CDATA[MooC]]></category>
		<category><![CDATA[Moodle]]></category>
		<location><![CDATA[APU]]></location>
		<guid isPermaLink="false">https://thejeshgn.com/?p=38270</guid>

					<description><![CDATA[I have been teaching a course at APU that includes Bash scripting. I have a love-hate relationship with Bash. It&#8217;s a weird combination of a programming language and an iterative CLI. It&#8217;s confusing, easy to make mistakes, and hard to debug, but on the other hand, it&#8217;s available on almost all systems. It&#46;&#46;&#46;]]></description>
										<content:encoded><![CDATA[
<p>I have been teaching a <a href="https://thejeshgn.com/projects/exploring-digital-technology/">course at APU</a> that includes Bash scripting. I have a love-hate relationship with <a href="https://www.gnu.org/software/bash/manual/html_node/index.html" target="_blank" rel="noreferrer noopener">Bash</a>. It&#8217;s a weird combination of a programming language and an iterative CLI. It&#8217;s confusing, easy to make mistakes, and hard to debug, but on the other hand, it&#8217;s available on almost all systems. It builds on the power of CLI and CLI tools available in the OS. Easy to write small and useful scripts that can automate your daily painful work. Hence, it&#8217;s worth knowing a bit of it even today.</p>



<p>The platform we (APU) use is Moodle. I have been in the MOOC industry for a decade now, and I have heard of Moodle so much, but this is the first time I have used it to run a course. To run a programming course, you will need an easy programming environment to challenge students. In my previous cases, we have used <a href="https://nsjail.dev/" target="_blank" rel="noreferrer noopener">an NSJail-based</a> environment with <a href="https://thejeshgn.com/tag/course-builder/">CourseBuilder</a> (now called Seek), which works really well. But in this case, for <a href="https://moodle.org/">Moodle</a>, it&#8217;s <a href="https://coderunner.org.nz/" target="_blank" rel="noreferrer noopener">CodeRunner</a> plugin. It seems fairly easy to use. That said, Bash is not supported out of the box as user language. So I had to use Python to make it possible. This also assumes the environment (CodeRunner/JOBE) has Bash installed, though not directly accessible through API as user language.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; auto-links: false; title: ; notranslate">
import subprocess

script = &quot;&quot;&quot;{{ TEST.testcode | e(&#039;py&#039;) }}&quot;&quot;&quot; + &#039;\n&#039; + &quot;&quot;&quot;{{ STUDENT_ANSWER | e(&#039;py&#039;) }}&quot;&quot;&quot; + &#039;\n&#039; + &quot;&quot;&quot;{{ TEST.extra | e(&#039;py&#039;) }}&quot;&quot;&quot;
input = &quot;&quot;&quot;{{ TEST.stdin | e(&#039;py&#039;) }}&quot;&quot;&quot;

with open(&#039;__prog__.sh&#039;, &#039;w&#039;) as outfile:
    outfile.write(script)

result = subprocess.run(&#x5B;&#039;/bin/bash&#039;, &#039;__prog__.sh&#039;], capture_output=True, text=True, input=input, timeout=5)
stdout = result.stdout.strip()
print(stdout)
</pre></div>


<p>The template Python code takes the <code>TEST.testcod</code>e and prepends it to the user-entered <code>STUDENT_ANSWER</code> code. It also takes <code>TEST.extra</code> code and appends it. Then, it runs it as a bash script using Python subprocess by passing <code>TEST.stdin</code> as input. Captures STDOUT and prints it for comparison.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><a href="https://thejeshgn.com/wp-content/uploads/2026/03/python_code_customization_to_run_bash.png" rel="lightbox[38270]"><img decoding="async" width="1024" height="419" src="https://thejeshgn.com/wp-content/uploads/2026/03/python_code_customization_to_run_bash-1024x419.png" alt="" class="wp-image-38267" srcset="https://thejeshgn.com/wp-content/uploads/2026/03/python_code_customization_to_run_bash-1024x419.png 1024w, https://thejeshgn.com/wp-content/uploads/2026/03/python_code_customization_to_run_bash-300x123.png 300w, https://thejeshgn.com/wp-content/uploads/2026/03/python_code_customization_to_run_bash-768x314.png 768w, https://thejeshgn.com/wp-content/uploads/2026/03/python_code_customization_to_run_bash-1536x629.png 1536w, https://thejeshgn.com/wp-content/uploads/2026/03/python_code_customization_to_run_bash-2048x838.png 2048w, https://thejeshgn.com/wp-content/uploads/2026/03/python_code_customization_to_run_bash-720x295.png 720w, https://thejeshgn.com/wp-content/uploads/2026/03/python_code_customization_to_run_bash-520x213.png 520w, https://thejeshgn.com/wp-content/uploads/2026/03/python_code_customization_to_run_bash-320x131.png 320w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><figcaption class="wp-element-caption">CodeRunner in Moodle. Customization using Templates for using Python to run Bash scripts written by user.</figcaption></figure>
</div>


<p>Example Question 1: Read an input as <code>score</code>. If the <code>score</code> is greater than or equal to 40, then print <code>P</code>. If the score is less than 40, then print <code>U</code>.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><a href="https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_inputs.png" rel="lightbox[38270]"><img loading="lazy" decoding="async" width="1024" height="504" src="https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_inputs-1024x504.png" alt="" class="wp-image-38268" srcset="https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_inputs-1024x504.png 1024w, https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_inputs-300x148.png 300w, https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_inputs-768x378.png 768w, https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_inputs-1536x756.png 1536w, https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_inputs-2048x1008.png 2048w, https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_inputs-720x354.png 720w, https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_inputs-520x256.png 520w, https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_inputs-320x157.png 320w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a><figcaption class="wp-element-caption">CodeRunner in Moodle. Example Bash question test case where STDIO is read and used. </figcaption></figure>
</div>


<p>Example Question 2: Write a function called <code>cube</code>. If a number is passed, it returns the cube of that number. For example <code>cube 5 # will return 25</code></p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><a href="https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_additional_code.png" rel="lightbox[38270]"><img loading="lazy" decoding="async" width="1024" height="504" src="https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_additional_code-1024x504.png" alt="" class="wp-image-38269" srcset="https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_additional_code-1024x504.png 1024w, https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_additional_code-300x148.png 300w, https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_additional_code-768x378.png 768w, https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_additional_code-1536x756.png 1536w, https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_additional_code-2048x1008.png 2048w, https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_additional_code-720x354.png 720w, https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_additional_code-520x256.png 520w, https://thejeshgn.com/wp-content/uploads/2026/03/bash_question_testcase_with_additional_code-320x157.png 320w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a><figcaption class="wp-element-caption">CodeRunner in Moodle. Example Bash question where we want to call a function user has written at the end. So it can be tested.</figcaption></figure>
</div>


<p>We did about three in-class labs using this. It worked well for all our cases. Though I must say we tried only Bash basics. Maybe there are use cases where this might break, but I think for most of it, this should work. </p>



<hr class="wp-block-separator has-text-color has-vivid-purple-color has-alpha-channel-opacity has-vivid-purple-background-color has-background is-style-dots"/>



<div class="wp-block-columns has-background is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex" style="background:linear-gradient(137deg,rgb(255,206,236) 0%,rgb(152,150,240) 62%)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:5px"></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p></p>



<p>You can read this blog using <a href="https://feeds.thejeshgn.com/thejeshgn" target="_blank" rel="noreferrer noopener">RSS Feed</a>. But if you are the person who loves getting emails, then you can join my readers by <a href="https://thejeshgn.com/subscribe/" target="_blank" rel="noreferrer noopener">signing</a> up.</p>


<div class="wp-block-jetpack-subscriptions__supports-newline wp-block-jetpack-subscriptions__show-subs wp-block-jetpack-subscriptions">
		<div>
			<div>
				<div>
					<p style="width: 25%;max-width: 100%;">
						<a href="https://thejeshgn.com/?post_type=post&#038;p=38270" style="width: calc(100% - 10px);font-size: 16px;padding: 15px 23px 15px 23px;margin: 0; margin-left: 10px;border-color: black;border-radius: 6px;border-width: 2px; background-color: #000000; color: #FFFFFF; text-decoration: none; white-space: nowrap; margin-left: 0">Subscribe</a>
					</p>
				</div>
			</div>
		</div>
	</div></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:5px"></div>
</div>



<p> </p>
]]></content:encoded>
					
					<wfw:commentRss>https://thejeshgn.com/2026/03/09/running-bash-programs-on-moodle-coderunner/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">38270</post-id>	</item>
		<item>
		<title>Motorola T82 Extreme is my PMR446 Walkie-Talkie</title>
		<link>https://thejeshgn.com/2026/02/09/motorola-t82-extreme-is-my-pmr446-walkie-talkie/</link>
					<comments>https://thejeshgn.com/2026/02/09/motorola-t82-extreme-is-my-pmr446-walkie-talkie/#respond</comments>
		
		<dc:creator><![CDATA[Thejesh GN]]></dc:creator>
		<pubDate>Mon, 09 Feb 2026 01:50:54 +0000</pubDate>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[Amateur (ham) Radio]]></category>
		<category><![CDATA[Gadgets]]></category>
		<category><![CDATA[Geek-Fun]]></category>
		<category><![CDATA[India 🇮🇳]]></category>
		<category><![CDATA[Motorcycling]]></category>
		<category><![CDATA[Review]]></category>
		<category><![CDATA[Riding Gear]]></category>
		<category><![CDATA[Travel Gadgets]]></category>
		<guid isPermaLink="false">https://thejeshgn.com/?p=38059</guid>

					<description><![CDATA[As a kid, I always wanted a set of Walkie Talkies to chat with friends. We got cell phones as adults, but that desire remained. A few years back, I took the HAM exam, but then COVID hit us, and I didn&#8217;t apply for a call sign. Now I have to retake the&#46;&#46;&#46;]]></description>
										<content:encoded><![CDATA[
<p>As a kid, I always wanted a set of Walkie Talkies to chat with friends. We got cell phones as adults, but that desire remained. A few years back, I took the HAM exam, but then COVID hit us, and I didn&#8217;t apply for a call sign. Now I have to retake the exams online, as the system now doesn&#8217;t allow me to apply for a call sign with paper exam results. I will take that exam again. But in the meantime, I also wanted something that I could use with friends and family who I don&#8217;t think are interested in taking an exam or applying for a license. Hence this search.</p>






<p>I do have a kind of Walkie-talkie in the form of <a href="https://thejeshgn.com/2024/10/30/review-bluarmor-c30-mesh-intercom-for-bikers/">Helmet communications</a>. There are some issues with using it as a general communications device. Some that matter to me are</p>



<ul class="wp-block-list">
<li>It&#8217;s not open; it&#8217;s proprietary. The only part of that ecosystem that is open is <a href="https://community.sena.com/hc/en-us/articles/360001060263-Universal-Intercom-Pairing" target="_blank" rel="noreferrer noopener">Sena&#8217;s Universal Intercom protocol</a>, which uses Bluetooth. It comes with its own limitations.</li>



<li>It&#8217;s made for helmet communication. Even if you plan to reuse it, it&#8217;s challenging to use in other situations due to its form factor.</li>



<li>It uses 2.5 GHz (the same frequency range as Bluetooth, Wi-Fi, etc.), so its range is somewhat limited. In our tests, it was around 700-900m direct line of sight. Some support a proprietary mesh network to increase the range.</li>
</ul>



<h3 class="wp-block-heading">Requirements</h3>



<p>With all this, I was left to look for a walkie-talkie. </p>



<ul class="wp-block-list">
<li>Is the license free in India? Ideally, across the world, but at least in India.</li>



<li>Open protocols</li>



<li>Decent range</li>



<li>Multi purpose</li>



<li>Easy to use, it shouldn&#8217;t take more than five minutes to learn its functionality</li>



<li>Easy to manage, charge, and rugged. I plan to use it everywhere.</li>



<li>Widely recognized brand and model. So I don&#8217;t get stopped at the airport and other places while I am carrying it.</li>



<li>Supported in India</li>



<li>True walkie-talkie, not PTT over the cell network.</li>
</ul>



<h3 class="wp-block-heading">License-free and open</h3>



<p>There are two bands used for public, license-free communication without encryption. The <a target="_blank" href="https://en.wikipedia.org/wiki/Family_Radio_Service" rel="noreferrer noopener">Family Radio Service (FRS)</a> is used in the USA and Canada. FRS operates between 462.5625 MHz and 467.7125 MHz. And <a target="_blank" href="https://en.wikipedia.org/wiki/PMR446" rel="noreferrer noopener">PMR446 (private mobile radio</a> used in most of Europe and India.</p>



<p>PMR446 is a private mobile radio that operates between 446.0 &#8211; 446.2 MHz in India. It&#8217;s a license-free band, as per the <a target="_blank" href="https://thejeshgn.com/documents/delicensing-low-power-and-very-low-power-short-range-radio-frequency-devices-g-s-r-1047e/" rel="noreferrer noopener">Gazette Notification G.S.R. 1047(E)</a>. Since I am dealing with India, I will focus on it. The rules limit output power to 0.5 W. It doesn&#8217;t seem like much, but you can reach a kilometer or more in urban conditions with buildings and trees. A 5KM to 10KM in direct line of sight conditions. It&#8217;s much better than my helmet communication system. The rules allow channel spacing of 6.25 kHz (Digital) and 12.5 kHz (analog). The frequency range can accommodate 16 analog or 32 digital channels. I have a dedicated page about <a target="_blank" href="https://thejeshgn.com/wiki/notebook/pmr446-personal-mobile-radio-at-446-mhz/" rel="noreferrer noopener">the PMR446 band, channels, channel guarding</a>, etc. The most important thing to know is that we need to select walkie-talkies that operate PMR446 in India.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><a href="https://thejeshgn.com/documents/delicensing-low-power-and-very-low-power-short-range-radio-frequency-devices-g-s-r-1047e/"><img loading="lazy" decoding="async" width="898" height="375" src="https://thejeshgn.com/wp-content/uploads/2024/11/GSR1047E-PMR446.png" alt="G.S.R. 1047(E) [PART II—SEC. 3(i)] Table V - Personal Mobile Radio at 446 MHz" class="wp-image-32962" srcset="https://thejeshgn.com/wp-content/uploads/2024/11/GSR1047E-PMR446.png 898w, https://thejeshgn.com/wp-content/uploads/2024/11/GSR1047E-PMR446-300x125.png 300w, https://thejeshgn.com/wp-content/uploads/2024/11/GSR1047E-PMR446-768x321.png 768w, https://thejeshgn.com/wp-content/uploads/2024/11/GSR1047E-PMR446-720x301.png 720w, https://thejeshgn.com/wp-content/uploads/2024/11/GSR1047E-PMR446-520x217.png 520w, https://thejeshgn.com/wp-content/uploads/2024/11/GSR1047E-PMR446-320x134.png 320w" sizes="auto, (max-width: 898px) 100vw, 898px" /></a><figcaption class="wp-element-caption">G.S.R. 1047(E) [PART II—SEC. 3(i)] Table V &#8211; Personal Mobile Radio at 446 MHz</figcaption></figure>
</div>


<p class="has-luminous-vivid-amber-background-color has-background">I have shown the snippet from the Gazette and given link to the rules for your reference. Please get your own legal advice. I am not a lawyer, and this is not legal advice.</p>



<p>The protocols are standardized, and most follow the ETSI Harmonised European Standard. Since this is something all <a target="_blank" href="https://en.wikipedia.org/wiki/PMR446#Technical_information" rel="noreferrer noopener">vendors use</a>, if someone wants to build, the protocol is not a secret sauce. This also makes the technology and walkies vendor-neutral.</p>



<h3 class="wp-block-heading">Privacy</h3>



<p>There is no encryption on PMR446. So you shouldn&#8217;t communicate anything that needs encryption on these bands. <mark style="background-color:#fcb900" class="has-inline-color">There is no privacy.</mark> There are ways to keep a set of people independent by using a specific channel and Squelch (CTSS or DCS). Squelch is called PL (Private Line) tone, CG (Channel Guard) tone, or QC (Quiet Channel) tone, depending on the vendor, but all refer to the same functionality. Basically, you use a channel and a specific CTSS or DCS code to keep your conversations independent from others. This setup doesn&#8217;t stop others from using the same combo and listening to you.</p>



<h3 class="wp-block-heading">Motorola T82 Extreme</h3>



<p>Armed with this information and requirement, I started my search, and my final list was</p>



<ul class="wp-block-list">
<li>Motorola Talkabout T82 Extreme &#8211; <a href="https://thejeshgn.com/documents/motorola-talkabout-t82-extreme-user-manual/">Manual</a></li>



<li>Kenwood TK 3501</li>



<li>Wavex PT100</li>



<li>Vertel Team Talkie Radio</li>



<li>Sanchar G3U</li>



<li>Baofeng GT-68 PMR Walkie Talkie</li>
</ul>



<p>If I had money, I would have tried each of them and then decided which to go with. Based on the information I found online, I went with the T82 by Motorola. If you have any other model listed here, let me know, and we can test them together.</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-2 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><a href="https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-freebrochure-1-scaled.png" rel="lightbox[38059]"><img loading="lazy" decoding="async" width="724" height="1024" data-id="38062" src="https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-freebrochure-1-724x1024.png" alt="" class="wp-image-38062" srcset="https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-freebrochure-1-724x1024.png 724w, https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-freebrochure-1-212x300.png 212w, https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-freebrochure-1-768x1086.png 768w, https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-freebrochure-1-1086x1536.png 1086w, https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-freebrochure-1-1448x2048.png 1448w, https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-freebrochure-1-720x1018.png 720w, https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-freebrochure-1-520x736.png 520w, https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-freebrochure-1-320x453.png 320w, https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-freebrochure-1-scaled.png 1810w" sizes="auto, (max-width: 724px) 100vw, 724px" /></a></figure>



<figure class="wp-block-image size-large"><a href="https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-free-brochure-2-scaled.png" rel="lightbox[38059]"><img loading="lazy" decoding="async" width="724" height="1024" data-id="38063" src="https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-free-brochure-2-724x1024.png" alt="" class="wp-image-38063" srcset="https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-free-brochure-2-724x1024.png 724w, https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-free-brochure-2-212x300.png 212w, https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-free-brochure-2-768x1086.png 768w, https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-free-brochure-2-1086x1536.png 1086w, https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-free-brochure-2-1448x2048.png 1448w, https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-free-brochure-2-720x1018.png 720w, https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-free-brochure-2-520x736.png 520w, https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-free-brochure-2-320x453.png 320w, https://thejeshgn.com/wp-content/uploads/2026/02/motorola-t82-extreme-walkie-talkies-licence-free-brochure-2-scaled.png 1810w" sizes="auto, (max-width: 724px) 100vw, 724px" /></a></figure>
</figure>



<p></p>



<p>In terms of cost, it wasn&#8217;t cheap; a pair of them cost me INR 18,000. It came in a package with two walkies, a carrying case, a charger, NIMH rechargeable batteries, earpieces, belt clips, etc. It&#8217;s probably the most expensive pair. G3U or PT100 is half the price.  </p>



<h4 class="wp-block-heading">Things I like</h4>



<ol class="wp-block-list">
<li>Brand recognition. Everyone knows Motorola. It also looks colorful, playful, rugged, and harmless. The support in India is good, and the community around it is also good. I have had other Motorola phones (pre-smartphone era cell phones), and they have been very rugged and have had very positive experiences in general.</li>



<li>It&#8217;s rugged. It can be used in conditions where others can&#8217;t be used. I won&#8217;t be scared to put it on a motorcycle handlebar or hang it outside my backpack. I am also not worried about water splashing on it (IPX4), though I wouldn&#8217;t submerge it. Also, not worried about accidentally dropping them.</li>



<li>A replaceable, rechargeable, 800 mAh NIMH battery powers it. It can be recharged using a micro USB charger. That said, you can replace the NIMH battery with 3 x AA Alkaline batteries in an emergency. The promised battery life is around 18 hours. Even if it&#8217;s just half, I will be happy. One can also upgrade to a 1300 mAh NiMH battery if required.</li>



<li>It comes with a headset with a boom mic. That makes it usable inside the helmet if you want, or use it hands-free. It has VOX/iVOX (Internal Voice Operated / Voice Operated Transmission ), which lets you transmit without pressing a button. It has three levels of sensitivity for voice activation. By default, it is in PTT (Push To Talk) mode, where you need to press and hold a button to talk. In VOX mode, sound activates the transmission. So all you do is speak, and the radio will transmit for you. This is especially useful when you are riding or doing manual work.</li>



<li>It supports 8 PMR Channels. User expandable to 16 Channels in countries where it&#8217;s allowed by government authorities. And 121 Sub-Codes (38 CTCSS Codes &amp; 83 DCS codes). So, there are plenty of options to isolate your group from others.</li>



<li>It&#8217;s very easy to use. Manuals and settings are very easy. It probably takes less than 5 minutes to teach someone to start using it effectively.</li>



<li>It has an Emergency Alert Mode with a dedicated button that can signal members of your group for help.</li>



<li>It has dual-channel monitoring. It lets you listen to two channels and engage with the primary one.</li>



<li>There are other features, such as a flashlight, roger Tone, channel monitoring and scanning, easy pairing, etc.</li>



<li>The range has been good. I was able to get at least 1 KM in urban settings with buildings. I continue to do more range tests. I will write a separate post about it.</li>



<li>As such, there is no limit to the number of folks you can have on the same channel. This is true for most Walkie-Talkies.</li>
</ol>



<h4 class="wp-block-heading">Things to improve</h4>



<ol class="wp-block-list">
<li>Micro USB. It should have been USB-C even if it was just 5 watts.</li>



<li>Price. It is expensive. I am looking to try Wavex PT100 and Vertel, which are half the price. If you have, let me know. We can compare.</li>
</ol>



<h3 class="wp-block-heading">Conclusion</h3>



<p>Should you get it? Maybe. It depends on your need. But once you start using it, you will see how useful it becomes. Especially if you ride or drive a lot in teams, or have a farm or work in a place with poor or no network, trek, or run events, etc.  As far as me, I am still going to use <a href="https://thejeshgn.com/2024/10/30/review-bluarmor-c30-mesh-intercom-for-bikers/">BluArmor</a> while riding. But I will surely carry it along with me. </p>



<p>It&#8217;s also a good entry point into HAM radio. It&#8217;s good enough to generate interest among kids and adults about analog communications.</p>



<hr class="wp-block-separator has-text-color has-vivid-purple-color has-alpha-channel-opacity has-vivid-purple-background-color has-background is-style-dots"/>



<div class="wp-block-columns has-background is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex" style="background:linear-gradient(137deg,rgb(255,206,236) 0%,rgb(152,150,240) 62%)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:5px"></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p></p>



<p>You can read this blog using <a href="https://feeds.thejeshgn.com/thejeshgn" target="_blank" rel="noreferrer noopener">RSS Feed</a>. But if you are the person who loves getting emails, then you can join my readers by <a href="https://thejeshgn.com/subscribe/" target="_blank" rel="noreferrer noopener">signing</a> up.</p>


<div class="wp-block-jetpack-subscriptions__supports-newline wp-block-jetpack-subscriptions__show-subs wp-block-jetpack-subscriptions">
		<div>
			<div>
				<div>
					<p style="width: 25%;max-width: 100%;">
						<a href="https://thejeshgn.com/?post_type=post&#038;p=38059" style="width: calc(100% - 10px);font-size: 16px;padding: 15px 23px 15px 23px;margin: 0; margin-left: 10px;border-color: black;border-radius: 6px;border-width: 2px; background-color: #000000; color: #FFFFFF; text-decoration: none; white-space: nowrap; margin-left: 0">Subscribe</a>
					</p>
				</div>
			</div>
		</div>
	</div></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:5px"></div>
</div>



<p> </p>
]]></content:encoded>
					
					<wfw:commentRss>https://thejeshgn.com/2026/02/09/motorola-t82-extreme-is-my-pmr446-walkie-talkie/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">38059</post-id>	</item>
		<item>
		<title>One Video Format</title>
		<link>https://thejeshgn.com/2026/01/19/one-video-format/</link>
					<comments>https://thejeshgn.com/2026/01/19/one-video-format/#respond</comments>
		
		<dc:creator><![CDATA[Thejesh GN]]></dc:creator>
		<pubDate>Mon, 19 Jan 2026 18:14:45 +0000</pubDate>
				<category><![CDATA[Podcast]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Free and Open Source]]></category>
		<category><![CDATA[Web 🌐]]></category>
		<guid isPermaLink="false">https://thejeshgn.com/?p=37947</guid>

					<description><![CDATA[I have a bunch of screen-casts that I want to release. I have not found a good PeerTube instance to host my videos. I am happy to pay a monthly fee, and I hope one day there will be a platform that provides this. For now, I will add them to my site,&#46;&#46;&#46;]]></description>
										<content:encoded><![CDATA[
<p>I have a bunch of screen-casts that I want to release. I have not found a good PeerTube instance to host my videos. I am happy to pay a monthly fee, and I hope one day there will be a platform that provides this.</p>



<p> For now, I will add them to my site, Archive.org, and YouTube. They are all CC-BY-SA. </p>



<p>I am thinking of uploading just one version of each video. No server side transcoding. Hence, I want to convert the videos into the most optimized format and size. I have been doing <a href="https://thejeshgn.com/wiki/styles/video-formatting-and-embedding/">some experiments</a> and seem to have found decent settings. Its 1080p or 720p with 24 frames. </p>



<p>If its mobile screencast then I will pad them with color, so it can be a proper FHD or HD.</p>



<p>I am going to use WebM as the container with AV1 as video codec and Opus as audio codec. Most screencasts could be just 720p.</p>



<figure class="wp-block-video aligncenter"><video height="720" style="aspect-ratio: 1280 / 720;" width="1280" controls src="https://thejeshgn.com/wp-content/uploads/2026/01/osmand_route_using_gpx_720p_24fps_bgcolor.webm"></video><figcaption class="wp-element-caption">OSMAnd routing using GPX file, 720p aka HD, <strong>2</strong> minutes, 17 seconds, 10MB</figcaption></figure>



<p></p>



<figure class="wp-block-video aligncenter"><video height="1080" style="aspect-ratio: 1920 / 1080;" width="1920" controls src="https://thejeshgn.com/wp-content/uploads/2026/01/Big_Buck_Bunny_1080_10s_1MB.webm"></video><figcaption class="wp-element-caption">Test video &#8211; Big Buck Bunny 1080p, 10 seconds, 1 MB file size</figcaption></figure>



<hr class="wp-block-separator has-text-color has-vivid-purple-color has-alpha-channel-opacity has-vivid-purple-background-color has-background is-style-dots"/>



<div class="wp-block-columns has-background is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex" style="background:linear-gradient(137deg,rgb(255,206,236) 0%,rgb(152,150,240) 62%)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:5px"></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p></p>



<p>You can read this blog using <a href="https://feeds.thejeshgn.com/thejeshgn" target="_blank" rel="noreferrer noopener">RSS Feed</a>. But if you are the person who loves getting emails, then you can join my readers by <a href="https://thejeshgn.com/subscribe/" target="_blank" rel="noreferrer noopener">signing</a> up.</p>


<div class="wp-block-jetpack-subscriptions__supports-newline wp-block-jetpack-subscriptions__show-subs wp-block-jetpack-subscriptions">
		<div>
			<div>
				<div>
					<p style="width: 25%;max-width: 100%;">
						<a href="https://thejeshgn.com/?post_type=post&#038;p=37947" style="width: calc(100% - 10px);font-size: 16px;padding: 15px 23px 15px 23px;margin: 0; margin-left: 10px;border-color: black;border-radius: 6px;border-width: 2px; background-color: #000000; color: #FFFFFF; text-decoration: none; white-space: nowrap; margin-left: 0">Subscribe</a>
					</p>
				</div>
			</div>
		</div>
	</div></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:5px"></div>
</div>



<p> </p>
]]></content:encoded>
					
					<wfw:commentRss>https://thejeshgn.com/2026/01/19/one-video-format/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		<enclosure url="https://thejeshgn.com/wp-content/uploads/2026/01/osmand_route_using_gpx_720p_24fps_bgcolor.webm" length="10511359" type="video/webm" />
<enclosure url="https://thejeshgn.com/wp-content/uploads/2026/01/Big_Buck_Bunny_1080_10s_1MB.webm" length="1059560" type="video/webm" />

		<post-id xmlns="com-wordpress:feed-additions:1">37947</post-id>	</item>
		<item>
		<title>Turning My Laptops into a Dask Distributed Cluster</title>
		<link>https://thejeshgn.com/2025/12/25/turning-my-laptops-into-a-dask-distributed-cluster/</link>
					<comments>https://thejeshgn.com/2025/12/25/turning-my-laptops-into-a-dask-distributed-cluster/#respond</comments>
		
		<dc:creator><![CDATA[Thejesh GN]]></dc:creator>
		<pubDate>Thu, 25 Dec 2025 04:46:37 +0000</pubDate>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[Code]]></category>
		<category><![CDATA[Dask Python Library]]></category>
		<category><![CDATA[Distributed Computing]]></category>
		<category><![CDATA[Distributed Task Queue]]></category>
		<category><![CDATA[Free and Open Source]]></category>
		<guid isPermaLink="false">https://thejeshgn.com/?p=37681</guid>

					<description><![CDATA[I wanted a distributed system where the client submitting the job has the code and logic, but the workers don&#8217;t. They just execute the workload they get. I had used Dask in the LocalCluster mode, where everything runs on the same computer but in different processes/threads. It works well, and I have used&#46;&#46;&#46;]]></description>
										<content:encoded><![CDATA[
<p>I wanted a distributed system where the client submitting the job has the code and logic, but the workers don&#8217;t. They just execute the workload they get. I had used <a href="https://www.dask.org/">Dask</a> in the LocalCluster mode, where everything runs on the same computer but in different processes/threads. It works well, and I have used it a bunch of times. But this time, I wanted many heterogeneous computing systems to come together and execute the work aka <a href="https://distributed.dask.org/en/stable/">Dask Distributed</a>.</p>






<h3 class="wp-block-heading">Introduction</h3>



<p>I knew Dask supports this through a scheduler and worker model. But I had not tried this. So I did, and it works pretty well. But you need to take care of some additional stuff.</p>



<ol class="wp-block-list">
<li>The client, scheduler, and workers should all use the same version of Python and the same version of Python packages. So it&#8217;s better to pin the package and the Python version. I have used Podman/Docker to run the scheduler and the workers. So I have tied down the image version and hence the versions of Python, Dask, Distributed, and their dependencies. I have used the identical versions for my client. See the header files in the client &#8211; distributed_dask.py</li>



<li>The workers won&#8217;t have additional packages. You can either create a new image of the worker with your required packages, or you can dynamically install them using the PipInstall plugin. This plugin takes an array of Python packages and installs them on all workers as they start. I have also pinned these packages Easy way to ship packages to workers.</li>



<li>I have created a small plugin here called EnvPlugin. This runs on all the workers. Essentially, it copies all the ENV variables from the worker.env file to the client or worker environment. That way, it&#8217;s easy to ship that as well.</li>



<li><a href="https://github.com/cloudpipe/cloudpickle" data-type="link" data-id="https://github.com/cloudpipe/cloudpickle">cloudpickle</a> is used to serialize the code to ship it from client to workers</li>



<li>Some of the other pinned packages lkelz4, msgpack etc are used by dask and sitributed.</li>
</ol>



<h3 class="wp-block-heading">Client Code</h3>



<p>This is the code you probably run from your machine. That connects to scheduler, send code and jobs so it can get the work done.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
# /// script
# requires-python = &quot;==3.10.12&quot;
# dependencies = &#x5B;
#   &quot;dask==2024.8.2&quot;,
#   &quot;distributed==2024.8.2&quot;,
#   &quot;toolz==0.12.0&quot;,
#   &quot;lz4==4.3.3&quot;,
#   &quot;tornado==6.4.1&quot;,
#   &quot;cloudpickle==3.0.0&quot;,
#   &quot;msgpack==1.0.8&quot;,
#   &quot;python-dotenv==1.1.1&quot;,
#   &quot;requests==2.32.5&quot;
# ]
# ///
import os
import json
import logging
import requests
import csv
from dotenv import dotenv_values

from dask import bag as db
from dask.distributed import (
    Client,
    LocalCluster,
    WorkerPlugin,
    PipInstall,
)

logging.basicConfig(level=logging.INFO, format=&quot;%(levelname)s %(message)s&quot;)


# ---- EnvPlugin plugin -------------------------------------------------

class EnvPlugin(WorkerPlugin):
    def __init__(self, env):
        self.env = env

    def setup(self, worker):
        logging.warning(&quot;EnvPlugin loaded with keys: %s&quot;, list(self.env.keys()))
        os.environ.update(self.env)


URL = &quot;https://httpbin.org/post&quot;
HEADERS = {&quot;accept&quot;: &quot;application/json&quot;}

# -- Acutal thing that runs on workers -----------------------------------------
def post_row(row: dict) -&gt; dict:
    &quot;&quot;&quot;Send one row as JSON payload&quot;&quot;&quot;
    API_KEY_FOR_HTTP_BIN = os.environ.get(&quot;API_KEY_FOR_HTTP_BIN&quot;)
    HEADERS&#x5B;&quot;x-api-key&quot;] = API_KEY_FOR_HTTP_BIN
    print(API_KEY_FOR_HTTP_BIN)
    r = requests.post(URL, json=row, headers=HEADERS, timeout=20)
    r.raise_for_status()
    return r.json()


def main(csv_path: str):
    with open(csv_path, newline=&quot;&quot;, encoding=&quot;utf-8&quot;) as f:
        records = list(csv.DictReader(f))

    env_vars = dotenv_values(&quot;worker.env&quot;)
    print(&quot;==========env_vars for workers=============&quot;)
    print(env_vars)
    os.environ.update(env_vars)

    scheduler_address = os.environ.get(&quot;DASK_SCHEDULER_ADDRESS&quot;)

    logging.info(f&quot;Connecting to remote scheduler at {scheduler_address}&quot;)
    client = Client(scheduler_address)

    # Setup env on the workers
    client.register_plugin(
        EnvPlugin(env_vars), name=&quot;env&quot;
    )

    # Install packages on the workers
    plugin = PipInstall(
        packages=&#x5B;
            &quot;python-dotenv==1.1.1&quot;,
            &quot;requests==2.32.5&quot;,
        ],
        pip_options=&#x5B;&quot;--upgrade&quot;],
    )
    client.register_plugin(plugin)

    b = db.from_sequence(records, partition_size=10)
    # Send to workers
    results = b.map(post_row).compute()
    client.close()

    # Print gathered results
    print(results)
    logging.info(f&quot;Completed {len(results)} requests&quot;)


if __name__ == &quot;__main__&quot;:
    import sys
    if len(sys.argv) != 2:
        print(&quot;uv run distributed_dask.py data.csv&quot;)
        raise SystemExit(1)

    main(sys.argv&#x5B;1])

</pre></div>

<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="1000" height="579" src="https://thejeshgn.com/wp-content/uploads/2023/12/mismatched-versions-error.png" alt="" class="wp-image-37684" srcset="https://thejeshgn.com/wp-content/uploads/2023/12/mismatched-versions-error.png 1000w, https://thejeshgn.com/wp-content/uploads/2023/12/mismatched-versions-error-300x174.png 300w, https://thejeshgn.com/wp-content/uploads/2023/12/mismatched-versions-error-768x445.png 768w, https://thejeshgn.com/wp-content/uploads/2023/12/mismatched-versions-error-720x417.png 720w, https://thejeshgn.com/wp-content/uploads/2023/12/mismatched-versions-error-520x301.png 520w, https://thejeshgn.com/wp-content/uploads/2023/12/mismatched-versions-error-320x185.png 320w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /><figcaption class="wp-element-caption">Mismatched versions error</figcaption></figure>
</div>


<h3 class="wp-block-heading">Setup and Run</h3>



<p>I am using Podman/Docker to run the scheduler and workers. It makes things easy and keeps the environment clean. I have used the <a href="https://docs.dask.org/en/latest/deploying-docker.html" target="_blank" rel="noreferrer noopener">Docker images provided</a> by the project. But in your case, you might want to build them and harden them. Especially the workers, as they will run whatever the client sends. So you will have to be careful if you are running a worker.</p>



<p> Create a network and then run the scheduler in that network ( you could run as part of host too). It starts the scheduler and starts listening for workers to join on port 8786. And exposes a dashboard on another port 8787. That you can access by going to <a href="http://localhost:8787" target="_blank" rel="noreferrer noopener">http://localhost:8787</a></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
podman network create dask

# start the scheduler
podman run --network dask \
 -p 8787:8787 -p 8686:8686 \
 --name scheduler \
 ghcr.io/dask/dask:2024.8.2 dask-scheduler 
</pre></div>


<p>Now you can create as many workers as you want and make them join the scheduler. Here I have two. </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
# start the worker 1
podman run -d \
  --name dask-worker-1 \
  --network dask \
  ghcr.io/dask/dask:2024.8.2 \
  dask-worker scheduler:8786 \
  --nworkers 1 \
  --nthreads 2 \
  --worker-port 9000:9009

# start the worker 2
podman run -d \
  --name dask-worker-2 \
  --network dask \
  ghcr.io/dask/dask:2024.8.2 \
  dask-worker scheduler:8786 \
  --nworkers 1 \
  --nthreads 2 \
  --worker-port 9000:9009
</pre></div>


<p>Clients use two ports. One 8786 port on the scheduler to notify them of joining. And another random port so the scheduler can talk to them. It usually auto-picks. But you can also specify a range, so it assigns one of the available ones in that range. So give some additional ports. If there are 8 workers then give a range of 9 or more. This also helps you recognize the open ports easily. You can check the scheduler logs to see if the workers have joined. You can also check the web dashboard to do the same.</p>



<p>Now just submit the jobs by calling our client script.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
export DASK_SCHEDULER_ADDRESS=tcp://127.0.0.1:8786
uv run distributed_dask.py data.csv
</pre></div>

<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="538" src="https://thejeshgn.com/wp-content/uploads/2023/12/dask_dashboard-1024x538.png" alt="" class="wp-image-37683" srcset="https://thejeshgn.com/wp-content/uploads/2023/12/dask_dashboard-1024x538.png 1024w, https://thejeshgn.com/wp-content/uploads/2023/12/dask_dashboard-300x157.png 300w, https://thejeshgn.com/wp-content/uploads/2023/12/dask_dashboard-768x403.png 768w, https://thejeshgn.com/wp-content/uploads/2023/12/dask_dashboard-1536x806.png 1536w, https://thejeshgn.com/wp-content/uploads/2023/12/dask_dashboard-720x378.png 720w, https://thejeshgn.com/wp-content/uploads/2023/12/dask_dashboard-520x273.png 520w, https://thejeshgn.com/wp-content/uploads/2023/12/dask_dashboard-320x168.png 320w, https://thejeshgn.com/wp-content/uploads/2023/12/dask_dashboard.png 1846w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">Dask Dashboard while jobs are running</figcaption></figure>
</div>


<p>That&#8217;s it. Now the client should be able to connect to the scheduler, send code and setup instructions to workers, then send jobs to workers, collect all the responses, and print all of them. </p>



<h3 class="wp-block-heading">Usage</h3>



<p>The example I have shown here seems trivial. But I built a CPU and network heave pipeline for processing I used this logic to parse Bihar SIR data for The Hindu (<a href="https://thejeshgn.com/2025/08/29/weekly-notes-35-2025/">WN-35/2025</a>) in a really short time. It was a CPU and network-intensive job. I could use the combined power of all my laptops to run this and get the job done so that they could run the story. I felt like I was running a supercomputer at home.</p>



<p>Also note what I have not shown in the example client code but it shouldn&#8217;t be difficult to add.</p>



<ol class="wp-block-list">
<li>Error handling. I have not done any here. Any error will break it</li>



<li>Retry mechanism</li>



<li>There is no real or persistent message queue here. So, at some point, if the scheduler goes down, you will lose work.</li>



<li>Hardening the workers. Anyway, as a worker, you shouldn&#8217;t join a scheduler you don&#8217;t trust, as they can send any kind of code to execute on the worker.</li>
</ol>



<hr class="wp-block-separator has-text-color has-vivid-purple-color has-alpha-channel-opacity has-vivid-purple-background-color has-background is-style-dots"/>



<div class="wp-block-columns has-background is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex" style="background:linear-gradient(137deg,rgb(255,206,236) 0%,rgb(152,150,240) 62%)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:5px"></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p></p>



<p>You can read this blog using <a href="https://feeds.thejeshgn.com/thejeshgn" target="_blank" rel="noreferrer noopener">RSS Feed</a>. But if you are the person who loves getting emails, then you can join my readers by <a href="https://thejeshgn.com/subscribe/" target="_blank" rel="noreferrer noopener">signing</a> up.</p>


<div class="wp-block-jetpack-subscriptions__supports-newline wp-block-jetpack-subscriptions__show-subs wp-block-jetpack-subscriptions">
		<div>
			<div>
				<div>
					<p style="width: 25%;max-width: 100%;">
						<a href="https://thejeshgn.com/?post_type=post&#038;p=37681" style="width: calc(100% - 10px);font-size: 16px;padding: 15px 23px 15px 23px;margin: 0; margin-left: 10px;border-color: black;border-radius: 6px;border-width: 2px; background-color: #000000; color: #FFFFFF; text-decoration: none; white-space: nowrap; margin-left: 0">Subscribe</a>
					</p>
				</div>
			</div>
		</div>
	</div></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:5px"></div>
</div>



<p> </p>
]]></content:encoded>
					
					<wfw:commentRss>https://thejeshgn.com/2025/12/25/turning-my-laptops-into-a-dask-distributed-cluster/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">37681</post-id>	</item>
		<item>
		<title>Mapping My Motorcycle Ride Through Songs</title>
		<link>https://thejeshgn.com/2025/12/08/mapping-my-motorcycle-ride-through-songs/</link>
					<comments>https://thejeshgn.com/2025/12/08/mapping-my-motorcycle-ride-through-songs/#respond</comments>
		
		<dc:creator><![CDATA[Thejesh GN]]></dc:creator>
		<pubDate>Mon, 08 Dec 2025 07:11:26 +0000</pubDate>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[Travel]]></category>
		<category><![CDATA[ListenBrainz]]></category>
		<category><![CDATA[Motorcycling]]></category>
		<category><![CDATA[Music]]></category>
		<category><![CDATA[MusicBrainz]]></category>
		<guid isPermaLink="false">https://thejeshgn.com/?p=37410</guid>

					<description><![CDATA[Once I finished blogging my recent ride to Kupalli and back, I thought it would be great to map the songs to the location. I was scrobbling my music to ListenBrainz, and any GPS was logged as usual. Using the good old way of using timestamps to match things, I mapped location to&#46;&#46;&#46;]]></description>
										<content:encoded><![CDATA[
<p>Once I finished blogging my recent ride to <a href="https://thejeshgn.com/2025/12/02/ride-to-kuvempus-kuppalli/">Kupalli and back</a>, I thought it would be great to map the songs to the location. I was scrobbling my music to ListenBrainz, and any GPS was logged as usual. Using the good old way of using timestamps to match things, I mapped location to songs and produced a geojson. Here is the map of them. Since day 1 and day 3 were the longest, I heard as many songs as possible. I have added day 2 for completeness, but I didn&#8217;t listen to much music that day.</p>



<p>The songs are mainly from the playlists <a href="https://listenbrainz.org/playlist/5de8742c-5fbc-4074-90d7-287eb957e712/" target="_blank" rel="noreferrer noopener">&#8220;Road Trip Across the World</a>&#8221; and <a href="https://listenbrainz.org/playlist/6e04b98f-aae2-4986-93b8-83ca5e1011d0/" target="_blank" rel="noreferrer noopener">&#8220;Road Trip Across Karnataka</a>&#8220;.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="581" src="https://thejeshgn.com/wp-content/uploads/2025/12/Screenshot-from-2025-12-07-00-33-19-1024x581.png" alt="Mapping My Motorcycle Ride Through Songs" class="wp-image-37475" srcset="https://thejeshgn.com/wp-content/uploads/2025/12/Screenshot-from-2025-12-07-00-33-19-1024x581.png 1024w, https://thejeshgn.com/wp-content/uploads/2025/12/Screenshot-from-2025-12-07-00-33-19-300x170.png 300w, https://thejeshgn.com/wp-content/uploads/2025/12/Screenshot-from-2025-12-07-00-33-19-768x436.png 768w, https://thejeshgn.com/wp-content/uploads/2025/12/Screenshot-from-2025-12-07-00-33-19-720x408.png 720w, https://thejeshgn.com/wp-content/uploads/2025/12/Screenshot-from-2025-12-07-00-33-19-520x295.png 520w, https://thejeshgn.com/wp-content/uploads/2025/12/Screenshot-from-2025-12-07-00-33-19-320x182.png 320w, https://thejeshgn.com/wp-content/uploads/2025/12/Screenshot-from-2025-12-07-00-33-19.png 1220w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">Mapping My Motorcycle Ride Through Songs</figcaption></figure>
</div>


<h3 class="wp-block-heading">Day 1</h3>


<p><div class="leaflet-map WPLeafletMap" style="height:550px; width:99%;"></div><script>
window.WPLeafletMapPlugin = window.WPLeafletMapPlugin || [];
window.WPLeafletMapPlugin.push(function WPLeafletMapShortcode() {/*<script>*/
var baseUrl = atob('aHR0cHM6Ly97c30udGlsZS5vcGVuc3RyZWV0bWFwLm9yZy97en0ve3h9L3t5fS5wbmc=');
var base = (!baseUrl && window.MQ) ?
    window.MQ.mapLayer() : L.tileLayer(baseUrl,
        L.Util.extend({}, {
            detectRetina: 0,
        },
        {"subdomains":"abc","id":"track-20251128-04-listenbrainz","noWrap":false,"maxZoom":20}        )
    );
    var options = L.Util.extend({}, {
        layers: [base],
        attributionControl: false
    },
    {"zoomControl":true,"scrollWheelZoom":true,"doubleClickZoom":true,"fitBounds":true,"minZoom":1,"maxZoom":20,"maxBounds":null,"attribution":"<a href=\"http:\/\/www.openstreetmap.org\/copyright\">OpenStreetMap<\/a> contributors, <a href=\"http:\/\/leafletjs.com\" title=\"A JS library for interactive maps\">Leaflet<\/a> and Thejesh GN."},
    {});
window.WPLeafletMapPlugin.createMap(options).setView([22.6610714,],5);window.WPLeafletMapPlugin.createScale({});});</script>
<script>
window.WPLeafletMapPlugin = window.WPLeafletMapPlugin || [];
window.WPLeafletMapPlugin.push(function WPLeafletjsonShortcode() {/*<script>*/
var src = '&amp;#8221;https://data.thejeshgn.com/geojson/track-20251128-04-listenbrainz.geojson&amp;#8221;';
var default_style = {"radius":5};
var rewrite_keys = {
    stroke : 'color',
    'stroke-width' : 'weight',
    'stroke-opacity' : 'opacity',
    fill : 'fillColor',
    'fill-opacity' : 'fillOpacity',
};
// htmlspecialchars converts & to "&amp;"; maybe unnecessarily, and maybe 3x
var ampersandRegex = /&(?:amp;){1,3}/g
var layer = L.ajaxGeoJson(src.replace(ampersandRegex, '&'), {
    type: 'json',
    style : layerStyle,
    onEachFeature : onEachFeature,
    pointToLayer: pointToLayer
});
var fitbounds = 0;
var circleMarker = 1;
var popup_text = window.WPLeafletMapPlugin.unescape("&lt;img width=&quot;250px&quot; src=&quot;{thumbnail_url}&quot;&gt;&lt;br /&gt;&lt;strong&gt;&lt;a target=&quot;_blank&quot; href=&quot;{track_url}&quot;/&gt;{track_name}&lt;/a&gt;&lt;/strong&gt; &lt;br /&gt; {artist_name}&lt;br /&gt;&lt;br&gt;");
var popup_property = "";
var table_view = 0;
var group = window.WPLeafletMapPlugin.getCurrentGroup();
var markerOptions = window.WPLeafletMapPlugin.getIconOptions({});
layer.addTo( group );
window.WPLeafletMapPlugin.geojsons.push( layer );
if (fitbounds) {
    layer.on('ready', function () {
        this.map.fitBounds( this.getBounds() );
    });
}
function layerStyle (feature) {
    var props = feature.properties || {};
    var style = {};
    function camelFun (_, first_letter) {
        return first_letter.toUpperCase();
    };
    for (var key in props) {
        if (key.match('-')) {
            var camelcase = key.replace(/-(\w)/, camelFun);
            style[ camelcase ] = props[ key ];
        }
        // rewrite style keys from geojson.io
        if (rewrite_keys[ key ]) {
            style[ rewrite_keys[ key ] ] = props[ key ];
        }
    }
    return L.Util.extend(style, default_style);
}
function onEachFeature (feature, layer) {
    var props = feature.properties || {};
    var text;
    if (table_view) {
        text = window.WPLeafletMapPlugin.propsToTable(props);
    } else {
        text = popup_property
            ? props[ popup_property ]
            : window.WPLeafletMapPlugin.template(
                popup_text, 
                feature.properties
            );
    }
    if (text) {
        layer.bindPopup( text );
    }
}
    function pointToLayer (feature, latlng) {
    if (circleMarker) {
        return L.circleMarker(latlng);
    }
    return L.marker(latlng, markerOptions);
}});</script>

<script><!--
window.WPLeafletMapPlugin=window.WPLeafletMapPlugin||[];window.WPLeafletMapPlugin.push(function(){let all_properties=["track_name"];let att_zoom="12";let att_propertyName="track_name7";if(typeof searchcontrol=="undefined"){var maps=[];var searchcontrol=[];}
var map=window.WPLeafletMapPlugin.getCurrentMap();var map_id=map._leaflet_id;if(typeof maps[map_id]=="undefined"){maps[map_id]=map;searchcontrol[map_id]=[];}
if(typeof searchcontrol[map_id][att_propertyName]=="undefined"){searchcontrol[map_id][att_propertyName]=att_propertyName;console.log("mapid: "+map_id);console.log(all_properties);console.log("propertyName: \"track_name7\", zoom: 12, collapsed: true, autoCollapseTime: 1200, textErr: \"Location not found\", textPlaceholder: \"Search...\", hideMarkerOnCollapse: true, position: \"topleft\", marker: {}, openPopup: true,");searchLayer=new L.LayerGroup();var duplicates=[];}
for(var i=0;i<WPLeafletMapPlugin.markers.length;i++){if(WPLeafletMapPlugin.markers[i]._map!==null){if(map_id==WPLeafletMapPlugin.markers[i]._map._leaflet_id){let a=WPLeafletMapPlugin.markers[i];let leafextsearch="";for(let i=0;i<all_properties.length;i++){att_property=all_properties[i];let this_options=a.getIcon().options;if(this_options.hasOwnProperty(att_property)){if(a.options[att_property]){leafextsearch=leafextsearch.concat(' | ',a.options[att_property]);}else{leafextsearch=leafextsearch.concat(' | ',this_options[att_property]);}}else
if(att_property=="popupContent"){if(a.getPopup()){if(a.getPopup().getContent()){let this_content=a.getPopup().getContent();this_content=this_content.replace(/<p>/ig,' ');this_content=this_content.replace(/<br>/ig,' ');this_content=this_content.replace(/(<([^>]+)>)/ig,'');this_content=this_content.replace(/ \| /g,'');this_content=this_content.replace(/ +/g,' ');leafextsearch=leafextsearch.concat(' | ',this_content);}}}
else{if(a.options[att_property]&&a.options[att_property]!=""){leafextsearch=leafextsearch.concat(' | ',a.options[att_property]);}else{}}}
if(leafextsearch!=""){let search=leafextsearch;if(typeof duplicates[search]=="undefined"){duplicates[search]=1;}else{duplicates[search]=duplicates[search]+1;}}
if(leafextsearch!=""){a.options["searchindex"]=i;a.options[att_propertyName]=leafextsearch;searchLayer.addLayer(a);}}}}
var markergroups=window.WPLeafletMapPlugin.markergroups;Object.entries(markergroups).forEach(([key,value])=>{if(markergroups[key]._map!==null){if(map_id==markergroups[key]._map._leaflet_id){markergroups[key].eachLayer(function(layer){if(layer instanceof L.Marker){}else if(layer instanceof L.Polygon||layer instanceof L.Circle||layer instanceof L.Polyline){let leafextsearch="";for(let i=0;i<all_properties.length;i++){att_property=all_properties[i];if(layer.hasOwnProperty(att_property)){if(layer.options[att_property]){leafextsearch=leafextsearch.concat(' | ',layer.options[att_property]);}}else
if(att_property=="popupContent"){if(layer.getPopup()){if(layer.getPopup().getContent()){let this_content=layer.getPopup().getContent();this_content=this_content.replace(/<p>/ig,' ');this_content=this_content.replace(/<br>/ig,' ');this_content=this_content.replace(/(<([^>]+)>)/ig,'');this_content=this_content.replace(/ \| /g,'');this_content=this_content.replace(/ +/g,' ');leafextsearch=leafextsearch.concat(' | ',this_content);}}}
else{if(layer.options[att_property]&&layer.options[att_property]!=""){leafextsearch=leafextsearch.concat(' | ',layer.options[att_property]);}else{}}
if(leafextsearch!=""){let search=leafextsearch;if(typeof duplicates[search]=="undefined"){duplicates[search]=1;}else{duplicates[search]=duplicates[search]+1;}}
if(leafextsearch!=""){layer.options["searchindex"]=i;layer.options[att_propertyName]=leafextsearch;searchLayer.addLayer(layer);}}}else{}});}}});var geojsons=window.WPLeafletMapPlugin.geojsons;for(var j=0,len=geojsons.length;j<len;j++){if(map_id==geojsons[j]._map._leaflet_id){geojsons[j].on("ready",function(e){let duplicates=[];j=0;e.target.eachLayer(function(layer){let leafextsearch="";if(layer.options){for(let i=0;i<all_properties.length;i++){att_property=all_properties[i];if(layer.options[att_property]&&layer.options[att_property]!=""){leafextsearch=leafextsearch.concat(' | ',layer.options[att_property]);}}}
for(let i=0;i<all_properties.length;i++){att_property=all_properties[i];if(att_property=="popupContent"){if(layer.getPopup()){let this_content=layer.getPopup().getContent();this_content=this_content.replace(/<p>/ig,' ');this_content=this_content.replace(/<br>/ig,' ');this_content=this_content.replace(/(<([^>]+)>)/ig,'');this_content=this_content.replace(/ \| /g,'');this_content=this_content.replace(/ +/g,' ');leafextsearch=leafextsearch.concat(' | ',this_content);}}else{if(layer.feature.properties.hasOwnProperty(att_property)){leafextsearch=leafextsearch.concat(' | ',layer.feature.properties[att_property]);}}}
let search=leafextsearch;if(typeof duplicates[search]=="undefined"){duplicates[search]=1;}else{duplicates[search]=duplicates[search]+1;}
layer.feature.properties["searchindex"]=j;j++;layer.feature.properties[att_propertyName]=leafextsearch;});e.target.eachLayer(function(layer){let search=layer.feature.properties[att_propertyName];if(duplicates[search]>1){layer.feature.properties[att_propertyName]=layer.feature.properties[att_propertyName]+" | "+layer.feature.properties["searchindex"];}
layer.feature.properties[att_propertyName]=layer.feature.properties[att_propertyName].replace(/\| /,'');});});geojsons[j].addTo(searchLayer);}}
if(Object.keys(searchLayer._layers).length>0){searchLayer.eachLayer(function(layer){if(layer.options[att_propertyName]){let search=layer.options[att_propertyName];if(duplicates[search]>1){layer.options[att_propertyName]=layer.options[att_propertyName]+" | "+layer.options["searchindex"];}
layer.options[att_propertyName]=layer.options[att_propertyName].replace(/\| /,'');}});var SearchControl=new L.Control.Search({layer:searchLayer,propertyName:"track_name7",zoom:12,collapsed:true,autoCollapseTime:1200,textErr:"Location not found",textPlaceholder:"Search...",hideMarkerOnCollapse:true,position:"topleft",marker:{},openPopup:true,initial:false,moveToLocation:function(latlng,title,map){if(latlng.layer.feature&&latlng.layer.feature.geometry&&latlng.layer.feature.geometry.type=="Point"){map.fitBounds(L.latLngBounds([latlng]));map.setZoom(att_zoom);}else if(latlng.layer instanceof L.Marker){map.fitBounds(L.latLngBounds([latlng]));map.setZoom(att_zoom);}else if(latlng.layer instanceof L.Circle){if(latlng.layer._map){console.log("has map");map.fitBounds(latlng.layer.getBounds());var zoom=map.getBoundsZoom(latlng.layer.getBounds());map.setView(latlng,zoom);}else{map.addLayer(latlng.layer);map.fitBounds(latlng.layer.getBounds());var zoom=map.getBoundsZoom(latlng.layer.getBounds());map.setView(latlng,zoom);map.removeLayer(latlng.layer);}}else{map.fitBounds(latlng.layer.getBounds());var zoom=map.getBoundsZoom(latlng.layer.getBounds());map.setView(latlng,zoom);}}});map.addControl(SearchControl);SearchControl.on("search:locationfound",function(e){if(e.target._map.hasLayer(e.layer)){}else{e.layer.addTo(map);this._markerSearch._markeradd=e.layer;}
if(e.layer.getPopup())
e.layer.openPopup([e.latlng.lat,e.latlng.lng]);e.sourceTarget._input.blur();});SearchControl.on("search:cancel",function(e){if(this._markerSearch&&this._markerSearch._markeradd){e.target._map.removeLayer(this._markerSearch._markeradd);}
map.closePopup();if(e.target.options.hideMarkerOnCollapse&&this._markerSearch){e.target._map.removeLayer(this._markerSearch);}
e.sourceTarget._input.blur();});map.addLayer(searchLayer);}});</script>

<script><!--
window.WPLeafletMapPlugin=window.WPLeafletMapPlugin||[];window.WPLeafletMapPlugin.push(function(){var map=window.WPLeafletMapPlugin.getCurrentMap();var map_id=map._leaflet_id;var maps=[];maps[map_id]=map;var allfit=[];if(true&&typeof maps[map_id]._shouldFitBounds==="undefined"){allfit[map_id]=new L.latLngBounds();}
var position="topleft";let all_options={"zoomInTitle":"Zoom in","zoomOutTitle":"Zoom out","zoomHomeIcon":"home","zoomHomeTitle":"Home","fit":true,"position":"topleft"};leafext_zoomhome_js(maps,map_id,allfit,position,all_options);});</script>

<script><!--
window.WPLeafletMapPlugin=window.WPLeafletMapPlugin||[];window.WPLeafletMapPlugin.push(function(){var map=window.WPLeafletMapPlugin.getCurrentMap();var fsControl=new L.Control.FullScreen({position:"topleft"});map.addControl(fsControl);});</script>
</p>



<p></p>



<h3 class="wp-block-heading">Day 2</h3>


<p><div class="leaflet-map WPLeafletMap" style="height:550px; width:99%;"></div><script>
window.WPLeafletMapPlugin = window.WPLeafletMapPlugin || [];
window.WPLeafletMapPlugin.push(function WPLeafletMapShortcode() {/*<script>*/
var baseUrl = atob('aHR0cHM6Ly97c30udGlsZS5vcGVuc3RyZWV0bWFwLm9yZy97en0ve3h9L3t5fS5wbmc=');
var base = (!baseUrl && window.MQ) ?
    window.MQ.mapLayer() : L.tileLayer(baseUrl,
        L.Util.extend({}, {
            detectRetina: 0,
        },
        {"subdomains":"abc","noWrap":false,"maxZoom":20}        )
    );
    var options = L.Util.extend({}, {
        layers: [base],
        attributionControl: false
    },
    {"zoomControl":true,"scrollWheelZoom":true,"doubleClickZoom":true,"fitBounds":true,"minZoom":1,"maxZoom":20,"maxBounds":null,"attribution":"<a href=\"http:\/\/www.openstreetmap.org\/copyright\">OpenStreetMap<\/a> contributors, <a href=\"http:\/\/leafletjs.com\" title=\"A JS library for interactive maps\">Leaflet<\/a> and Thejesh GN."},
    {});
window.WPLeafletMapPlugin.createMap(options).setView([22.6610714,],5);window.WPLeafletMapPlugin.createScale({});});</script>
<script>
window.WPLeafletMapPlugin = window.WPLeafletMapPlugin || [];
window.WPLeafletMapPlugin.push(function WPLeafletjsonShortcode() {/*<script>*/
var src = '&amp;#8221;https://data.thejeshgn.com/geojson/track-20251129-08-listenbrainz.geojson&amp;#8221;';
var default_style = {"radius":10};
var rewrite_keys = {
    stroke : 'color',
    'stroke-width' : 'weight',
    'stroke-opacity' : 'opacity',
    fill : 'fillColor',
    'fill-opacity' : 'fillOpacity',
};
// htmlspecialchars converts & to "&amp;"; maybe unnecessarily, and maybe 3x
var ampersandRegex = /&(?:amp;){1,3}/g
var layer = L.ajaxGeoJson(src.replace(ampersandRegex, '&'), {
    type: 'json',
    style : layerStyle,
    onEachFeature : onEachFeature,
    pointToLayer: pointToLayer
});
var fitbounds = 0;
var circleMarker = 1;
var popup_text = window.WPLeafletMapPlugin.unescape("&lt;img width=&quot;250px&quot; src=&quot;{thumbnail_url}&quot;&gt;&lt;br /&gt;&lt;strong&gt;&lt;a target=&quot;_blank&quot; href=&quot;{track_url}&quot;/&gt;{track_name}&lt;/a&gt;&lt;/strong&gt; &lt;br /&gt; {artist_name}&lt;br /&gt;&lt;br&gt;");
var popup_property = "";
var table_view = 0;
var group = window.WPLeafletMapPlugin.getCurrentGroup();
var markerOptions = window.WPLeafletMapPlugin.getIconOptions({});
layer.addTo( group );
window.WPLeafletMapPlugin.geojsons.push( layer );
if (fitbounds) {
    layer.on('ready', function () {
        this.map.fitBounds( this.getBounds() );
    });
}
function layerStyle (feature) {
    var props = feature.properties || {};
    var style = {};
    function camelFun (_, first_letter) {
        return first_letter.toUpperCase();
    };
    for (var key in props) {
        if (key.match('-')) {
            var camelcase = key.replace(/-(\w)/, camelFun);
            style[ camelcase ] = props[ key ];
        }
        // rewrite style keys from geojson.io
        if (rewrite_keys[ key ]) {
            style[ rewrite_keys[ key ] ] = props[ key ];
        }
    }
    return L.Util.extend(style, default_style);
}
function onEachFeature (feature, layer) {
    var props = feature.properties || {};
    var text;
    if (table_view) {
        text = window.WPLeafletMapPlugin.propsToTable(props);
    } else {
        text = popup_property
            ? props[ popup_property ]
            : window.WPLeafletMapPlugin.template(
                popup_text, 
                feature.properties
            );
    }
    if (text) {
        layer.bindPopup( text );
    }
}
    function pointToLayer (feature, latlng) {
    if (circleMarker) {
        return L.circleMarker(latlng);
    }
    return L.marker(latlng, markerOptions);
}});</script>

<script><!--
window.WPLeafletMapPlugin=window.WPLeafletMapPlugin||[];window.WPLeafletMapPlugin.push(function(){let all_properties=["track_name"];let att_zoom="12";let att_propertyName="track_name5";if(typeof searchcontrol=="undefined"){var maps=[];var searchcontrol=[];}
var map=window.WPLeafletMapPlugin.getCurrentMap();var map_id=map._leaflet_id;if(typeof maps[map_id]=="undefined"){maps[map_id]=map;searchcontrol[map_id]=[];}
if(typeof searchcontrol[map_id][att_propertyName]=="undefined"){searchcontrol[map_id][att_propertyName]=att_propertyName;console.log("mapid: "+map_id);console.log(all_properties);console.log("propertyName: \"track_name5\", zoom: 12, collapsed: true, autoCollapseTime: 1200, textErr: \"Location not found\", textPlaceholder: \"Search...\", hideMarkerOnCollapse: true, position: \"topleft\", marker: {}, openPopup: true,");searchLayer=new L.LayerGroup();var duplicates=[];}
for(var i=0;i<WPLeafletMapPlugin.markers.length;i++){if(WPLeafletMapPlugin.markers[i]._map!==null){if(map_id==WPLeafletMapPlugin.markers[i]._map._leaflet_id){let a=WPLeafletMapPlugin.markers[i];let leafextsearch="";for(let i=0;i<all_properties.length;i++){att_property=all_properties[i];let this_options=a.getIcon().options;if(this_options.hasOwnProperty(att_property)){if(a.options[att_property]){leafextsearch=leafextsearch.concat(' | ',a.options[att_property]);}else{leafextsearch=leafextsearch.concat(' | ',this_options[att_property]);}}else
if(att_property=="popupContent"){if(a.getPopup()){if(a.getPopup().getContent()){let this_content=a.getPopup().getContent();this_content=this_content.replace(/<p>/ig,' ');this_content=this_content.replace(/<br>/ig,' ');this_content=this_content.replace(/(<([^>]+)>)/ig,'');this_content=this_content.replace(/ \| /g,'');this_content=this_content.replace(/ +/g,' ');leafextsearch=leafextsearch.concat(' | ',this_content);}}}
else{if(a.options[att_property]&&a.options[att_property]!=""){leafextsearch=leafextsearch.concat(' | ',a.options[att_property]);}else{}}}
if(leafextsearch!=""){let search=leafextsearch;if(typeof duplicates[search]=="undefined"){duplicates[search]=1;}else{duplicates[search]=duplicates[search]+1;}}
if(leafextsearch!=""){a.options["searchindex"]=i;a.options[att_propertyName]=leafextsearch;searchLayer.addLayer(a);}}}}
var markergroups=window.WPLeafletMapPlugin.markergroups;Object.entries(markergroups).forEach(([key,value])=>{if(markergroups[key]._map!==null){if(map_id==markergroups[key]._map._leaflet_id){markergroups[key].eachLayer(function(layer){if(layer instanceof L.Marker){}else if(layer instanceof L.Polygon||layer instanceof L.Circle||layer instanceof L.Polyline){let leafextsearch="";for(let i=0;i<all_properties.length;i++){att_property=all_properties[i];if(layer.hasOwnProperty(att_property)){if(layer.options[att_property]){leafextsearch=leafextsearch.concat(' | ',layer.options[att_property]);}}else
if(att_property=="popupContent"){if(layer.getPopup()){if(layer.getPopup().getContent()){let this_content=layer.getPopup().getContent();this_content=this_content.replace(/<p>/ig,' ');this_content=this_content.replace(/<br>/ig,' ');this_content=this_content.replace(/(<([^>]+)>)/ig,'');this_content=this_content.replace(/ \| /g,'');this_content=this_content.replace(/ +/g,' ');leafextsearch=leafextsearch.concat(' | ',this_content);}}}
else{if(layer.options[att_property]&&layer.options[att_property]!=""){leafextsearch=leafextsearch.concat(' | ',layer.options[att_property]);}else{}}
if(leafextsearch!=""){let search=leafextsearch;if(typeof duplicates[search]=="undefined"){duplicates[search]=1;}else{duplicates[search]=duplicates[search]+1;}}
if(leafextsearch!=""){layer.options["searchindex"]=i;layer.options[att_propertyName]=leafextsearch;searchLayer.addLayer(layer);}}}else{}});}}});var geojsons=window.WPLeafletMapPlugin.geojsons;for(var j=0,len=geojsons.length;j<len;j++){if(map_id==geojsons[j]._map._leaflet_id){geojsons[j].on("ready",function(e){let duplicates=[];j=0;e.target.eachLayer(function(layer){let leafextsearch="";if(layer.options){for(let i=0;i<all_properties.length;i++){att_property=all_properties[i];if(layer.options[att_property]&&layer.options[att_property]!=""){leafextsearch=leafextsearch.concat(' | ',layer.options[att_property]);}}}
for(let i=0;i<all_properties.length;i++){att_property=all_properties[i];if(att_property=="popupContent"){if(layer.getPopup()){let this_content=layer.getPopup().getContent();this_content=this_content.replace(/<p>/ig,' ');this_content=this_content.replace(/<br>/ig,' ');this_content=this_content.replace(/(<([^>]+)>)/ig,'');this_content=this_content.replace(/ \| /g,'');this_content=this_content.replace(/ +/g,' ');leafextsearch=leafextsearch.concat(' | ',this_content);}}else{if(layer.feature.properties.hasOwnProperty(att_property)){leafextsearch=leafextsearch.concat(' | ',layer.feature.properties[att_property]);}}}
let search=leafextsearch;if(typeof duplicates[search]=="undefined"){duplicates[search]=1;}else{duplicates[search]=duplicates[search]+1;}
layer.feature.properties["searchindex"]=j;j++;layer.feature.properties[att_propertyName]=leafextsearch;});e.target.eachLayer(function(layer){let search=layer.feature.properties[att_propertyName];if(duplicates[search]>1){layer.feature.properties[att_propertyName]=layer.feature.properties[att_propertyName]+" | "+layer.feature.properties["searchindex"];}
layer.feature.properties[att_propertyName]=layer.feature.properties[att_propertyName].replace(/\| /,'');});});geojsons[j].addTo(searchLayer);}}
if(Object.keys(searchLayer._layers).length>0){searchLayer.eachLayer(function(layer){if(layer.options[att_propertyName]){let search=layer.options[att_propertyName];if(duplicates[search]>1){layer.options[att_propertyName]=layer.options[att_propertyName]+" | "+layer.options["searchindex"];}
layer.options[att_propertyName]=layer.options[att_propertyName].replace(/\| /,'');}});var SearchControl=new L.Control.Search({layer:searchLayer,propertyName:"track_name5",zoom:12,collapsed:true,autoCollapseTime:1200,textErr:"Location not found",textPlaceholder:"Search...",hideMarkerOnCollapse:true,position:"topleft",marker:{},openPopup:true,initial:false,moveToLocation:function(latlng,title,map){if(latlng.layer.feature&&latlng.layer.feature.geometry&&latlng.layer.feature.geometry.type=="Point"){map.fitBounds(L.latLngBounds([latlng]));map.setZoom(att_zoom);}else if(latlng.layer instanceof L.Marker){map.fitBounds(L.latLngBounds([latlng]));map.setZoom(att_zoom);}else if(latlng.layer instanceof L.Circle){if(latlng.layer._map){console.log("has map");map.fitBounds(latlng.layer.getBounds());var zoom=map.getBoundsZoom(latlng.layer.getBounds());map.setView(latlng,zoom);}else{map.addLayer(latlng.layer);map.fitBounds(latlng.layer.getBounds());var zoom=map.getBoundsZoom(latlng.layer.getBounds());map.setView(latlng,zoom);map.removeLayer(latlng.layer);}}else{map.fitBounds(latlng.layer.getBounds());var zoom=map.getBoundsZoom(latlng.layer.getBounds());map.setView(latlng,zoom);}}});map.addControl(SearchControl);SearchControl.on("search:locationfound",function(e){if(e.target._map.hasLayer(e.layer)){}else{e.layer.addTo(map);this._markerSearch._markeradd=e.layer;}
if(e.layer.getPopup())
e.layer.openPopup([e.latlng.lat,e.latlng.lng]);e.sourceTarget._input.blur();});SearchControl.on("search:cancel",function(e){if(this._markerSearch&&this._markerSearch._markeradd){e.target._map.removeLayer(this._markerSearch._markeradd);}
map.closePopup();if(e.target.options.hideMarkerOnCollapse&&this._markerSearch){e.target._map.removeLayer(this._markerSearch);}
e.sourceTarget._input.blur();});map.addLayer(searchLayer);}});</script>

<script><!--
window.WPLeafletMapPlugin=window.WPLeafletMapPlugin||[];window.WPLeafletMapPlugin.push(function(){var map=window.WPLeafletMapPlugin.getCurrentMap();var map_id=map._leaflet_id;var maps=[];maps[map_id]=map;var allfit=[];if(true&&typeof maps[map_id]._shouldFitBounds==="undefined"){allfit[map_id]=new L.latLngBounds();}
var position="topleft";let all_options={"zoomInTitle":"Zoom in","zoomOutTitle":"Zoom out","zoomHomeIcon":"home","zoomHomeTitle":"Home","fit":true,"position":"topleft"};leafext_zoomhome_js(maps,map_id,allfit,position,all_options);});</script>

<script><!--
window.WPLeafletMapPlugin=window.WPLeafletMapPlugin||[];window.WPLeafletMapPlugin.push(function(){var map=window.WPLeafletMapPlugin.getCurrentMap();var fsControl=new L.Control.FullScreen({position:"topleft"});map.addControl(fsControl);});</script>
</p>



<p></p>



<h3 class="wp-block-heading">Day 3</h3>


<p><div class="leaflet-map WPLeafletMap" style="height:550px; width:99%;"></div><script>
window.WPLeafletMapPlugin = window.WPLeafletMapPlugin || [];
window.WPLeafletMapPlugin.push(function WPLeafletMapShortcode() {/*<script>*/
var baseUrl = atob('aHR0cHM6Ly97c30udGlsZS5vcGVuc3RyZWV0bWFwLm9yZy97en0ve3h9L3t5fS5wbmc=');
var base = (!baseUrl && window.MQ) ?
    window.MQ.mapLayer() : L.tileLayer(baseUrl,
        L.Util.extend({}, {
            detectRetina: 0,
        },
        {"subdomains":"abc","noWrap":false,"maxZoom":20}        )
    );
    var options = L.Util.extend({}, {
        layers: [base],
        attributionControl: false
    },
    {"zoomControl":true,"scrollWheelZoom":true,"doubleClickZoom":true,"fitBounds":true,"minZoom":1,"maxZoom":20,"maxBounds":null,"attribution":"<a href=\"http:\/\/www.openstreetmap.org\/copyright\">OpenStreetMap<\/a> contributors, <a href=\"http:\/\/leafletjs.com\" title=\"A JS library for interactive maps\">Leaflet<\/a> and Thejesh GN."},
    {});
window.WPLeafletMapPlugin.createMap(options).setView([22.6610714,],5);window.WPLeafletMapPlugin.createScale({});});</script>
<script>
window.WPLeafletMapPlugin = window.WPLeafletMapPlugin || [];
window.WPLeafletMapPlugin.push(function WPLeafletjsonShortcode() {/*<script>*/
var src = '&amp;#8221;https://data.thejeshgn.com/geojson/track-20251130-08-listenbrainz.geojson&amp;#8221;';
var default_style = {"radius":5};
var rewrite_keys = {
    stroke : 'color',
    'stroke-width' : 'weight',
    'stroke-opacity' : 'opacity',
    fill : 'fillColor',
    'fill-opacity' : 'fillOpacity',
};
// htmlspecialchars converts & to "&amp;"; maybe unnecessarily, and maybe 3x
var ampersandRegex = /&(?:amp;){1,3}/g
var layer = L.ajaxGeoJson(src.replace(ampersandRegex, '&'), {
    type: 'json',
    style : layerStyle,
    onEachFeature : onEachFeature,
    pointToLayer: pointToLayer
});
var fitbounds = 0;
var circleMarker = 1;
var popup_text = window.WPLeafletMapPlugin.unescape("&lt;img width=&quot;250px&quot; src=&quot;{thumbnail_url}&quot;&gt;&lt;br /&gt;&lt;strong&gt;&lt;a target=&quot;_blank&quot; href=&quot;{track_url}&quot;/&gt;{track_name}&lt;/a&gt;&lt;/strong&gt; &lt;br /&gt; {artist_name}&lt;br /&gt;&lt;br&gt;");
var popup_property = "";
var table_view = 0;
var group = window.WPLeafletMapPlugin.getCurrentGroup();
var markerOptions = window.WPLeafletMapPlugin.getIconOptions({});
layer.addTo( group );
window.WPLeafletMapPlugin.geojsons.push( layer );
if (fitbounds) {
    layer.on('ready', function () {
        this.map.fitBounds( this.getBounds() );
    });
}
function layerStyle (feature) {
    var props = feature.properties || {};
    var style = {};
    function camelFun (_, first_letter) {
        return first_letter.toUpperCase();
    };
    for (var key in props) {
        if (key.match('-')) {
            var camelcase = key.replace(/-(\w)/, camelFun);
            style[ camelcase ] = props[ key ];
        }
        // rewrite style keys from geojson.io
        if (rewrite_keys[ key ]) {
            style[ rewrite_keys[ key ] ] = props[ key ];
        }
    }
    return L.Util.extend(style, default_style);
}
function onEachFeature (feature, layer) {
    var props = feature.properties || {};
    var text;
    if (table_view) {
        text = window.WPLeafletMapPlugin.propsToTable(props);
    } else {
        text = popup_property
            ? props[ popup_property ]
            : window.WPLeafletMapPlugin.template(
                popup_text, 
                feature.properties
            );
    }
    if (text) {
        layer.bindPopup( text );
    }
}
    function pointToLayer (feature, latlng) {
    if (circleMarker) {
        return L.circleMarker(latlng);
    }
    return L.marker(latlng, markerOptions);
}});</script>

<script><!--
window.WPLeafletMapPlugin=window.WPLeafletMapPlugin||[];window.WPLeafletMapPlugin.push(function(){let all_properties=["track_name"];let att_zoom="12";let att_propertyName="track_name7";if(typeof searchcontrol=="undefined"){var maps=[];var searchcontrol=[];}
var map=window.WPLeafletMapPlugin.getCurrentMap();var map_id=map._leaflet_id;if(typeof maps[map_id]=="undefined"){maps[map_id]=map;searchcontrol[map_id]=[];}
if(typeof searchcontrol[map_id][att_propertyName]=="undefined"){searchcontrol[map_id][att_propertyName]=att_propertyName;console.log("mapid: "+map_id);console.log(all_properties);console.log("propertyName: \"track_name7\", zoom: 12, collapsed: true, autoCollapseTime: 1200, textErr: \"Location not found\", textPlaceholder: \"Search...\", hideMarkerOnCollapse: true, position: \"topleft\", marker: {}, openPopup: true,");searchLayer=new L.LayerGroup();var duplicates=[];}
for(var i=0;i<WPLeafletMapPlugin.markers.length;i++){if(WPLeafletMapPlugin.markers[i]._map!==null){if(map_id==WPLeafletMapPlugin.markers[i]._map._leaflet_id){let a=WPLeafletMapPlugin.markers[i];let leafextsearch="";for(let i=0;i<all_properties.length;i++){att_property=all_properties[i];let this_options=a.getIcon().options;if(this_options.hasOwnProperty(att_property)){if(a.options[att_property]){leafextsearch=leafextsearch.concat(' | ',a.options[att_property]);}else{leafextsearch=leafextsearch.concat(' | ',this_options[att_property]);}}else
if(att_property=="popupContent"){if(a.getPopup()){if(a.getPopup().getContent()){let this_content=a.getPopup().getContent();this_content=this_content.replace(/<p>/ig,' ');this_content=this_content.replace(/<br>/ig,' ');this_content=this_content.replace(/(<([^>]+)>)/ig,'');this_content=this_content.replace(/ \| /g,'');this_content=this_content.replace(/ +/g,' ');leafextsearch=leafextsearch.concat(' | ',this_content);}}}
else{if(a.options[att_property]&&a.options[att_property]!=""){leafextsearch=leafextsearch.concat(' | ',a.options[att_property]);}else{}}}
if(leafextsearch!=""){let search=leafextsearch;if(typeof duplicates[search]=="undefined"){duplicates[search]=1;}else{duplicates[search]=duplicates[search]+1;}}
if(leafextsearch!=""){a.options["searchindex"]=i;a.options[att_propertyName]=leafextsearch;searchLayer.addLayer(a);}}}}
var markergroups=window.WPLeafletMapPlugin.markergroups;Object.entries(markergroups).forEach(([key,value])=>{if(markergroups[key]._map!==null){if(map_id==markergroups[key]._map._leaflet_id){markergroups[key].eachLayer(function(layer){if(layer instanceof L.Marker){}else if(layer instanceof L.Polygon||layer instanceof L.Circle||layer instanceof L.Polyline){let leafextsearch="";for(let i=0;i<all_properties.length;i++){att_property=all_properties[i];if(layer.hasOwnProperty(att_property)){if(layer.options[att_property]){leafextsearch=leafextsearch.concat(' | ',layer.options[att_property]);}}else
if(att_property=="popupContent"){if(layer.getPopup()){if(layer.getPopup().getContent()){let this_content=layer.getPopup().getContent();this_content=this_content.replace(/<p>/ig,' ');this_content=this_content.replace(/<br>/ig,' ');this_content=this_content.replace(/(<([^>]+)>)/ig,'');this_content=this_content.replace(/ \| /g,'');this_content=this_content.replace(/ +/g,' ');leafextsearch=leafextsearch.concat(' | ',this_content);}}}
else{if(layer.options[att_property]&&layer.options[att_property]!=""){leafextsearch=leafextsearch.concat(' | ',layer.options[att_property]);}else{}}
if(leafextsearch!=""){let search=leafextsearch;if(typeof duplicates[search]=="undefined"){duplicates[search]=1;}else{duplicates[search]=duplicates[search]+1;}}
if(leafextsearch!=""){layer.options["searchindex"]=i;layer.options[att_propertyName]=leafextsearch;searchLayer.addLayer(layer);}}}else{}});}}});var geojsons=window.WPLeafletMapPlugin.geojsons;for(var j=0,len=geojsons.length;j<len;j++){if(map_id==geojsons[j]._map._leaflet_id){geojsons[j].on("ready",function(e){let duplicates=[];j=0;e.target.eachLayer(function(layer){let leafextsearch="";if(layer.options){for(let i=0;i<all_properties.length;i++){att_property=all_properties[i];if(layer.options[att_property]&&layer.options[att_property]!=""){leafextsearch=leafextsearch.concat(' | ',layer.options[att_property]);}}}
for(let i=0;i<all_properties.length;i++){att_property=all_properties[i];if(att_property=="popupContent"){if(layer.getPopup()){let this_content=layer.getPopup().getContent();this_content=this_content.replace(/<p>/ig,' ');this_content=this_content.replace(/<br>/ig,' ');this_content=this_content.replace(/(<([^>]+)>)/ig,'');this_content=this_content.replace(/ \| /g,'');this_content=this_content.replace(/ +/g,' ');leafextsearch=leafextsearch.concat(' | ',this_content);}}else{if(layer.feature.properties.hasOwnProperty(att_property)){leafextsearch=leafextsearch.concat(' | ',layer.feature.properties[att_property]);}}}
let search=leafextsearch;if(typeof duplicates[search]=="undefined"){duplicates[search]=1;}else{duplicates[search]=duplicates[search]+1;}
layer.feature.properties["searchindex"]=j;j++;layer.feature.properties[att_propertyName]=leafextsearch;});e.target.eachLayer(function(layer){let search=layer.feature.properties[att_propertyName];if(duplicates[search]>1){layer.feature.properties[att_propertyName]=layer.feature.properties[att_propertyName]+" | "+layer.feature.properties["searchindex"];}
layer.feature.properties[att_propertyName]=layer.feature.properties[att_propertyName].replace(/\| /,'');});});geojsons[j].addTo(searchLayer);}}
if(Object.keys(searchLayer._layers).length>0){searchLayer.eachLayer(function(layer){if(layer.options[att_propertyName]){let search=layer.options[att_propertyName];if(duplicates[search]>1){layer.options[att_propertyName]=layer.options[att_propertyName]+" | "+layer.options["searchindex"];}
layer.options[att_propertyName]=layer.options[att_propertyName].replace(/\| /,'');}});var SearchControl=new L.Control.Search({layer:searchLayer,propertyName:"track_name7",zoom:12,collapsed:true,autoCollapseTime:1200,textErr:"Location not found",textPlaceholder:"Search...",hideMarkerOnCollapse:true,position:"topleft",marker:{},openPopup:true,initial:false,moveToLocation:function(latlng,title,map){if(latlng.layer.feature&&latlng.layer.feature.geometry&&latlng.layer.feature.geometry.type=="Point"){map.fitBounds(L.latLngBounds([latlng]));map.setZoom(att_zoom);}else if(latlng.layer instanceof L.Marker){map.fitBounds(L.latLngBounds([latlng]));map.setZoom(att_zoom);}else if(latlng.layer instanceof L.Circle){if(latlng.layer._map){console.log("has map");map.fitBounds(latlng.layer.getBounds());var zoom=map.getBoundsZoom(latlng.layer.getBounds());map.setView(latlng,zoom);}else{map.addLayer(latlng.layer);map.fitBounds(latlng.layer.getBounds());var zoom=map.getBoundsZoom(latlng.layer.getBounds());map.setView(latlng,zoom);map.removeLayer(latlng.layer);}}else{map.fitBounds(latlng.layer.getBounds());var zoom=map.getBoundsZoom(latlng.layer.getBounds());map.setView(latlng,zoom);}}});map.addControl(SearchControl);SearchControl.on("search:locationfound",function(e){if(e.target._map.hasLayer(e.layer)){}else{e.layer.addTo(map);this._markerSearch._markeradd=e.layer;}
if(e.layer.getPopup())
e.layer.openPopup([e.latlng.lat,e.latlng.lng]);e.sourceTarget._input.blur();});SearchControl.on("search:cancel",function(e){if(this._markerSearch&&this._markerSearch._markeradd){e.target._map.removeLayer(this._markerSearch._markeradd);}
map.closePopup();if(e.target.options.hideMarkerOnCollapse&&this._markerSearch){e.target._map.removeLayer(this._markerSearch);}
e.sourceTarget._input.blur();});map.addLayer(searchLayer);}});</script>

<script><!--
window.WPLeafletMapPlugin=window.WPLeafletMapPlugin||[];window.WPLeafletMapPlugin.push(function(){var clmarkers=L.markerClusterGroup({showCoverageOnHover:true,zoomToBoundsOnClick:true,spiderfyOnMaxZoom:true,removeOutsideVisibleBounds:true,disableClusteringAtZoom:17,maxClusterRadius:80,});leafext_markercluster_js(clmarkers);});</script>

<script><!--
window.WPLeafletMapPlugin=window.WPLeafletMapPlugin||[];window.WPLeafletMapPlugin.push(function(){var map=window.WPLeafletMapPlugin.getCurrentMap();var map_id=map._leaflet_id;var maps=[];maps[map_id]=map;var allfit=[];if(true&&typeof maps[map_id]._shouldFitBounds==="undefined"){allfit[map_id]=new L.latLngBounds();}
var position="topleft";let all_options={"zoomInTitle":"Zoom in","zoomOutTitle":"Zoom out","zoomHomeIcon":"home","zoomHomeTitle":"Home","fit":true,"position":"topleft"};leafext_zoomhome_js(maps,map_id,allfit,position,all_options);});</script>

<script><!--
window.WPLeafletMapPlugin=window.WPLeafletMapPlugin||[];window.WPLeafletMapPlugin.push(function(){var map=window.WPLeafletMapPlugin.getCurrentMap();var fsControl=new L.Control.FullScreen({position:"topleft"});map.addControl(fsControl);});</script>
</p>



<p></p>



<p>The script is simple and straightforward.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = &quot;&gt;=3.9&quot;
# dependencies = &#x5B;
#   &quot;gpxpy&quot;,
#   &quot;requests&quot;,
# ]
# ///

import sys
import json
import os
from datetime import datetime, timezone
from bisect import bisect_left

import gpxpy
import requests


LB_ROOT = &quot;https://api.listenbrainz.org&quot;
USERNAME = &quot;thejeshgn&quot;
MAX_GAP_SECONDS = 600   # 10 min window


def load_gpx_points(path):
    with open(path, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
        gpx = gpxpy.parse(f)

    pts = &#x5B;]
    for tr in gpx.tracks:
        for seg in tr.segments:
            for p in seg.points:
                if not p.time:
                    continue

                t = p.time
                if t.tzinfo is None:
                    t = t.replace(tzinfo=timezone.utc)
                else:
                    t = t.astimezone(timezone.utc)

                pts.append(
                    {
                        &quot;time&quot;: t,
                        &quot;lat&quot;: p.latitude,
                        &quot;lon&quot;: p.longitude,
                        &quot;ele&quot;: p.elevation,
                    }
                )

    pts.sort(key=lambda x: x&#x5B;&quot;time&quot;])
    return pts


def build_thumbnail_and_track_urls(
    release_mbid: str | None,
    cover_art_url: str | None,
    recording_mbid: str | None,
    recording_msid: str | None,
):
    &quot;&quot;&quot;
    Build thumbnail and ListenBrainz URL.

    Priority:
    - URL based on release_mbid: https://listenbrainz.org/release/&lt;release_mbid&gt;/
    - Fallback URL based on recording id: https://listenbrainz.org/track/&lt;recording_*id&gt;/
    - Thumbnail from cover_art_url, else Cover Art Archive with release_mbid
    &quot;&quot;&quot;

    # URL: prefer release URL
    track_url = None
    rec_id = recording_mbid or recording_msid
    if rec_id:
        track_url = f&quot;https://listenbrainz.org/track/{rec_id}/&quot;

    # Thumbnail / cover art
    thumb = cover_art_url
    if not thumb and release_mbid:
        thumb = f&quot;https://coverartarchive.org/release/{release_mbid}/front-250&quot;

    return thumb, track_url


def fetch_listens(min_ts):
    url = f&quot;{LB_ROOT}/1/user/{USERNAME}/listens&quot;
    params = {
        &quot;min_ts&quot;: int(min_ts),
        &quot;count&quot;: 10000,
    }

    headers = {}
    token = os.getenv(&quot;LISTENBRAINZ_TOKEN&quot;)
    if token:
        headers&#x5B;&quot;Authorization&quot;] = f&quot;Token {token}&quot;

    r = requests.get(url, params=params, headers=headers, timeout=30)
    r.raise_for_status()

    listens_raw = r.json().get(&quot;payload&quot;, {}).get(&quot;listens&quot;, &#x5B;])
    listens = &#x5B;]

    for it in listens_raw:
        ts = it.get(&quot;listened_at&quot;)
        if not ts:
            continue

        t = datetime.fromtimestamp(ts, tz=timezone.utc)
        md = it.get(&quot;track_metadata&quot;, {}) or {}
        add = md.get(&quot;mbid_mapping&quot;, {}) or {}

        release_mbid = add.get(&quot;release_mbid&quot;)
        recording_mbid = add.get(&quot;recording_mbid&quot;)
        artist_mbids = add.get(&quot;artist_mbids&quot;)
        recording_msid = add.get(&quot;recording_msid&quot;) or it.get(&quot;recording_msid&quot;)
        cover_art_url = add.get(&quot;cover_art_url&quot;)

        thumbnail_url, track_url = build_thumbnail_and_track_urls(
            release_mbid=release_mbid,
            cover_art_url=cover_art_url,
            recording_mbid=recording_mbid,
            recording_msid=recording_msid,
        )

        listens.append(
            {
                &quot;time&quot;: t,
                &quot;track_name&quot;: md.get(&quot;track_name&quot;),
                &quot;artist_name&quot;: md.get(&quot;artist_name&quot;),
                &quot;release_name&quot;: md.get(&quot;release_name&quot;),
                &quot;release_mbid&quot;: release_mbid,
                &quot;recording_mbid&quot;: recording_mbid,
                &quot;recording_msid&quot;: recording_msid,
                &quot;artist_mbids&quot;: artist_mbids,
                &quot;thumbnail_url&quot;: thumbnail_url,
                &quot;track_url&quot;: track_url,
            }
        )

    listens.sort(key=lambda x: x&#x5B;&quot;time&quot;])
    return listens


def match(points, listens):
    &quot;&quot;&quot;
    Match GPX points to listens, but only emit one point when the song changes.

    Logic:
    - Compress consecutive listens with the same &quot;song key&quot;
      (recording_mbid / recording_msid / track+artist).
    - For each such listen, pick the closest GPX point in time.
    &quot;&quot;&quot;

    if not points or not listens:
        return &#x5B;]

    # Prebuild list of point times for bisect
    point_times = &#x5B;p&#x5B;&quot;time&quot;] for p in points]

    def song_key(l):
        return (
            l.get(&quot;recording_mbid&quot;),
            l.get(&quot;recording_msid&quot;),
            l.get(&quot;track_name&quot;),
            l.get(&quot;artist_name&quot;),
        )

    # Keep only listens where the song actually changes
    filtered_listens = &#x5B;]
    last_key = None
    for l in listens:
        k = song_key(l)
        if k != last_key:
            filtered_listens.append(l)
            last_key = k

    out = &#x5B;]

    from bisect import bisect_left

    for l in filtered_listens:
        idx = bisect_left(point_times, l&#x5B;&quot;time&quot;])
        candidates = &#x5B;]
        if idx &gt; 0:
            candidates.append(points&#x5B;idx - 1])
        if idx &lt; len(points):
            candidates.append(points&#x5B;idx])

        if not candidates:
            continue

        best_p = min(
            candidates,
            key=lambda p: abs((p&#x5B;&quot;time&quot;] - l&#x5B;&quot;time&quot;]).total_seconds()),
        )

        dt = abs((best_p&#x5B;&quot;time&quot;] - l&#x5B;&quot;time&quot;]).total_seconds())
        if dt &lt;= MAX_GAP_SECONDS:
            out.append((best_p, l))

    return out


def clean_props(d: dict) -&gt; dict:
    &quot;&quot;&quot;Return a copy without None values.&quot;&quot;&quot;
    return {k: v for k, v in d.items() if v is not None}


def to_geojson(pairs):
    feats = &#x5B;]

    for p, l in pairs:
        base_props = {
            &quot;gpx_time&quot;: p&#x5B;&quot;time&quot;].isoformat(),
            &quot;gpx_elevation&quot;: p&#x5B;&quot;ele&quot;],
            &quot;track_name&quot;: l.get(&quot;track_name&quot;),
            &quot;artist_name&quot;: l.get(&quot;artist_name&quot;),
            &quot;release_name&quot;: l.get(&quot;release_name&quot;),
            &quot;listened_at&quot;: l&#x5B;&quot;time&quot;].isoformat(),
            &quot;source&quot;: &quot;ListenBrainz&quot;,
            # important IDs
            &quot;release_mbid&quot;: l.get(&quot;release_mbid&quot;),
            # optional IDs
            &quot;recording_mbid&quot;: l.get(&quot;recording_mbid&quot;),
            &quot;recording_msid&quot;: l.get(&quot;recording_msid&quot;),
            &quot;artist_mbids&quot;: l.get(&quot;artist_mbids&quot;),
            # links and media
            &quot;thumbnail_url&quot;: l.get(&quot;thumbnail_url&quot;),
            &quot;track_url&quot;: l.get(&quot;track_url&quot;),
        }

        props = clean_props(base_props)

        feats.append(
            {
                &quot;type&quot;: &quot;Feature&quot;,
                &quot;geometry&quot;: {
                    &quot;type&quot;: &quot;Point&quot;,
                    &quot;coordinates&quot;: &#x5B;p&#x5B;&quot;lon&quot;], p&#x5B;&quot;lat&quot;]],
                },
                &quot;properties&quot;: props,
            }
        )

    return {&quot;type&quot;: &quot;FeatureCollection&quot;, &quot;features&quot;: feats}


def main():
    if len(sys.argv) != 3:
        print(&quot;Usage: uv run gpx_lb.py &lt;track.gpx&gt; &lt;out.geojson&gt;&quot;)
        sys.exit(1)

    gpx_path = sys.argv&#x5B;1]
    out_path = sys.argv&#x5B;2]

    pts = load_gpx_points(gpx_path)
    if not pts:
        print(&quot;No GPX points with timestamps.&quot;)
        sys.exit(1)

    start_ts = int(pts&#x5B;0]&#x5B;&quot;time&quot;].timestamp())
    listens = fetch_listens(start_ts - MAX_GAP_SECONDS)

    if not listens:
        print(&quot;No listens returned from ListenBrainz for that period.&quot;)
        sys.exit(0)

    pairs = match(pts, listens)
    gj = to_geojson(pairs)

    with open(out_path, &quot;w&quot;, encoding=&quot;utf-8&quot;) as f:
        json.dump(gj, f, ensure_ascii=False, indent=2)

    print(f&quot;Created {len(gj&#x5B;&#039;features&#039;])} features → {out_path}&quot;)


if __name__ == &quot;__main__&quot;:
    main()

</pre></div>


<hr class="wp-block-separator has-text-color has-vivid-purple-color has-alpha-channel-opacity has-vivid-purple-background-color has-background is-style-dots"/>



<div class="wp-block-columns has-background is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex" style="background:linear-gradient(137deg,rgb(255,206,236) 0%,rgb(152,150,240) 62%)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:5px"></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p></p>



<p>You can read this blog using <a href="https://feeds.thejeshgn.com/thejeshgn" target="_blank" rel="noreferrer noopener">RSS Feed</a>. But if you are the person who loves getting emails, then you can join my readers by <a href="https://thejeshgn.com/subscribe/" target="_blank" rel="noreferrer noopener">signing</a> up.</p>


<div class="wp-block-jetpack-subscriptions__supports-newline wp-block-jetpack-subscriptions__show-subs wp-block-jetpack-subscriptions">
		<div>
			<div>
				<div>
					<p style="width: 25%;max-width: 100%;">
						<a href="https://thejeshgn.com/?post_type=post&#038;p=37410" style="width: calc(100% - 10px);font-size: 16px;padding: 15px 23px 15px 23px;margin: 0; margin-left: 10px;border-color: black;border-radius: 6px;border-width: 2px; background-color: #000000; color: #FFFFFF; text-decoration: none; white-space: nowrap; margin-left: 0">Subscribe</a>
					</p>
				</div>
			</div>
		</div>
	</div></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:5px"></div>
</div>



<p> </p>
]]></content:encoded>
					
					<wfw:commentRss>https://thejeshgn.com/2025/12/08/mapping-my-motorcycle-ride-through-songs/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">37410</post-id>	</item>
		<item>
		<title>David MacMillan and James Robinson at IISc</title>
		<link>https://thejeshgn.com/2025/11/05/david-macmillan-and-james-robinson-at-iisc/</link>
					<comments>https://thejeshgn.com/2025/11/05/david-macmillan-and-james-robinson-at-iisc/#respond</comments>
		
		<dc:creator><![CDATA[Thejesh GN]]></dc:creator>
		<pubDate>Wed, 05 Nov 2025 16:05:15 +0000</pubDate>
				<category><![CDATA[Life]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Science]]></category>
		<category><![CDATA[Scientific Temper]]></category>
		<location><![CDATA[Bengaluru]]></location>
		<characters><![CDATA[Anand K 🧑🏽]]></characters>
		<guid isPermaLink="false">https://thejeshgn.com/?p=36983</guid>

					<description><![CDATA[You don&#8217;t often get the chance to hear Nobel Laureates live. When the opportunity arose in the form of the Nobel Prize Dialogue, The Future We Want, I applied. I wasn&#8217;t expecting to receive an invitation. I had assumed that this would for young people and they would prefer folks under 30. The&#46;&#46;&#46;]]></description>
										<content:encoded><![CDATA[
<p>You don&#8217;t often get the chance to hear Nobel Laureates live. When the opportunity arose in the form of the <a href="https://www.nobelprize.org/events/nobel-prize-dialogue/india-2025/" target="_blank" rel="noreferrer noopener">Nobel Prize Dialogue, The Future We Want</a>, I applied. I wasn&#8217;t expecting to receive an invitation. I had assumed that this would for young people and they would prefer folks under 30. The basic premise was to inspire the younger folks. But they did invite folks who are young at heart as well :) And the JN Tata auditorium at IISc was packed.</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-3 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><a href="https://thejeshgn.com/wp-content/uploads/2007/06/thej_nobelprize_blr_2025.jpeg" rel="lightbox[36983]"><img loading="lazy" decoding="async" width="1024" height="473" data-id="36957" src="https://thejeshgn.com/wp-content/uploads/2007/06/thej_nobelprize_blr_2025-1024x473.jpeg" alt="" class="wp-image-36957" srcset="https://thejeshgn.com/wp-content/uploads/2007/06/thej_nobelprize_blr_2025-1024x473.jpeg 1024w, https://thejeshgn.com/wp-content/uploads/2007/06/thej_nobelprize_blr_2025-300x139.jpeg 300w, https://thejeshgn.com/wp-content/uploads/2007/06/thej_nobelprize_blr_2025-768x355.jpeg 768w, https://thejeshgn.com/wp-content/uploads/2007/06/thej_nobelprize_blr_2025-720x332.jpeg 720w, https://thejeshgn.com/wp-content/uploads/2007/06/thej_nobelprize_blr_2025-520x240.jpeg 520w, https://thejeshgn.com/wp-content/uploads/2007/06/thej_nobelprize_blr_2025-320x148.jpeg 320w, https://thejeshgn.com/wp-content/uploads/2007/06/thej_nobelprize_blr_2025.jpeg 1280w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></figure>



<figure class="wp-block-image size-large"><a href="https://thejeshgn.com/wp-content/uploads/2007/06/20251103_132446-scaled.jpg" rel="lightbox[36983]"><img loading="lazy" decoding="async" width="1024" height="577" data-id="36955" src="https://thejeshgn.com/wp-content/uploads/2007/06/20251103_132446-1024x577.jpg" alt="" class="wp-image-36955" srcset="https://thejeshgn.com/wp-content/uploads/2007/06/20251103_132446-1024x577.jpg 1024w, https://thejeshgn.com/wp-content/uploads/2007/06/20251103_132446-300x169.jpg 300w, https://thejeshgn.com/wp-content/uploads/2007/06/20251103_132446-768x432.jpg 768w, https://thejeshgn.com/wp-content/uploads/2007/06/20251103_132446-1536x865.jpg 1536w, https://thejeshgn.com/wp-content/uploads/2007/06/20251103_132446-2048x1153.jpg 2048w, https://thejeshgn.com/wp-content/uploads/2007/06/20251103_132446-720x405.jpg 720w, https://thejeshgn.com/wp-content/uploads/2007/06/20251103_132446-520x293.jpg 520w, https://thejeshgn.com/wp-content/uploads/2007/06/20251103_132446-320x180.jpg 320w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></figure>



<figure class="wp-block-image size-large"><a href="https://thejeshgn.com/wp-content/uploads/2011/07/20251103_123906-scaled.jpg" rel="lightbox[36983]"><img loading="lazy" decoding="async" width="1024" height="577" data-id="36956" src="https://thejeshgn.com/wp-content/uploads/2011/07/20251103_123906-1024x577.jpg" alt="" class="wp-image-36956" srcset="https://thejeshgn.com/wp-content/uploads/2011/07/20251103_123906-1024x577.jpg 1024w, https://thejeshgn.com/wp-content/uploads/2011/07/20251103_123906-300x169.jpg 300w, https://thejeshgn.com/wp-content/uploads/2011/07/20251103_123906-768x432.jpg 768w, https://thejeshgn.com/wp-content/uploads/2011/07/20251103_123906-1536x865.jpg 1536w, https://thejeshgn.com/wp-content/uploads/2011/07/20251103_123906-2048x1153.jpg 2048w, https://thejeshgn.com/wp-content/uploads/2011/07/20251103_123906-720x405.jpg 720w, https://thejeshgn.com/wp-content/uploads/2011/07/20251103_123906-520x293.jpg 520w, https://thejeshgn.com/wp-content/uploads/2011/07/20251103_123906-320x180.jpg 320w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></figure>



<figure class="wp-block-image size-large"><a href="https://thejeshgn.com/wp-content/uploads/2016/10/20251103_120000-scaled.jpg" rel="lightbox[36983]"><img loading="lazy" decoding="async" width="1024" height="577" data-id="36952" src="https://thejeshgn.com/wp-content/uploads/2016/10/20251103_120000-1024x577.jpg" alt="" class="wp-image-36952" srcset="https://thejeshgn.com/wp-content/uploads/2016/10/20251103_120000-1024x577.jpg 1024w, https://thejeshgn.com/wp-content/uploads/2016/10/20251103_120000-300x169.jpg 300w, https://thejeshgn.com/wp-content/uploads/2016/10/20251103_120000-768x432.jpg 768w, https://thejeshgn.com/wp-content/uploads/2016/10/20251103_120000-1536x865.jpg 1536w, https://thejeshgn.com/wp-content/uploads/2016/10/20251103_120000-2048x1153.jpg 2048w, https://thejeshgn.com/wp-content/uploads/2016/10/20251103_120000-720x405.jpg 720w, https://thejeshgn.com/wp-content/uploads/2016/10/20251103_120000-520x293.jpg 520w, https://thejeshgn.com/wp-content/uploads/2016/10/20251103_120000-320x180.jpg 320w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></figure>



<figure class="wp-block-image size-large"><a href="https://thejeshgn.com/wp-content/uploads/2018/08/20251103_113541-scaled.jpg" rel="lightbox[36983]"><img loading="lazy" decoding="async" width="1024" height="577" data-id="36950" src="https://thejeshgn.com/wp-content/uploads/2018/08/20251103_113541-1024x577.jpg" alt="" class="wp-image-36950" srcset="https://thejeshgn.com/wp-content/uploads/2018/08/20251103_113541-1024x577.jpg 1024w, https://thejeshgn.com/wp-content/uploads/2018/08/20251103_113541-300x169.jpg 300w, https://thejeshgn.com/wp-content/uploads/2018/08/20251103_113541-768x432.jpg 768w, https://thejeshgn.com/wp-content/uploads/2018/08/20251103_113541-1536x865.jpg 1536w, https://thejeshgn.com/wp-content/uploads/2018/08/20251103_113541-2048x1153.jpg 2048w, https://thejeshgn.com/wp-content/uploads/2018/08/20251103_113541-720x405.jpg 720w, https://thejeshgn.com/wp-content/uploads/2018/08/20251103_113541-520x293.jpg 520w, https://thejeshgn.com/wp-content/uploads/2018/08/20251103_113541-320x180.jpg 320w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></figure>



<figure class="wp-block-image size-large"><a href="https://thejeshgn.com/wp-content/uploads/2018/03/20251103_113049-scaled.jpg" rel="lightbox[36983]"><img loading="lazy" decoding="async" width="1024" height="577" data-id="36949" src="https://thejeshgn.com/wp-content/uploads/2018/03/20251103_113049-1024x577.jpg" alt="" class="wp-image-36949" srcset="https://thejeshgn.com/wp-content/uploads/2018/03/20251103_113049-1024x577.jpg 1024w, https://thejeshgn.com/wp-content/uploads/2018/03/20251103_113049-300x169.jpg 300w, https://thejeshgn.com/wp-content/uploads/2018/03/20251103_113049-768x432.jpg 768w, https://thejeshgn.com/wp-content/uploads/2018/03/20251103_113049-1536x865.jpg 1536w, https://thejeshgn.com/wp-content/uploads/2018/03/20251103_113049-2048x1153.jpg 2048w, https://thejeshgn.com/wp-content/uploads/2018/03/20251103_113049-720x405.jpg 720w, https://thejeshgn.com/wp-content/uploads/2018/03/20251103_113049-520x293.jpg 520w, https://thejeshgn.com/wp-content/uploads/2018/03/20251103_113049-320x180.jpg 320w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></figure>



<figure class="wp-block-image size-large"><a href="https://thejeshgn.com/wp-content/uploads/2023/08/20251103_112915-scaled.jpg" rel="lightbox[36983]"><img loading="lazy" decoding="async" width="1024" height="577" data-id="36948" src="https://thejeshgn.com/wp-content/uploads/2023/08/20251103_112915-1024x577.jpg" alt="" class="wp-image-36948" srcset="https://thejeshgn.com/wp-content/uploads/2023/08/20251103_112915-1024x577.jpg 1024w, https://thejeshgn.com/wp-content/uploads/2023/08/20251103_112915-300x169.jpg 300w, https://thejeshgn.com/wp-content/uploads/2023/08/20251103_112915-768x432.jpg 768w, https://thejeshgn.com/wp-content/uploads/2023/08/20251103_112915-1536x865.jpg 1536w, https://thejeshgn.com/wp-content/uploads/2023/08/20251103_112915-2048x1153.jpg 2048w, https://thejeshgn.com/wp-content/uploads/2023/08/20251103_112915-720x405.jpg 720w, https://thejeshgn.com/wp-content/uploads/2023/08/20251103_112915-520x293.jpg 520w, https://thejeshgn.com/wp-content/uploads/2023/08/20251103_112915-320x180.jpg 320w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></figure>



<figure class="wp-block-image size-large"><a href="https://thejeshgn.com/wp-content/uploads/2025/11/20251103_104109-scaled.jpg" rel="lightbox[36983]"><img loading="lazy" decoding="async" width="1024" height="577" data-id="36947" src="https://thejeshgn.com/wp-content/uploads/2025/11/20251103_104109-1024x577.jpg" alt="" class="wp-image-36947" srcset="https://thejeshgn.com/wp-content/uploads/2025/11/20251103_104109-1024x577.jpg 1024w, https://thejeshgn.com/wp-content/uploads/2025/11/20251103_104109-300x169.jpg 300w, https://thejeshgn.com/wp-content/uploads/2025/11/20251103_104109-768x432.jpg 768w, https://thejeshgn.com/wp-content/uploads/2025/11/20251103_104109-1536x865.jpg 1536w, https://thejeshgn.com/wp-content/uploads/2025/11/20251103_104109-2048x1153.jpg 2048w, https://thejeshgn.com/wp-content/uploads/2025/11/20251103_104109-720x405.jpg 720w, https://thejeshgn.com/wp-content/uploads/2025/11/20251103_104109-520x293.jpg 520w, https://thejeshgn.com/wp-content/uploads/2025/11/20251103_104109-320x180.jpg 320w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></figure>



<figure class="wp-block-image size-large"><a href="https://thejeshgn.com/wp-content/uploads/2019/04/20251103_123115-scaled.jpg" rel="lightbox[36983]"><img loading="lazy" decoding="async" width="1024" height="577" data-id="36953" src="https://thejeshgn.com/wp-content/uploads/2019/04/20251103_123115-1024x577.jpg" alt="" class="wp-image-36953" srcset="https://thejeshgn.com/wp-content/uploads/2019/04/20251103_123115-1024x577.jpg 1024w, https://thejeshgn.com/wp-content/uploads/2019/04/20251103_123115-300x169.jpg 300w, https://thejeshgn.com/wp-content/uploads/2019/04/20251103_123115-768x432.jpg 768w, https://thejeshgn.com/wp-content/uploads/2019/04/20251103_123115-1536x865.jpg 1536w, https://thejeshgn.com/wp-content/uploads/2019/04/20251103_123115-2048x1153.jpg 2048w, https://thejeshgn.com/wp-content/uploads/2019/04/20251103_123115-720x405.jpg 720w, https://thejeshgn.com/wp-content/uploads/2019/04/20251103_123115-520x293.jpg 520w, https://thejeshgn.com/wp-content/uploads/2019/04/20251103_123115-320x180.jpg 320w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></figure>



<figure class="wp-block-image size-large"><a href="https://thejeshgn.com/wp-content/uploads/2008/12/20251103_115924-scaled.jpg" rel="lightbox[36983]"><img loading="lazy" decoding="async" width="1024" height="577" data-id="36951" src="https://thejeshgn.com/wp-content/uploads/2008/12/20251103_115924-1024x577.jpg" alt="" class="wp-image-36951" srcset="https://thejeshgn.com/wp-content/uploads/2008/12/20251103_115924-1024x577.jpg 1024w, https://thejeshgn.com/wp-content/uploads/2008/12/20251103_115924-300x169.jpg 300w, https://thejeshgn.com/wp-content/uploads/2008/12/20251103_115924-768x432.jpg 768w, https://thejeshgn.com/wp-content/uploads/2008/12/20251103_115924-1536x865.jpg 1536w, https://thejeshgn.com/wp-content/uploads/2008/12/20251103_115924-2048x1153.jpg 2048w, https://thejeshgn.com/wp-content/uploads/2008/12/20251103_115924-720x405.jpg 720w, https://thejeshgn.com/wp-content/uploads/2008/12/20251103_115924-520x293.jpg 520w, https://thejeshgn.com/wp-content/uploads/2008/12/20251103_115924-320x180.jpg 320w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></figure>
</figure>



<p>Loved the talks by David MacMillan (chemistry 2021) and James Robinson (economic sciences 2024). Both were inspiring. There were other talks too, but these two were the stars of the day. There was also a pleasant surprise for the audience in the form of Kavita Krishnamurthy and L Subramaniam performing to kick off the day.</p>



<p><a href="https://www.youtube.com/live/Lb7NTvsXGnU?si=d2AQdvW1QeCI6moz&amp;t=9991">Quote</a> of the day <em>&#8220;Ask yourself, Do you think it&#8217;s cool&#8221;.</em></p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<div class="video-container"><iframe loading="lazy" class="youtube-player" width="640" height="360" src="https://www.youtube.com/embed/Lb7NTvsXGnU?version=3&#038;rel=1&#038;showsearch=0&#038;showinfo=1&#038;iv_load_policy=1&#038;fs=1&#038;hl=en-US&#038;autohide=2&#038;wmode=transparent" allowfullscreen="true" style="border:0;" sandbox="allow-scripts allow-same-origin allow-popups allow-presentation allow-popups-to-escape-sandbox"></iframe></div>
</div></figure>



<hr class="wp-block-separator has-text-color has-vivid-purple-color has-alpha-channel-opacity has-vivid-purple-background-color has-background is-style-dots"/>



<div class="wp-block-columns has-background is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex" style="background:linear-gradient(137deg,rgb(255,206,236) 0%,rgb(152,150,240) 62%)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:5px"></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p></p>



<p>You can read this blog using <a href="https://feeds.thejeshgn.com/thejeshgn" target="_blank" rel="noreferrer noopener">RSS Feed</a>. But if you are the person who loves getting emails, then you can join my readers by <a href="https://thejeshgn.com/subscribe/" target="_blank" rel="noreferrer noopener">signing</a> up.</p>


<div class="wp-block-jetpack-subscriptions__supports-newline wp-block-jetpack-subscriptions__show-subs wp-block-jetpack-subscriptions">
		<div>
			<div>
				<div>
					<p style="width: 25%;max-width: 100%;">
						<a href="https://thejeshgn.com/?post_type=post&#038;p=36983" style="width: calc(100% - 10px);font-size: 16px;padding: 15px 23px 15px 23px;margin: 0; margin-left: 10px;border-color: black;border-radius: 6px;border-width: 2px; background-color: #000000; color: #FFFFFF; text-decoration: none; white-space: nowrap; margin-left: 0">Subscribe</a>
					</p>
				</div>
			</div>
		</div>
	</div></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:5px"></div>
</div>



<p> </p>
]]></content:encoded>
					
					<wfw:commentRss>https://thejeshgn.com/2025/11/05/david-macmillan-and-james-robinson-at-iisc/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">36983</post-id>	</item>
		<item>
		<title>Send Dramatiq Tasks directly by Pushing JSON to Redis</title>
		<link>https://thejeshgn.com/2025/11/03/send-dramatiq-tasks-directly-by-pushing-json-to-redis/</link>
					<comments>https://thejeshgn.com/2025/11/03/send-dramatiq-tasks-directly-by-pushing-json-to-redis/#respond</comments>
		
		<dc:creator><![CDATA[Thejesh GN]]></dc:creator>
		<pubDate>Sun, 02 Nov 2025 21:29:45 +0000</pubDate>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[Background Tasks]]></category>
		<category><![CDATA[Code]]></category>
		<category><![CDATA[Distributed Task Queue]]></category>
		<category><![CDATA[Dramatiq]]></category>
		<category><![CDATA[Free and Open Source]]></category>
		<category><![CDATA[Redict]]></category>
		<category><![CDATA[Redis]]></category>
		<guid isPermaLink="false">https://thejeshgn.com/?p=36908</guid>

					<description><![CDATA[Dramatiq uses JSON as the messaging format, and the sender or caller of the Job doesn&#8217;t need to have access to the Actor or know a lot about it. This has a considerable advantage. Once you set up the workers to execute the Job, all that is needed is to call the Job&#46;&#46;&#46;]]></description>
										<content:encoded><![CDATA[
<p><a href="https://thejeshgn.com/2025/10/30/simple-and-easy-background-tasks-with-dramatiq/">Dramatiq</a> uses JSON as the messaging format, and the sender or caller of the Job doesn&#8217;t need to have access to the Actor or know a lot about it. This has a considerable advantage. Once you set up the workers to execute the Job, all that is needed is to call the Job by posting a JSON message to the Broker. The JSON payload (<a href="https://dramatiq.io/advanced.html#enqueueing-messages-from-other-languages">from docs</a>) looks like this</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{
  &quot;queue_name&quot;: &quot;&quot;,     // The name of the queue the message is being pushed on
  &quot;actor_name&quot;: &quot;&quot;,         // The name of the actor that should handle this message
  &quot;args&quot;: &#x5B;],              // A list of positional arguments that are passed to the actor
  &quot;kwargs&quot;: {},                // A dictionary of keyword arguments that are passed to the actor
  &quot;options&quot;: {},               // Arbitrary options that are used by middleware. Leave this empty
  &quot;message_id&quot;: &quot;unique-id&quot;,   // A UUID4 value representing the message&#039;s unique id in the system
  &quot;message_timestamp&quot;: 0,      // The UNIX timestamp in milliseconds representing when the message was first enqueued
}
</pre></div>


<p>This feature enables us to call the Job from any programming language that can communicate with the Broker and format a JSON payload. Let&#8217;s examine an example Actor <a href="https://thejeshgn.com/2025/10/30/simple-and-easy-background-tasks-with-dramatiq/">from our previous blog post</a>, called <code>send_welcome_email</code>. which takes two arguments, <code>user_id</code> and <code>msg</code> in that order. Now, let&#8217;s create a payload to call <code>send_welcome_email</code></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{
  &quot;queue_name&quot;: &quot;default&quot;,
  &quot;actor_name&quot;: &quot;send_welcome_email&quot;,
  &quot;args&quot;: &#x5B;
    1234,
    &quot;Message for email send to redis directly&quot;
  ],
  &quot;kwargs&quot;: {},
  &quot;options&quot;: {
    &quot;redis_message_id&quot;: &quot;f3bcdcb4-1e18-41fa-9190-bf34d77a8fbe&quot;
  },
  &quot;message_id&quot;: &quot;425564d6-f762-455f-b8dd-69c9da1dd8fd&quot;,
  &quot;message_timestamp&quot;: 1762113802478
}
</pre></div>


<p>In our example, we are using Redis as the Broker. The default queue is called <code>default</code>. We also need to send <code>redis_message_id</code>, as part of  options, that is a different UUID4 value than <code>message_id</code>. You also need to make sure it&#8217;s unique. We are using a generated UUIDs here. Additionally, you can see that the <code>args</code> contain the arguments passed to the Actor. The remaining attributes are self-explanatory.</p>



<p>Now we need to post it to the Redis broker, to a default namespace called <code>dramatiq</code>. Dramatiq stores the message in a hash (<a href="https://redict.io/docs/commands/hset/">HSET</a>) and then pushes (<a href="https://redict.io/docs/commands/rpush/">RPUSH</a>)  <code>redis_message_id</code> of the message to a queue. The hash key is <code>dramatiq:default.msgs</code> and the filed is the <code>redis_message_id</code>. For the queue the key is <code>dramatiq:default</code>.</p>



<p>Then get to <code>redict-cli</code>. I am running <a href="https://thejeshgn.com/2025/10/30/simple-and-easy-background-tasks-with-dramatiq/">redict using docker/podman</a>, I will just shell into it</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
 podman  exec -it redict sh
</pre></div>


<p>Then call the command on db 1, as that is the redis DB I am using.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
# PART 1 - Add message to hash
redict-cli -n 1 HSET dramatiq:default.msgs f3bcdcb4-1e18-41fa-9190-bf34d77a8fbe &#039;{
  &quot;queue_name&quot;: &quot;default&quot;,
  &quot;actor_name&quot;: &quot;send_welcome_email&quot;,
  &quot;args&quot;: &#x5B;
    1234,
    &quot;Message for email send to redis directly&quot;
  ],
  &quot;kwargs&quot;: {},
  &quot;options&quot;: {
    &quot;redis_message_id&quot;: &quot;f3bcdcb4-1e18-41fa-9190-bf34d77a8fbe&quot;
  },
  &quot;message_id&quot;: &quot;425564d6-f762-455f-b8dd-69c9da1dd8fd&quot;,
  &quot;message_timestamp&quot;: 1762113802478
}&#039;

# PART 2 - Push the id to queue
redict-cli -n 1 RPUSH dramatiq:default f3bcdcb4-1e18-41fa-9190-bf34d77a8fbe
</pre></div>


<p>That&#8217;s it. That should run the Job on one of the workers. Below, I have written a simple Python script that posts a similar message directly to Redis.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
# /// script
# requires-python = &quot;&gt;=3.10&quot;
# dependencies = &#x5B;
#   &quot;redis&quot;
# ]
# ///
&quot;&quot;&quot;
Usage:
  uv run send_tasks_directly.py
&quot;&quot;&quot;
import redis, json, uuid, time

# connect to redis
r = redis.Redis(host=&#039;localhost&#039;, port=6379, db=1)
namespace = &quot;dramatiq&quot;
queue_name = &quot;default&quot;
redis_message_id = str(uuid.uuid4())
msg = {
  &quot;queue_name&quot;: queue_name,
  &quot;actor_name&quot;: &quot;send_welcome_email&quot;,
  &quot;args&quot;: &#x5B;1234, &quot;Message for email send to redis directly&quot;],
  &quot;kwargs&quot;: {},
  &quot;options&quot;: {&quot;redis_message_id&quot;: redis_message_id},
  &quot;message_id&quot;: str(uuid.uuid4()),
  &quot;message_timestamp&quot;: int(time.time() * 1000)
}
payload = json.dumps(msg, separators=(&quot;,&quot;, &quot;:&quot;))

r.hset(f&quot;{namespace}:{queue_name}.msgs&quot;, redis_message_id, payload)
r.rpush(f&quot;{namespace}:{queue_name}&quot;, redis_message_id)

</pre></div>


<p>I can do the same from Rust (run using <a href="https://rust-script.org/">rust-script</a>) as well. </p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="725" src="https://thejeshgn.com/wp-content/uploads/2025/11/post_to_dramatiq_from_rust_lang-1024x725.png" alt="" class="wp-image-36942" srcset="https://thejeshgn.com/wp-content/uploads/2025/11/post_to_dramatiq_from_rust_lang-1024x725.png 1024w, https://thejeshgn.com/wp-content/uploads/2025/11/post_to_dramatiq_from_rust_lang-300x212.png 300w, https://thejeshgn.com/wp-content/uploads/2025/11/post_to_dramatiq_from_rust_lang-768x544.png 768w, https://thejeshgn.com/wp-content/uploads/2025/11/post_to_dramatiq_from_rust_lang-720x510.png 720w, https://thejeshgn.com/wp-content/uploads/2025/11/post_to_dramatiq_from_rust_lang-520x368.png 520w, https://thejeshgn.com/wp-content/uploads/2025/11/post_to_dramatiq_from_rust_lang-320x226.png 320w, https://thejeshgn.com/wp-content/uploads/2025/11/post_to_dramatiq_from_rust_lang.png 1088w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">Post to Dramatiq from Rust Lang</figcaption></figure>
</div>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: cpp; title: ; notranslate">
//! ```cargo
//! &#x5B;dependencies]
//! redis = &quot;0.25&quot;
//! serde = { version = &quot;1&quot;, features = &#x5B;&quot;derive&quot;] }
//! serde_json = &quot;1&quot;
//! uuid = { version = &quot;1&quot;, features = &#x5B;&quot;v4&quot;] }
//! chrono = &quot;0.4&quot;
//! ```

// Run using
// rust-script send_tasks_directly.rs

use chrono::Utc;
use redis::Commands;
use redis::ConnectionLike;
use serde::Serialize;
use uuid::Uuid;

#&#x5B;derive(Serialize)]
struct Message {
    queue_name: String,
    actor_name: String,
    args: Vec&lt;serde_json::Value&gt;,
    kwargs: serde_json::Value,
    options: serde_json::Value,
    message_id: String,
    message_timestamp: i64,
}
fn main() -&gt; redis::RedisResult&lt;()&gt; {
    let client = redis::Client::open(&quot;redis://127.0.0.1:6379/1&quot;)?;
    let mut con = client.get_connection()?;

    let namespace = &quot;dramatiq&quot;;
    let queue_name = &quot;default&quot;;
    let redis_message_id = Uuid::new_v4().to_string();
    println!(&quot;redis_message_id &#039;{}&#039;&quot;, redis_message_id);

    let msg = Message {
        queue_name: queue_name.into(),
        actor_name: &quot;send_welcome_email&quot;.into(),
        args: vec!&#x5B;
            serde_json::json!(1234),
            serde_json::json!(&quot;Message for email send to redis directly from Rust&quot;),
        ],
        kwargs: serde_json::json!({}),
        options: serde_json::json!({ &quot;redis_message_id&quot;: redis_message_id }),
        message_id: Uuid::new_v4().to_string(),
        message_timestamp: Utc::now().timestamp_millis(),
    };

    let payload = serde_json::to_string_pretty(&amp;msg).unwrap();
    println!(&quot;payload &#039;{}&#039;&quot;, payload);

    let hash_key = format!(&quot;{}:{}&quot;, namespace, queue_name.to_string() + &quot;.msgs&quot;);
    let list_key = format!(&quot;{}:{}&quot;, namespace, queue_name);

    con.hset::&lt;_, _, _, ()&gt;(&amp;hash_key, &amp;redis_message_id, payload)?;
    println!(&quot;Sent message to hash_key &#039;{}&#039;&quot;, hash_key);

    let stored_value: String = con.hget(&amp;hash_key, &amp;redis_message_id)?;
    println!(&quot;🔍 Retrieved from Redis:\n{}&quot;, stored_value);

    con.req_packed_command(&amp;redis::cmd(&quot;PING&quot;).get_packed_command())?;
    con.rpush::&lt;_, _, ()&gt;(&amp;list_key, &amp;redis_message_id)?;

    println!(&quot;Sent message to Redis queue list_key &#039;{}&#039;&quot;, list_key);
    Ok(())
}
</pre></div>


<p>Isn&#8217;t that nifty?</p>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-dots"/>



<p><strong>Question:</strong> Why redis_message_id when there is already message_id at the Dramatiq level?</p>



<p>We are manually adding redis_message_id here because we are performing the same work that RedisBroker does internally. If we use the standard APIs, then it&#8217;s done internally automatically. Internally, redis_message_id is generated as a unique ID every time a message is retried, whereas the message_id remains the same. So if a message is tried only once, there would be a single message_id and redis_message_id. But if it&#8217;s retried five times, then there will be a single message_id and five different redis_message_id.</p>



<hr class="wp-block-separator has-text-color has-vivid-purple-color has-alpha-channel-opacity has-vivid-purple-background-color has-background is-style-dots"/>



<div class="wp-block-columns has-background is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex" style="background:linear-gradient(137deg,rgb(255,206,236) 0%,rgb(152,150,240) 62%)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:5px"></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p></p>



<p>You can read this blog using <a href="https://feeds.thejeshgn.com/thejeshgn" target="_blank" rel="noreferrer noopener">RSS Feed</a>. But if you are the person who loves getting emails, then you can join my readers by <a href="https://thejeshgn.com/subscribe/" target="_blank" rel="noreferrer noopener">signing</a> up.</p>


<div class="wp-block-jetpack-subscriptions__supports-newline wp-block-jetpack-subscriptions__show-subs wp-block-jetpack-subscriptions">
		<div>
			<div>
				<div>
					<p style="width: 25%;max-width: 100%;">
						<a href="https://thejeshgn.com/?post_type=post&#038;p=36908" style="width: calc(100% - 10px);font-size: 16px;padding: 15px 23px 15px 23px;margin: 0; margin-left: 10px;border-color: black;border-radius: 6px;border-width: 2px; background-color: #000000; color: #FFFFFF; text-decoration: none; white-space: nowrap; margin-left: 0">Subscribe</a>
					</p>
				</div>
			</div>
		</div>
	</div></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:5px"></div>
</div>



<p> </p>
]]></content:encoded>
					
					<wfw:commentRss>https://thejeshgn.com/2025/11/03/send-dramatiq-tasks-directly-by-pushing-json-to-redis/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">36908</post-id>	</item>
	</channel>
</rss>
