<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Martin Fitzpatrick</title><link href="https://www.martinfitzpatrick.com/" rel="alternate"/><link href="https://www.martinfitzpatrick.com/feeds/atom.xml" rel="self"/><id>https://www.martinfitzpatrick.com/</id><updated>2025-06-12T08:00:00+00:00</updated><subtitle>Python tutorials, projects and books</subtitle><entry><title>Remaking the classic Atari game ET in 10 lines of SAM Coupé BASIC</title><link href="https://www.martinfitzpatrick.com/remaking-the-classic-atari-game-et-in-10-lines-of-basic/" rel="alternate"/><published>2021-02-07T09:00:00+00:00</published><updated>2021-02-07T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2021-02-07:/remaking-the-classic-atari-game-et-in-10-lines-of-basic/</id><summary type="html">The &lt;a href="https://gkanold.server.deerpower.de/"&gt;Basic 10 Liner contest&lt;/a&gt; has been running for 11 years. This year (2021) the 10th competition,
and the first time I've heard of it, thanks to &lt;a href="http://www.breakintoprogram.co.uk/"&gt;Dean Belfield&lt;/a&gt;.</summary><content type="html">&lt;p&gt;The &lt;a href="https://gkanold.server.deerpower.de/"&gt;Basic 10 Liner contest&lt;/a&gt; has been running for 11 years. This year (2021) the 10th competition,
and the first time I've heard of it, thanks to &lt;a href="http://www.breakintoprogram.co.uk/"&gt;Dean Belfield&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The competition is simple: write a game in &lt;em&gt;some BASIC language&lt;/em&gt; in &lt;em&gt;10 lines of code&lt;/em&gt;.
Like the &lt;a href="/microbit-space-invaders/"&gt;arcade games for the micro:bit&lt;/a&gt; the fun comes from trying to squeeze as much of a game as possible out
of impossible limits.&lt;/p&gt;
&lt;p&gt;Since many BASIC dialects allow you to stack multiple statements onto the same line (SAM BASIC is no exception) there are
additional category limits on &lt;em&gt;characters per line&lt;/em&gt;, e.g.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Category "PUR-80": Program a game in 10 lines (max 80 characters per logical line, abbreviations are allowed).&lt;/li&gt;
&lt;li&gt;Category "PUR-120": Program a game in 10 lines (max 120 characters per logical line, abbreviations are allowed)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After finishing my first attempt &lt;a href="/basic10-sam-snake"&gt;Snake&lt;/a&gt; in 80 char lines, I wanted to have a go at something a
little more complex under the PUR-120 category. I chose a remake of the Atari "Classic" ET: the Extra Terrestrial.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Gameplay video" src="https://i.imgur.com/ZLjMKaP.gif"/&gt;
&lt;em&gt;Complete playthrough video, with win&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="playing"&gt;Playing&lt;/h2&gt;
&lt;p&gt;You control ET using the keys Q, A, O &amp;amp; P. The objective is to collect all the bits of phone
and then escape in the rocket. Bits of phone are hidden in pits on the map, which you need to
explore. To explore a pit, move over it and press SPACE. Explored pits are cleared from the map.&lt;/p&gt;
&lt;p&gt;Trying to stop you are 3 agents which follow you around. They can move more quickly over diagonals
and will get more desperate as you collect more pieces of the phone, getting faster. Once you have
5 bits of phone the rocket will appear, and you must race to get there before the agents catch you.&lt;/p&gt;
&lt;p&gt;The game is hard, but completable (see the included GIF). The trick is to choose carefully the
order of the pits you clear and use the agents behaviour on diagonals to herd them out of the way.&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  You can download a disk image containing &lt;a href="https://download.martinfitzpatrick.com/basic10.dsk"&gt;Snake and ET from here&lt;/a&gt;.
To play, you can use the &lt;a href="http://www.simcoupe.org/"&gt;SimCoupe&lt;/a&gt; emulator. Insert disk image with File&amp;gt;Open. Load &amp;amp; Run with &lt;code&gt;LOAD "et" LINE 1&lt;/code&gt;. Press &lt;code&gt;ESC&lt;/code&gt; to break to source.&lt;/p&gt;
&lt;h2 id="readable-code"&gt;Readable code&lt;/h2&gt;
&lt;p&gt;The complete code is shown below with inline comments.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-basic"&gt;basic&lt;/span&gt;
&lt;pre&gt;&lt;code class="basic"&gt;# vars x, y: current coords
#      p: number of parts collected
#      o: holds memory address &amp;amp;8000 (start of pits x, y)
#      b: holds o+10 (start of agents x, y)
#      c: memory location of user defined graphic 150
LET o=&amp;amp;8000,c=UDG CHR$ 150,b=o+10,x=10,y=10,p=0:

# Poke the pit locations (10 bytes) and agent locations (6 bytes) to memory
POKE o,5,5,15,5,26,5,11,15,19,15,4,4,18,6,10,7:

# Poke the custom character graphics to memory.
POKE c,0,124,124,96,50,20,124,126       # ET character
POKE c+8,0,60,126,255,255,126,60,0      # hole
POKE c+16,24,60,60,60,126,60,102        # rocket
POKE c+24,24,60,24,27,62,24,24,24       # agent
POKE c+32,112,120,24,24,88,56,24,60     # telephone / space dildo

# Set the background colour to a calm green.
PALETTE 0, 65

# Built in SAM Coupe sound effect.
ZAP

# Main loop
DO
    LET i$=INKEY$

    # Clear the players current location.
    PRINT AT y,x;" "
    IF i$="q":LET y=y-1: ELSE IF i$="a": LET y=y+1: ELSE IF i$="o": LET x=x-1: ELSE IF i$="p": LET x=x+1: END IF

    # Constrain positions to map.
    LET x=x MOD 31
    LET y=y MOD 18

    # Draw the new location.
    PRINT AT y,x; PEN 7;CHR$ 150

    # Handle agents.
    FOR k=0 TO 2

        # Get this agents location from memory.
        LET bx=PEEK (b+k*2), by=PEEK (b+k*2+1)

        # Random number 0-7, if less than the number of pieces move. As the player collects
        # more pieces, this evaluates to TRUE more frequently.
        IF RND(7)&amp;lt;=p
            PRINT AT by,bx;" "

            # Calculate diff to player, as a -1..+1 delta, update.
            LET xd=(x-bx), bx=bx+xd DIV ABS xd: LET yd=(y-by), by=by+yd DIV ABS yd

            # Put the new location back in memory.
            POKE (b+k*2),bx,by
        END IF

        # Draw the agent in black.
        PEN 8: PRINT AT by,bx;CHR$ 153

        # If the agent has hit the player, exit: game over.
        EXIT IF bx=x AND by=y

    NEXT k

    # Handle the holes.
    FOR k=0 TO 4

        # Get hole's x &amp;amp; y location from memory.
        LET hx=PEEK (o+k*2), hy=PEEK (o+k*2+1)

        # hx is 0 if the hole has been explored.
        IF hx
            # Draw the pit (PEN will still be 8 from the agents).
            PRINT AT hy,hx; CHR$ 151

            # h=(x=hx AND y=hy) means h=TRUE if the player is over this hole.
            LET h=x=hx AND y=hy

            # If the player is over the hole and pressing Space
            IF i$=" " AND h

                # Erase the hole's coords (we could just wipe the x)
                POKE (o+k*2), 0,0

                # Increase the parts count and make a ZAP noise.
                LET p=p+1: ZAP

                # Draw the space dildo.
                PRINT AT 18,p; PEN 11;CHR$ 154:

            END IF

        END IF
    NEXT k

    # If the players got 5 parts.
    IF p=5
        # Draw the rocket.
        PEN 10: PRINT AT 10,20;CHR$ 152

        # If the player is standing on the rocket.
        IF x=20 AND y=10

            # Draw a rocket launch.
            FOR ry=1 TO 10
                # Draw rocket (red), backspace, line feed, * (yellow)
                PRINT AT 10-ry,20;PEN 10;CHR$ 152;CHR$ 8;CHR$ 10;PEN 14;"*"

                # Sound effect
                POW
            NEXT ry

            # Exit the game, won!
            EXIT IF 1

        END IF
    END IF
LOOP

# Both EXIT IF calls end here, Restart
RUN

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The sprites are drawn using User Defined Graphics the SAM's custom character graphics.
Each character is 8x8 monochrome (1 bpp) and can be poked to memory directly as
a series of 8, 1 byte values. The expression &lt;code&gt;UDG CHR$ 150&lt;/code&gt; returns the &lt;em&gt;memory address&lt;/em&gt; of
the character in code position 150. Poking 8 bytes to this address will replace that character.&lt;/p&gt;
&lt;h2 id="pur-120"&gt;PUR 120&lt;/h2&gt;
&lt;p&gt;To squeeze the game down to fit the 120 character line limit I used the following tricks.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;MOD 18&lt;/code&gt; and &lt;code&gt;MOD31&lt;/code&gt; to wrap the player position, rather than comparisons.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;MOD&lt;/code&gt;, &lt;code&gt;ABS&lt;/code&gt; and &lt;code&gt;DIV&lt;/code&gt; to calculate the move for the agents.&lt;/li&gt;
&lt;li&gt;The &amp;amp;8000 address was stored in a variable &lt;code&gt;o&lt;/code&gt; saving 4 chars each, or 2 for &lt;code&gt;o+1&lt;/code&gt;, &lt;code&gt;o+2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Specific ordering of each loop block (originally the draw order was different).&lt;/li&gt;
&lt;li&gt;Statements were folded back into lines using &lt;code&gt;:&lt;/code&gt;, including breaking &lt;code&gt;IF&lt;/code&gt; and &lt;code&gt;ELSE&lt;/code&gt; blocks across lines. SAM BASIC doesn't care about line grouping.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;EXIT IF 1&lt;/code&gt; is redundant, we could just use &lt;code&gt;RUN&lt;/code&gt;, but the earlier
&lt;code&gt;EXIT IF bx=x AND by=y&lt;/code&gt; would become &lt;code&gt;IF bx=x AND by=y: RUN: END IF&lt;/code&gt; (+8 chars) overflowing that line.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-basic"&gt;basic&lt;/span&gt;
&lt;pre&gt;&lt;code class="basic"&gt;1LET o=&amp;amp;8000,c=UDG CHR$150,b=o+10,x=10,y=10,p=0:POKE o,5,5,15,5,26,5,11,15,19,15,4,4,18,6,10,7:PALETTE 0, 65:ZAP
2POKE c,0,124,124,96,50,20,124,126:POKE c+8,0,60,126,255,255,126,60,0:POKE c+16,24,60,60,60,126,60,102
3POKE c+24,24,60,24,27,62,24,24,24:POKE c+32,112,120,24,24,88,56,24,60:DO:LET i$=INKEY$:PRINT AT y,x;" ":IF i$="q"
4LET y=y-1:ELSE IF i$="a":LET y=y+1:ELSE IF i$="o":LET x=x-1:ELSE IF i$="p":LET x=x+1:END IF:LET x=x MOD 31
5LET y=y MOD 18:PRINT AT y,x; PEN 7;CHR$150:FOR k=0 TO 2:LET bx=PEEK (b+k*2), by=PEEK (b+k*2+1):IF RND(7)&amp;lt;=p
6PRINT AT by,bx;" ":LET xd=(x-bx),bx=bx+xd DIV ABS xd:LET yd=(y-by),by=by+yd DIV ABS yd:POKE (b+k*2),bx,by:END IF
7PEN 8:PRINT AT by,bx;CHR$153:EXIT IF bx=x AND by=y:NEXT k:FOR k=0 TO 4:LET hx=PEEK (o+k*2), hy=PEEK (o+k*2+1)
8IF hx:PRINT AT hy,hx; CHR$151:LET h=x=hx AND y=hy:IF i$=" " AND h:POKE (o+k*2), 0,0:LET p=p+1:ZAP
9PRINT AT 18,p; PEN 11;CHR$154:END IF:END IF:NEXT k:IF p=5:PEN 10:PRINT AT 10,20;CHR$152:IF x=20 AND y=10
10FOR ry=1 TO 10:PRINT AT 10-ry,20;PEN 10;CHR$152;CHR$8;CHR$10;PEN 14;"*":POW:NEXT ry:EXIT IF 1:END IF:END IF:LOOP:RUN
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;I didn't fully understand the 120 line limit and the possible packing with SAM BASIC until the game was "finished"
(and the above can actually be packed more e.g. &lt;code&gt;PEN 14&lt;/code&gt; can be entered as &lt;code&gt;PEN14&lt;/code&gt; for example). But
the longest line is now line 10 with 119 chars.&lt;/p&gt;
&lt;p&gt;The game is available on an disk image &lt;a href="https://download.martinfitzpatrick.com/basic10.dsk"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you're looking for a reference to SAM BASIC you can take a look at the original &lt;a href="https://sam.speccy.cz/basic/sam-coupe_use-guide.pdf"&gt;User Manual&lt;/a&gt; and
the &lt;a href="https://sam.speccy.cz/basic/sam-basic_complete_guide.pdf"&gt;Complete Guide to SAM Basic&lt;/a&gt; by Graham Burtenshaw.&lt;/p&gt;</content><category term="sam-coupe"/><category term="basic"/><category term="8bit"/><category term="games"/></entry><entry><title>6th Edition - Create GUI Applications with Python &amp; Qt, Released — PyQt6 &amp; PySide6 Books updated for 2025 with model view controller architecture, new Python/Qt features and more examples</title><link href="https://www.martinfitzpatrick.com/pyqt6-pyside6-books-updated-2025/" rel="alternate"/><published>2025-06-12T08:00:00+00:00</published><updated>2025-06-12T08:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2025-06-12:/pyqt6-pyside6-books-updated-2025/</id><summary type="html">The 6th edition of my book &lt;em&gt;Create GUI Applications with Python &amp;amp; Qt&lt;/em&gt; is now
available, for PyQt6 &amp;amp; PySide6.</summary><content type="html">
            &lt;p&gt;The 6th edition of my book &lt;em&gt;Create GUI Applications with Python &amp;amp; Qt&lt;/em&gt; is now
available, for PyQt6 &amp;amp; PySide6.&lt;/p&gt;
&lt;p&gt;This update brings the book up to date with the latest changes in PyQt6 &amp;amp; PySide6, and also updates code to make use of newer features in Python. Many of the chapters have been updated and extended with more examples of form layouts, built-in dialogs and architecture, particularly using Model View Controller (MVC) architecture.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You can buy the latest editions below --&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PyQt6 - &lt;a href="https://www.pythonguis.com/pyqt6-book/"&gt;PyQt6 Book, 6th Edition, Create GUI Applications with Python &amp;amp; Qt6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;PySide6 - &lt;a href="https://www.pythonguis.com/pyside6-book/"&gt;PySide6 Book, 6th Edition, Create GUI Applications with Python &amp;amp; Qt6&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As always, if you've previously bought a copy of the book you &lt;strong&gt;get these updates for free!&lt;/strong&gt; Just go to &lt;a href="https://martinfitzpatrick.com/library"&gt;your account downloads page&lt;/a&gt; and enter the email you used for the purchase.&lt;/p&gt;
&lt;p&gt;If you bought the book elsewhere (in paperback or digital) you can register to get these updates too -- just email your receipt to register@pythonguis.com&lt;/p&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.mfitzp.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt"/><category term="qt6"/><category term="python"/></entry><entry><title>PyQt6 Book now available in Korean: 파이썬과 Qt6로 GUI 애플리케이션 만들기 — The hands-on guide to creating GUI applications with Python gets a new translation</title><link href="https://www.martinfitzpatrick.com/pyqt6-book-now-available-in-korean/" rel="alternate"/><published>2023-05-04T09:00:00+00:00</published><updated>2023-05-04T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2023-05-04:/pyqt6-book-now-available-in-korean/</id><summary type="html">I am very happy to announce that my Python GUI programming book
&lt;strong&gt;Create GUI Applications with Python &amp;amp; Qt6 / PyQt6 Edition&lt;/strong&gt; is now available
in Korean from &lt;a href="http://www.acornpub.co.kr/book/python-qt6"&gt;Acorn Publishing&lt;/a&gt;</summary><content type="html">
            &lt;p&gt;I am very happy to announce that my Python GUI programming book
&lt;strong&gt;Create GUI Applications with Python &amp;amp; Qt6 / PyQt6 Edition&lt;/strong&gt; is now available
in Korean from &lt;a href="http://www.acornpub.co.kr/book/python-qt6"&gt;Acorn Publishing&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It's more than a little mind-blowing to see a book I've written translated into
another language -- not least one I cannot remotely understand! When I started
writing this book a few years ago I could never have imagined it would end up on book shelves
in Korea, never mind in Korean. This is just fantastic.&lt;/p&gt;
&lt;p&gt;&lt;img alt="파이썬과 Qt6로 GUI 애플리케이션 만들기" src="https://www.martinfitzpatrick.com/static/news/pyqt6-book-now-available-in-korean/pyqt6-book-korean.jpg"  loading="lazy" width="279" height="350"/&gt;
&lt;em&gt;파이썬과 Qt6로 GUI 애플리케이션 만들기 파이썬 애플리케이션 제작 실습 가이드&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you're in Korea, you can also pick up a copy at any of the following bookstores:
&lt;a href="https://product.kyobobook.co.kr/detail/S000201323866"&gt;Kyobobook&lt;/a&gt;,
&lt;a href="http://www.yes24.com/Product/Goods/118042593"&gt;YES24&lt;/a&gt; or
&lt;a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=313563300"&gt;Aladin&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thanks again to Acorn Publishing for translating my book and making it available to readers in Korea.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.mfitzp.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt"/><category term="qt6"/><category term="python"/></entry><entry><title>Getting Started With Git and GitHub in Your Python Projects — Version-Controlling Your Python Projects With Git and GitHub</title><link href="https://www.martinfitzpatrick.com/git-github-python/" rel="alternate"/><published>2023-03-20T06:00:00+00:00</published><updated>2023-03-20T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2023-03-20:/git-github-python/</id><summary type="html">Using a &lt;a href="https://en.wikipedia.org/wiki/Version_control"&gt;version control system (VCS)&lt;/a&gt; is crucial for any software development project. These systems allow developers to track changes to the project's codebase over time, removing the need to keep multiple copies of the project folder.</summary><content type="html">&lt;p&gt;Using a &lt;a href="https://en.wikipedia.org/wiki/Version_control"&gt;version control system (VCS)&lt;/a&gt; is crucial for any software development project. These systems allow developers to track changes to the project's codebase over time, removing the need to keep multiple copies of the project folder.&lt;/p&gt;
&lt;p&gt;VCSs also facilitate experimenting with new features and ideas without breaking existing functionality in a given project. They also enable collaboration with other developers that can contribute code, documentation, and more.&lt;/p&gt;
&lt;p&gt;In this article, we'll learn about &lt;a href="https://git-scm.com/"&gt;Git&lt;/a&gt;, the most popular VCS out there. We'll learn everything we need to get started with this VCS and start creating our own repositories. We'll also learn how to publish those repositories to &lt;a href="https://github.com/about"&gt;GitHub&lt;/a&gt;, another popular tool among developers nowadays.&lt;/p&gt;
&lt;h2 id="installing-and-setting-up-git"&gt;Installing and Setting Up Git&lt;/h2&gt;
&lt;p&gt;To use Git in our coding projects, we first need to install it on our computer. To do this, we need to navigate to Git's &lt;a href="https://git-scm.com/downloads"&gt;download page&lt;/a&gt; and choose the appropriate installer for our operating system. Once we've downloaded the installer, we need to run it and follow the on-screen instructions.&lt;/p&gt;
&lt;p&gt;We can check if everything is working correctly by opening a terminal or command-line window and running &lt;code&gt;git --version&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once we've confirmed the successful installation, we should provide Git with some personal information. You'll only need to do this once for every computer. Now go ahead and run the following commands with your own information:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ git config --global user.name &amp;lt;"YOUR NAME"&amp;gt;
$ git config --global user.email &amp;lt;name@email.com&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The first command adds your full name to Git's config file. The second command adds your email. Git will use this information in all your repositories.&lt;/p&gt;
&lt;p class="admonition admonition-warning"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-exclamation-circle"&gt;&lt;/i&gt;&lt;/span&gt;  If you publish your projects to a remote server like GitHub, then your email address will be visible to anyone with access to that repository. If you don't want to expose your email address this way, then you should create a separate email address to use with Git.&lt;/p&gt;
&lt;p&gt;As you'll learn in a moment, Git uses the concept of &lt;strong&gt;branches&lt;/strong&gt; to manage its repositories. A branch is a copy of your project's folder at a given time in the development cycle. The default branch of new repositories is named either &lt;code&gt;master&lt;/code&gt; or &lt;code&gt;main&lt;/code&gt;, depending on your current version of Git.&lt;/p&gt;
&lt;p&gt;You can change the name of the default branch by running the following command:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ git config --global init.defaultBranch &amp;lt;branch_name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This command will set the name of Git's default branch to &lt;code&gt;branch_name&lt;/code&gt;. Remember that this is just a placeholder name. You need to provide a suitable name for your installation.&lt;/p&gt;
&lt;p&gt;Another useful setting is the default text editor Git will use to type in commit messages and other messages in your repo. For example, if you use an editor like Visual Studio Code, then you can configure Git to use it:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;# Visual Studio Code
$ git config --global core.editor "code --wait"
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;With this command, we tell Git to use VS Code to process commit messages and any other message we need to enter through Git.&lt;/p&gt;
&lt;p&gt;Finally, to inspect the changes we've made to Git's configuration files, we can run the following command:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ git config --global -e
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This command will open the global &lt;code&gt;.gitconfig&lt;/code&gt; file in our default editor. There, we can fix any error we have made or add new settings. Then we just need to save the file and close it.&lt;/p&gt;
&lt;h2 id="understanding-how-git-works"&gt;Understanding How Git Works&lt;/h2&gt;
&lt;p&gt;Git works by allowing us to take a &lt;em&gt;snapshot&lt;/em&gt; of the current state of all the files in our project's folder. Each time we save one of those snapshots, we make a Git &lt;strong&gt;commit&lt;/strong&gt;. Then the cycle starts again, and Git creates new snapshots, showing how our project looked like at any moment.&lt;/p&gt;
&lt;p class="admonition admonition-info"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-info"&gt;&lt;/i&gt;&lt;/span&gt;  Git was created in 2005 by &lt;a href="https://en.wikipedia.org/wiki/Linus_Torvalds"&gt;Linus Torvalds&lt;/a&gt;, the creator of the &lt;a href="https://en.wikipedia.org/wiki/Linux_kernel"&gt;Linux kernel&lt;/a&gt;. Git is an &lt;a href="https://www.pythonguis.com/faq/charge-for-open-source-software/"&gt;open-source&lt;/a&gt; project that is licensed under the &lt;a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html"&gt;GNU General Public License (GPL) v2&lt;/a&gt;. It was initially made to facilitate kernel development due to the lack of a suitable alternative.&lt;/p&gt;
&lt;p&gt;The general workflow for making a Git commit to saving different snapshots goes through the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Change&lt;/strong&gt; the content of our project's folder.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stage&lt;/strong&gt; or mark the changes we want to save in our next commit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commit&lt;/strong&gt; or save the changes permanently in our project's Git database.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As the third step mentions, Git uses a special database called a &lt;strong&gt;repository&lt;/strong&gt;. This database is kept inside your project's directory under a folder called &lt;code&gt;.git&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="version-controlling-a-project-with-git-the-basics"&gt;Version-Controlling a Project With Git: The Basics&lt;/h2&gt;
&lt;p&gt;In this section, we'll create a local repository and learn how to manage it using the Git &lt;a href="https://en.wikipedia.org/wiki/Command-line_interface"&gt;command-line interface (CLI)&lt;/a&gt;. On macOS and Linux, we can use the default terminal application to follow along with this tutorial.&lt;/p&gt;
&lt;p&gt;On Windows, we recommend using Git Bash, which is part of the &lt;a href="https://gitforwindows.org/"&gt;Git For Windows&lt;/a&gt; package. Go to the Git Bash download page, get the installer, run it, and follow the on-screen instruction. Make sure to check the &lt;em&gt;Additional Icons&lt;/em&gt; -&amp;gt; &lt;em&gt;On the Desktop&lt;/em&gt; to get direct access to Git Bash on your desktop so that you can quickly find and launch the app.&lt;/p&gt;
&lt;p&gt;Alternatively, you can also use either Windows' Command Prompt or &lt;a href="https://learn.microsoft.com/en-us/powershell/"&gt;PowerShell&lt;/a&gt;. However, some commands may differ from the commands used in this tutorial.&lt;/p&gt;
&lt;h3&gt;Initializing a Git Repository&lt;/h3&gt;
&lt;p&gt;To start version-controlling a project, we need to initialize a new Git repository in the project's root folder or directory. In this tutorial, we'll use a sample project to facilitate the explanation. Go ahead and create a new folder in your file system. Then navigate to that folder in your terminal by running these commands:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ mkdir sample_project
$ cd sample_project
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The first command creates the project's root folder or directory, while the second command allows you to navigate into that folder. Don't close your terminal window. You'll be using it throughout the next sections.&lt;/p&gt;
&lt;p&gt;To initialize a Git repository in this folder, we need to use the &lt;a href="https://git-scm.com/docs/git-init"&gt;&lt;code&gt;git init&lt;/code&gt;&lt;/a&gt; command like in the example below:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ git init
Initialized empty Git repository in /.../sample_project/.git/
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This command creates a subfolder called &lt;code&gt;.git&lt;/code&gt; inside the project's folder. The leading dot in the folder's name means that this is a hidden directory. So, you may not see anything on your file manager. You can check the existence of &lt;code&gt;.git&lt;/code&gt; with the &lt;code&gt;ls -a&lt;/code&gt;, which lists all files in a given folder, including the hidden ones.&lt;/p&gt;
&lt;h3&gt;Checking the Status of Our Project&lt;/h3&gt;
&lt;p&gt;Git provides the &lt;a href="https://git-scm.com/docs/git-status"&gt;&lt;code&gt;git status&lt;/code&gt;&lt;/a&gt; command to allow us to identify the current state of a Git repository. Because our &lt;code&gt;sample_project&lt;/code&gt; folder is still empty, running &lt;code&gt;git status&lt;/code&gt; will display something like this:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ git status
On branch main

No commits yet

nothing to commit (create/copy files and use "git add" to track)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When we run &lt;code&gt;git status&lt;/code&gt;, we get detailed information about the current state of our Git repository. This command is pretty useful, and we'll turn back to it in multiple moments.&lt;/p&gt;
&lt;p&gt;As an example of how useful the &lt;code&gt;git status&lt;/code&gt; command is, go ahead and create a file called &lt;code&gt;main.py&lt;/code&gt; inside the project's folder using the following commands:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ touch main.py

$ git status
On branch main

No commits yet

Untracked files:
  (use "git add &amp;lt;file&amp;gt;..." to include in what will be committed)
    main.py

nothing added to commit but untracked files present (use "git add" to track)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;With the &lt;a href="https://en.wikipedia.org/wiki/Touch_(command)"&gt;&lt;code&gt;touch&lt;/code&gt;&lt;/a&gt; command, we create a new &lt;code&gt;main.py&lt;/code&gt; file under our project's folder. Then we run &lt;code&gt;git status&lt;/code&gt; again. This time, we get information about the presence of an untracked file called &lt;code&gt;main.py&lt;/code&gt;. We also get some basic instructions on how to add this file to our Git repo. Providing these guidelines or instructions is one of the neatest features of &lt;code&gt;git status&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, what is all that about untracked files? In the following section, we'll learn more about this topic.&lt;/p&gt;
&lt;h3&gt;Tracking and Committing Changes&lt;/h3&gt;
&lt;p&gt;A file in a Git repository can be either &lt;strong&gt;tracked&lt;/strong&gt; or &lt;strong&gt;untracked&lt;/strong&gt;. Any file that wasn't present in the last commit is considered an untracked file. Git doesn't keep a history of changes for untracked files in your project's folder.&lt;/p&gt;
&lt;p&gt;In our example, we haven't made any commits to our Git repo, so &lt;code&gt;main.py&lt;/code&gt; is naturally untracked. To start tracking it, run the &lt;a href="https://git-scm.com/docs/git-add"&gt;&lt;code&gt;git add&lt;/code&gt;&lt;/a&gt; command as follows:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ git add main.py

$ git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached &amp;lt;file&amp;gt;..." to unstage)
    new file:   main.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This &lt;code&gt;git add&lt;/code&gt; command has added &lt;code&gt;main.py&lt;/code&gt; to the list of tracked files. Now it's time to save the file permanently using the &lt;a href="https://git-scm.com/docs/git-commit"&gt;&lt;code&gt;git commit&lt;/code&gt;&lt;/a&gt; command with an appropriate commit message provided with the &lt;code&gt;-m&lt;/code&gt; option:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ git commit -m "Add main.py"
[main (root-commit) 5ac6586] Add main.py
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 main.py

$ git status
On branch master
nothing to commit, working tree clean
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We have successfully made our first commit, saving &lt;code&gt;main.py&lt;/code&gt; to our Git repository. The &lt;code&gt;git commit&lt;/code&gt; command requires a commit message, which we can provide through the &lt;code&gt;-m&lt;/code&gt; option. Commit messages should clearly describe what we have changed in our project.&lt;/p&gt;
&lt;p&gt;After the commit, our &lt;code&gt;main&lt;/code&gt; branch is completely clean, as you can conclude from the &lt;code&gt;git status&lt;/code&gt; output.&lt;/p&gt;
&lt;p&gt;Now let's start the cycle again by modifying &lt;code&gt;main.py&lt;/code&gt;, staging the changes, and creating a new commit. Go ahead and run the following commands:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ echo "print('Hello, World!')" &amp;gt; main.py
$ cat main.py
print('Hello, World!')

$ git add main.py

$ git commit -m "Create a 'Hello, World!' script  on  main.py"
[main 2f33f7e] Create a 'Hello, World!' script  on  main.py
 1 file changed, 1 insertion(+)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/Echo_(command)"&gt;&lt;code&gt;echo&lt;/code&gt;&lt;/a&gt; command adds the statement &lt;code&gt;"print('Hello, World!')"&lt;/code&gt; to our &lt;code&gt;main.py&lt;/code&gt; file. You can confirm this addition with the &lt;a href="https://en.wikipedia.org/wiki/Cat_(Unix)"&gt;&lt;code&gt;cat&lt;/code&gt;&lt;/a&gt; command, which lists the content of one or more target files. You can also open &lt;code&gt;main.py&lt;/code&gt; in your favorite editor and update the file there if you prefer.&lt;/p&gt;
&lt;p class="admonition admonition-info"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-info"&gt;&lt;/i&gt;&lt;/span&gt;  We can also use the &lt;a href="https://git-scm.com/docs/git-stage"&gt;&lt;code&gt;git stage&lt;/code&gt;&lt;/a&gt; command to stage or add files to a Git repository and include them in our next commit.&lt;/p&gt;
&lt;p&gt;We've made two commits to our Git repo. We can list our &lt;strong&gt;commit history&lt;/strong&gt; using the &lt;a href="https://git-scm.com/docs/git-log"&gt;&lt;code&gt;git log&lt;/code&gt;&lt;/a&gt; command as follows:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ git log --oneline
2f33f7e (HEAD -&amp;gt; main) Create a 'Hello, World!' script  on  main.py
5ac6586 Add main.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;git log&lt;/code&gt; command allows us to list all our previous commits. In this example, we've used the &lt;code&gt;--oneline&lt;/code&gt; option to list commits in a single line each. This command takes us to a dedicated output space. To leave that space, we can press the letter &lt;code&gt;Q&lt;/code&gt; on our keyboard.&lt;/p&gt;
&lt;h3&gt;Using a &lt;code&gt;.gitignore&lt;/code&gt; File to Skip Unneeded Files&lt;/h3&gt;
&lt;p&gt;While working with Git, we will often have files and folders that we must not save to our Git repo. For example, most Python projects include a &lt;code&gt;venv/&lt;/code&gt; folder with a virtual environment for that project. Go ahead and create one with the following command:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ python -m venv venv
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Once we've added a Python virtual environment to our project's folder, we can run &lt;code&gt;git status&lt;/code&gt; again to check the repo state:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ git status
On branch main
Untracked files:
  (use "git add &amp;lt;file&amp;gt;..." to include in what will be committed)
    venv/

nothing added to commit but untracked files present (use "git add" to track)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now the &lt;code&gt;venv/&lt;/code&gt; folder appears as an untracked file in our Git repository. We don't need to keep track of this folder because it's not part of our project's codebase. It's only a tool for working on the project. So, we need to ignore this folder. To do that, we can add the folder to a &lt;code&gt;.gitignore&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Go ahead and create a &lt;code&gt;.gitignore&lt;/code&gt; file in the project's folder. Add the &lt;code&gt;venv/&lt;/code&gt; folders to it and run &lt;code&gt;git status&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ touch .gitignore
$ echo "venv/" &amp;gt; .gitignore
$ git status
On branch main
Untracked files:
  (use "git add &amp;lt;file&amp;gt;..." to include in what will be committed)
    .gitignore

nothing added to commit but untracked files present (use "git add" to track)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now &lt;code&gt;git status&lt;/code&gt; doesn't list &lt;code&gt;venv/&lt;/code&gt; as an untracked file. This means that Git is ignoring that folder. If we take a look at the output, then we'll see that &lt;code&gt;.gitignore&lt;/code&gt; is now listed as an untracked file. We must commit our &lt;code&gt;.gitignore&lt;/code&gt; files to the Git repository. This will prevent other developers working with us from having to create their own local &lt;code&gt;.gitignore&lt;/code&gt; files.&lt;/p&gt;
&lt;p class="admonition admonition-info"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-info"&gt;&lt;/i&gt;&lt;/span&gt;   We can also list multiple files and folders in our &lt;code&gt;.gitignore&lt;/code&gt; file one per line. The file even accepts &lt;a href="https://en.wikipedia.org/wiki/Glob_(programming)"&gt;glob patterns&lt;/a&gt; to match specific types of files, such as &lt;code&gt;*.txt&lt;/code&gt;. If you want to save yourself some work, then you can take advantage of GitHub's &lt;a href="https://github.com/github/gitignore"&gt;gitignore&lt;/a&gt; repository, which provides a rich list of predefined &lt;code&gt;.gitignore&lt;/code&gt; files for different programming languages and development environments.&lt;/p&gt;
&lt;p&gt;We can also set up a global &lt;code&gt;.gitignore&lt;/code&gt; file on our computer. This global file will apply to all our Git repositories. If you decide to use this option, then go ahead and create a &lt;code&gt;.gitignore_global&lt;/code&gt; in your home folder.&lt;/p&gt;
&lt;h2 id="working-with-branches-in-git"&gt;Working With Branches in Git&lt;/h2&gt;
&lt;p&gt;One of the most powerful features of Git is that it allows us to create multiple branches. A &lt;strong&gt;branch&lt;/strong&gt; is a copy of our project's current status and commits history. Having the option to create and handle branches allows us to make changes to our project without messing up the main line of development.&lt;/p&gt;
&lt;p&gt;We'll often find that software projects maintain several independent branches to facilitate the development process. A common branch model distinguishes between four different types of branches:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;master&lt;/code&gt; branch that holds the main line of development&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;develop&lt;/code&gt; branch that holds the last developments&lt;/li&gt;
&lt;li&gt;One or more &lt;code&gt;feature&lt;/code&gt; branches that hold changes intended to add new features&lt;/li&gt;
&lt;li&gt;One or more &lt;code&gt;bugfix&lt;/code&gt; branches that hold changes intended to fix critical bugs&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;However, the branching model to use is up to you. In the following sections, we'll learn how to manage branches using Git.&lt;/p&gt;
&lt;h3&gt;Creating New Branches&lt;/h3&gt;
&lt;p&gt;Working all the time on the &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;master&lt;/code&gt; branch isn't a good idea. We can end up creating a mess and breaking the code. So, whenever we want to experiment with a new idea, implement a new feature, fix a bug, or just refactor a piece of code, we should create a new branch.&lt;/p&gt;
&lt;p&gt;To kick things off, let's create a new branch called &lt;code&gt;hello&lt;/code&gt; on our Git repo under the &lt;code&gt;sample_project&lt;/code&gt; folder. To do that, we can use the &lt;a href="https://git-scm.com/docs/git-branch"&gt;&lt;code&gt;git branch&lt;/code&gt;&lt;/a&gt; command followed by the branch's name:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ git branch hello
$ git branch --list
* main
  hello
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The first command creates a new branch in our Git repo. The second command allows us to list all the branches that currently exist in our repository. Again, we can press the letter &lt;code&gt;Q&lt;/code&gt; on our keyboard to get back to the terminal prompt.&lt;/p&gt;
&lt;p&gt;The star symbol denotes the currently active branch, which is &lt;code&gt;main&lt;/code&gt; in the example. We want to work on &lt;code&gt;hello&lt;/code&gt;, so we need to activate that branch. In Git's terminology, we need to check out to &lt;code&gt;hello&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Checking Out to a New Branch&lt;/h3&gt;
&lt;p&gt;Although we have just created a new branch, in order to start working on it, we need to &lt;em&gt;switch&lt;/em&gt; to or check out to it by using the &lt;a href="https://git-scm.com/docs/git-checkout"&gt;&lt;code&gt;git checkout&lt;/code&gt;&lt;/a&gt; command as follows:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ git checkout hello
Switched to branch 'hello'

$ git branch --list
  main
* hello

$ git log --oneline
2f33f7e (HEAD -&amp;gt; hello, main) Create a 'Hello, World!' script  on  main.py
5ac6586 Add main.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;git checkout&lt;/code&gt; command takes the name of an existing branch as an argument. Once we run the command, Git takes us to the target branch.&lt;/p&gt;
&lt;p class="admonition admonition-info"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-info"&gt;&lt;/i&gt;&lt;/span&gt;  We can derive a new branch from whatever branch we need.&lt;/p&gt;
&lt;p&gt;As you can see, &lt;code&gt;git branch --list&lt;/code&gt; indicates which branch we are currently on by placing a &lt;code&gt;*&lt;/code&gt; symbol in front of the relevant branch name. If we check the commit history with &lt;code&gt;git log --oneline&lt;/code&gt;, then we'll get the same as we get from &lt;code&gt;main&lt;/code&gt; because &lt;code&gt;hello&lt;/code&gt; is a copy of it.&lt;/p&gt;
&lt;p class="admonition admonition-info"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-info"&gt;&lt;/i&gt;&lt;/span&gt;  The &lt;code&gt;git checkout&lt;/code&gt; can take a &lt;code&gt;-b&lt;/code&gt; flag that we can use to create a new branch and immediately check out to it in a single step. That's what most developers use while working with Git repositories. In our example, we could have run &lt;code&gt;git checkout -b hello&lt;/code&gt; to create the &lt;code&gt;hello&lt;/code&gt; branch and check out to it with one command.&lt;/p&gt;
&lt;p&gt;Let's make some changes to our project and create another commit. Go ahead and run the following commands:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ echo "print('Welcome to PythonGUIs!')" &amp;gt;&amp;gt; main.py
$ cat main.py
print('Hello, World!')
print('Welcome to PythonGUIs!')

$ git add main.py
$ git commit -m "Extend our 'Hello, World' program with a welcome message."
[hello be62476] Extend our 'Hello, World' program with a welcome message.
 1 file changed, 1 insertion(+)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The final command committed our changes to the &lt;code&gt;hello&lt;/code&gt; branch. If we compare the commit history of both branches, then we'll see the difference:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ git log --oneline -1
be62476 (HEAD -&amp;gt; hello) Extend our 'Hello, World' program with a welcome message.

$ git checkout main
Switched to branch 'main'

$ git log --oneline -1
2f33f7e (HEAD -&amp;gt; main) Create a 'Hello, World!' script  on  main.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we first run &lt;code&gt;git log --oneline&lt;/code&gt; with &lt;code&gt;-1&lt;/code&gt; as an argument. This argument tells Git to give us only the last commit in the active branch's commit history. To inspect the commit history of &lt;code&gt;main&lt;/code&gt;, we first need to check out to that branch. Then we can run the same &lt;code&gt;git log&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;Now say that we're happy with the changes we've made to our project in the &lt;code&gt;hello&lt;/code&gt; branch, and we want to update &lt;code&gt;main&lt;/code&gt; with those changes. How can we do this? We need to merge &lt;code&gt;hello&lt;/code&gt; into &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Merging Two Branches Together&lt;/h3&gt;
&lt;p&gt;To add the commits we've made in a separate branch back to another branch, we can run what is known as a &lt;strong&gt;merge&lt;/strong&gt;. For example, say we want to merge the new commits in &lt;code&gt;hello&lt;/code&gt; into &lt;code&gt;main&lt;/code&gt;. In this case, we first need to switch back to &lt;code&gt;main&lt;/code&gt; and then run the &lt;a href="https://git-scm.com/docs/git-merge"&gt;&lt;code&gt;git merge&lt;/code&gt;&lt;/a&gt; command using &lt;code&gt;hello&lt;/code&gt; as an argument:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ git checkout main
Already on 'main'

$ git merge hello
Updating 2f33f7e..be62476
Fast-forward
 main.py | 1 +
 1 file changed, 1 insertion(+)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To merge a branch into another branch, we first need to check out the branch we want to update. Then we can run &lt;code&gt;git merge&lt;/code&gt;. In the example above, we first check out to &lt;code&gt;main&lt;/code&gt;. Once there, we can merge &lt;code&gt;hello&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Deleting Unused Branches&lt;/h3&gt;
&lt;p&gt;Once we've finished working in a given branch, we can delete the entire branch to keep our repo as clean as possible. Following our example, now that we've merged &lt;code&gt;hello&lt;/code&gt; into &lt;code&gt;main&lt;/code&gt;, we can remove &lt;code&gt;hello&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To remove a branch from a Git repo, we use the &lt;code&gt;git branch&lt;/code&gt; command with the &lt;code&gt;--delete&lt;/code&gt; option. To successfully run this command, make sure to switch to another branch before:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ git checkout main
Already on 'main'

$ git branch --delete hello
Deleted branch hello (was be62476).

$ git branch --list
* main
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Deleting unused branches is a good way to keep our Git repositories clean, organized, and up to date. Also, deleting them as soon as we finish the work is even better because having old branches around may be confusing for other developers collaborating with our project. They might end up wondering why these branches are still alive.&lt;/p&gt;
&lt;h2 id="using-a-gui-client-for-git"&gt;Using a GUI Client for Git&lt;/h2&gt;
&lt;p&gt;In the previous sections, we've learned to use the &lt;code&gt;git&lt;/code&gt; command-line tool to manage Git repositories. If you prefer to use GUI tools, then you'll find a bunch of third-party GUI frontends for Git. While they won't completely replace the need for using the command-line tool, they can simplify your day-to-day workflow.&lt;/p&gt;
&lt;p class="admonition admonition-info"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-info"&gt;&lt;/i&gt;&lt;/span&gt;  You can get a complete list of standalone GUI clients available on the Git &lt;a href="https://git-scm.com/downloads/guis"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Most popular IDEs and code editors, including &lt;a href="https://www.jetbrains.com/pycharm/"&gt;PyCharm&lt;/a&gt; and &lt;a href="https://www.pythonguis.com/tutorials/getting-started-vs-code-python/"&gt;Visual Studio Code&lt;/a&gt;, come with basic Git integration out-of-the-box. Some developers will prefer this approach as it is directly integrated with their development environment of choice.&lt;/p&gt;
&lt;p&gt;If you need something more advanced, then &lt;a href="https://www.gitkraken.com"&gt;GitKraken&lt;/a&gt; is probably a good choice. This tool provides a standalone, cross-platform GUI client for Git that comes with many additional features that can boost your productivity.&lt;/p&gt;
&lt;h2 id="managing-a-project-with-github"&gt;Managing a Project With GitHub&lt;/h2&gt;
&lt;p&gt;If we publish a project on a remote server with support for Git repositories, then anyone with appropriate permissions can &lt;a href="https://git-scm.com/docs/git-clone"&gt;clone&lt;/a&gt; our project, creating a local copy on their computer. Then, they can make changes to our project, commit them to their local copy, and finally push the changes back to the remote server. This workflow provides a straightforward way to allow other developers to contribute code to your projects.&lt;/p&gt;
&lt;p&gt;In the following sections, we'll learn how to create a remote repository on GitHub and then push our existing local repository to it. Before we do that, though, head over to &lt;a href="https://github.com/"&gt;GitHub.com&lt;/a&gt; and create an account there if you don't have one yet. Once you have a GitHub account, you can set up the connection to that account so that you can use it with Git.&lt;/p&gt;
&lt;h3&gt;Setting Up a Secure Connection to GitHub&lt;/h3&gt;
&lt;p&gt;In order to work with GitHub via the &lt;code&gt;git&lt;/code&gt; command, we need to be able to authenticate ourselves. There are a few ways of doing that. However, using &lt;a href="https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh"&gt;SSH&lt;/a&gt; is the recommended way. The first step in the process is to generate an SSH key, which you can do with the following command:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ ssh-keygen -t ed25519 -C "GitHub - name@email.com"
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Replace the placeholder email address with the address you've associated with your GitHub account. Once you run this command, you'll get three different prompts in a row. You can respond to them by pressing Enter to accept the default option. Alternatively, you can provide custom responses.&lt;/p&gt;
&lt;p&gt;Next, we need to copy the contents of our &lt;code&gt;id_ed25519.pub&lt;/code&gt; file. To do this, you can run the following command:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ cat ~/.ssh/id_ed25519.pub
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Select the command's output and copy it. Then go to your GitHub &lt;em&gt;Settings&lt;/em&gt; page and click the &lt;em&gt;SSH and GPG keys&lt;/em&gt; option. There, select &lt;em&gt;New SSH key&lt;/em&gt;, set a descriptive title for the key, make sure that the &lt;em&gt;Key Type&lt;/em&gt; is set to &lt;em&gt;Authentication Key&lt;/em&gt;, and finally, paste the contents of &lt;code&gt;id_ed25519.pub&lt;/code&gt; in the &lt;em&gt;Key&lt;/em&gt; field. Finally, click the &lt;em&gt;Add SSH key&lt;/em&gt; button.&lt;/p&gt;
&lt;p class="admonition admonition-warning"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-exclamation-circle"&gt;&lt;/i&gt;&lt;/span&gt;  At this point, you may be asked to provide some kind of &lt;a href="https://en.wikipedia.org/wiki/Multi-factor_authentication"&gt;Two-Factor Authentication (2FA)&lt;/a&gt; code. So, be ready for that extra security step.&lt;/p&gt;
&lt;p&gt;Now we can test our connection by running the following command:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ ssh -T git@github.com
The authenticity of host 'github.com (IP ADDRESS)' can not be established.
ECDSA key fingerprint is SHA256:p2QAMXNIC1TJYWeIOttrVc98/R1BUFWu3/LiyKgUfQM.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Make sure to check whether the key fingerprint shown on your output matches &lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints"&gt;GitHub's public key fingerprint&lt;/a&gt;. If it matches, then enter &lt;em&gt;yes&lt;/em&gt; and press Enter to connect. Otherwise, don't connect.&lt;/p&gt;
&lt;p&gt;If the connection is successful, we will get a message like this:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;Hi USERNAME! You have successfully authenticated, but GitHub does not provide shell access.
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Congrats! You've successfully connected to GitHub via SSH using a secure SSH key. Now it's time to start working with GitHub.&lt;/p&gt;
&lt;h3&gt;Creating and Setting Up a GitHub Repository&lt;/h3&gt;
&lt;p&gt;Now that you have a GitHub account with a proper SSH connection, let's create a remote repository on GitHub using its web interface. Head over to the GitHub page and click the &lt;code&gt;+&lt;/code&gt; icon next to your avatar in the top-right corner. Then select &lt;em&gt;New repository&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Give your new repo a unique name and choose who can see this repository. To continue with our example, we can give this repository the same name as our local project, &lt;code&gt;sample_project&lt;/code&gt;.&lt;/p&gt;
&lt;p class="admonition admonition-warning"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-exclamation-circle"&gt;&lt;/i&gt;&lt;/span&gt;  To avoid conflicts with your existing local repository, don't add &lt;code&gt;.gitignore&lt;/code&gt;, &lt;code&gt;README&lt;/code&gt;, or &lt;code&gt;LICENSE&lt;/code&gt; files to your remote repository.&lt;/p&gt;
&lt;p&gt;Next, set the repo's visibility to &lt;em&gt;Private&lt;/em&gt; so that no one else can access the code. Finally, click the &lt;em&gt;Create repository&lt;/em&gt; button at the end of the page.&lt;/p&gt;
&lt;p class="admonition admonition-info"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-info"&gt;&lt;/i&gt;&lt;/span&gt;  If you create a &lt;em&gt;Public&lt;/em&gt; repository, make sure also to choose an &lt;a href="https://choosealicense.com"&gt;open-source license&lt;/a&gt; for your project to tell people what they can and can't do with your code.&lt;/p&gt;
&lt;p&gt;You'll get a &lt;em&gt;Quick setup&lt;/em&gt; page as your remote repository has no content yet. Right at the top, you'll have the choice to connect this repository via HTTPS or SSH. Copy the SSH link and run the following command to tell Git where the remote repository is hosted:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ git remote add origin git@github.com:USERNAME/sample_project.git
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This command adds a new remote repository called &lt;code&gt;origin&lt;/code&gt; to our local Git repo.&lt;/p&gt;
&lt;p class="admonition admonition-info"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-info"&gt;&lt;/i&gt;&lt;/span&gt;  The name &lt;code&gt;origin&lt;/code&gt; is commonly used to denote the main remote repository associated with a given project. This is the default name Git uses to identify the main remote repo.&lt;/p&gt;
&lt;p&gt;Git allows us to add several remote repositories to a single local one using the &lt;code&gt;git remote add&lt;/code&gt; command. This allows us to have several remote copies of your local Git repo.&lt;/p&gt;
&lt;h3&gt;Pushing a Local Git Repository to GitHub&lt;/h3&gt;
&lt;p&gt;With a new and empty GitHub repository in place, we can go ahead and push the content of our local repo to its remote copy. To do this, we use the &lt;code&gt;git push&lt;/code&gt; command providing the target remote repo and the local branch as arguments:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ git push -u origin main
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 8 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (9/9), 790 bytes | 790.00 KiB/s, done.
Total 9 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:USERNAME/sample_project.git
 * [new branch]      main -&amp;gt; main
branch 'main' set up to track 'origin/main'.
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This is the first time we push something to the remote repo &lt;code&gt;sample_project&lt;/code&gt;, so we use the &lt;code&gt;-u&lt;/code&gt; option to tell Git that we want to set the local &lt;code&gt;main&lt;/code&gt; branch to track the remote &lt;code&gt;main&lt;/code&gt; branch. The command's output provides a pretty detailed summary of the process.&lt;/p&gt;
&lt;p&gt;Note that if you don't add the &lt;code&gt;-u&lt;/code&gt; option, then Git will ask what you want to do. A safe workaround is to copy and paste the commands GitHub suggests, so that you don't forget &lt;code&gt;-u&lt;/code&gt;.&lt;/p&gt;
&lt;p class="admonition admonition-info"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-info"&gt;&lt;/i&gt;&lt;/span&gt;  Using the same command, we can push any local branch to any remote copy of our project's repo. Just change the repo and branch name: &lt;code&gt;git push -u remote_name branch_name&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now let's head over to our browser and refresh the GitHub page. We will see all of our project files and commit history there.&lt;/p&gt;
&lt;p&gt;Now we can continue developing our project and making new commits locally. To push our commits to the remote &lt;code&gt;main&lt;/code&gt; branch, we just need to run &lt;code&gt;git push&lt;/code&gt;. This time, we don't have to use the remote or branch name because we've already set &lt;code&gt;main&lt;/code&gt; to track &lt;code&gt;origin/main&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Pulling Content From a GitHub Repository&lt;/h3&gt;
&lt;p&gt;We can do basic file editing and make commits within GitHub itself. For example, if we click the &lt;code&gt;main.py&lt;/code&gt; file and then click the &lt;em&gt;pencil&lt;/em&gt; icon at the top of the file, we can add another line of code and commit those changes to the remote &lt;code&gt;main&lt;/code&gt; branch directly on GitHub.&lt;/p&gt;
&lt;p&gt;Go ahead and add the statement &lt;code&gt;print("Your Git Tutorial is Here...")&lt;/code&gt; to the end of &lt;code&gt;main.py&lt;/code&gt;. Then go to the end of the page and click the &lt;em&gt;Commit changes&lt;/em&gt; button. This makes a new commit on your remote repository.&lt;/p&gt;
&lt;p&gt;This remote commit won't appear in your local commit history. To download it and update your local &lt;code&gt;main&lt;/code&gt; branch, use the &lt;a href="https://git-scm.com/docs/git-pull"&gt;&lt;code&gt;git pull&lt;/code&gt;&lt;/a&gt; command:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ git pull
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 696 bytes | 174.00 KiB/s, done.
From github.com:USERNAME/sample_project
   be62476..605b6a7  main       -&amp;gt; origin/main
Updating be62476..605b6a7
Fast-forward
 main.py | 1 +
 1 file changed, 1 insertion(+)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Again, the command's output provides all the details about the operation. Note that &lt;code&gt;git pull&lt;/code&gt; will download the remote branch and update the local branch in a single step.&lt;/p&gt;
&lt;p&gt;If we want to download the remote branch without updating the local one, then we can use the &lt;code&gt;[git fetch](https://git-scm.com/docs/git-fetch)&lt;/code&gt; command. This practice gives us the chance to review the changes and commit them to our local repo only if they look right.&lt;/p&gt;
&lt;p&gt;For example, go ahead and update the remote copy of &lt;code&gt;main.py&lt;/code&gt; by adding another statement like &lt;code&gt;print("Let's go!!")&lt;/code&gt;. Commit the changes. Then get back to your local repo and run the following command:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ git fetch
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 731 bytes | 243.00 KiB/s, done.
From github.com:USERNAME/sample_project
   605b6a7..ba489df  main       -&amp;gt; origin/main
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This command downloaded the latest changes from &lt;code&gt;origin/main&lt;/code&gt; to our local repo. Now we can compare the remote copy of &lt;code&gt;main.py&lt;/code&gt; to the local copy. To do this, we can use the &lt;a href="https://git-scm.com/docs/git-diff"&gt;&lt;code&gt;git diff&lt;/code&gt;&lt;/a&gt; command as follows:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-shell"&gt;shell&lt;/span&gt;
&lt;pre&gt;&lt;code class="shell"&gt;$ git diff main origin/main
diff --git a/main.py b/main.py
index be2aa66..4f0e7cf 100644
--- a/main.py
+++ b/main.py
@@ -1,3 +1,4 @@
 print('Hello, World!')
 print('Welcome to PythonGUIs!')
 print("Your Git Tutorial is Here...")
+print("Let's go!!")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In the command's output, you can see that the remote branch adds a line containing &lt;code&gt;print("Let's go!!")&lt;/code&gt; to the end of &lt;code&gt;main.py&lt;/code&gt;. This change looks good, so we can use &lt;code&gt;git pull&lt;/code&gt; to commit the change automatically.&lt;/p&gt;
&lt;h3&gt;Exploring Alternatives to GitHub&lt;/h3&gt;
&lt;p&gt;While GitHub is the most popular public Git server and collaboration platform in use, it is far from being the only one. &lt;a href="https://about.gitlab.com"&gt;GitLab.com&lt;/a&gt; and &lt;a href="https://bitbucket.org"&gt;BitBucket&lt;/a&gt; are popular commercial alternatives similar to GitHub. While they have paid plans, both offer free plans, with some restrictions, for individual users.&lt;/p&gt;
&lt;p&gt;Although, if you would like to use a completely open-source platform instead, &lt;a href="https://codeberg.org"&gt;Codeberg&lt;/a&gt; might be a good option. It's a community-driven alternative with a focus on supporting &lt;a href="https://en.wikipedia.org/wiki/Free_software"&gt;Free Software&lt;/a&gt;. Therefore, in order to use Codeberg, your project needs to use a &lt;a href="https://docs.codeberg.org/getting-started/faq/#is-it-allowed-to-host-non-free-software%3F"&gt;compatible open-source license&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Optionally, you can also run your own Git server. While you could just use barebones &lt;code&gt;git&lt;/code&gt; for this, software such as &lt;a href="https://gitlab.com/rluna-gitlab/gitlab-ce"&gt;GitLab Community Edition (CE)&lt;/a&gt; and &lt;a href="https://forgejo.org/"&gt;Forgejo&lt;/a&gt; provide you with both the benefits of running your own server and the experience of using a service like GitHub.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;By now, you're able to use Git for version-controlling your projects. Git is a powerful tool that will make you much more efficient and productive, especially as the scale of your project grows over time.&lt;/p&gt;
&lt;p&gt;While this guide introduced you to most of its basic concepts and common commands, Git has many more commands and options that you can use to be even more productive. Now, you know enough to get up to speed with Git.&lt;/p&gt;</content><category term="python"/></entry><entry><title>Working With Classes in Python — Understanding the Intricacies of Python Classes</title><link href="https://www.martinfitzpatrick.com/python-classes/" rel="alternate"/><published>2023-03-06T06:00:00+00:00</published><updated>2023-03-06T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2023-03-06:/python-classes/</id><summary type="html">Python supports &lt;a href="https://en.wikipedia.org/wiki/Object-oriented_programming"&gt;object-oriented programming (OOP)&lt;/a&gt; through classes, which allow you to bundle data and behavior in a single entity. Python classes allow you to quickly model concepts by creating representations of real objects that you can then use to organize your code.</summary><content type="html">&lt;p&gt;Python supports &lt;a href="https://en.wikipedia.org/wiki/Object-oriented_programming"&gt;object-oriented programming (OOP)&lt;/a&gt; through classes, which allow you to bundle data and behavior in a single entity. Python classes allow you to quickly model concepts by creating representations of real objects that you can then use to organize your code.&lt;/p&gt;
&lt;p&gt;In this tutorial, you'll learn how OOP and classes work in Python. This knowledge will allow you to quickly grasp how you can use their classes and APIs to create robust Python applications.&lt;/p&gt;
&lt;h2 id="defining-classes-in-python"&gt;Defining Classes in Python&lt;/h2&gt;
&lt;p&gt;Python classes are templates or blueprints that allow us to create objects through &lt;strong&gt;instantiation&lt;/strong&gt;. These objects will contain data representing the object's state, and methods that will act on the data providing the object's behavior.&lt;/p&gt;
&lt;p class="admonition admonition-info"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-info"&gt;&lt;/i&gt;&lt;/span&gt;  Instantiation is the process of creating instances of a class by calling the &lt;strong&gt;class constructor&lt;/strong&gt; with appropriate arguments.&lt;/p&gt;
&lt;p&gt;Attributes and methods make up what is known as the class &lt;strong&gt;interface&lt;/strong&gt; or &lt;a href="https://en.wikipedia.org/wiki/API"&gt;API&lt;/a&gt;. This interface allows us to operate on the objects without needing to understand their internal implementation and structure.&lt;/p&gt;
&lt;p&gt;Alright, it is time to start creating our own classes. We'll start by defining a &lt;code&gt;Color&lt;/code&gt; class with minimal functionality. To do that in Python, you'll use the &lt;code&gt;class&lt;/code&gt; keyword followed by the class name. Then you provide the class body in the next &lt;a href="https://en.wikipedia.org/wiki/Indentation_(typesetting)"&gt;indentation&lt;/a&gt; level:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; class Color:
...     pass
...

&amp;gt;&amp;gt;&amp;gt; red = Color()

&amp;gt;&amp;gt;&amp;gt; type(red)
&amp;lt;class '__main__.Color'&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we defined our &lt;code&gt;Color&lt;/code&gt; class using the &lt;code&gt;class&lt;/code&gt; keyword. This class is empty. It doesn't have attributes or methods. Its body only contains a &lt;code&gt;pass&lt;/code&gt; statement, which is Python's way to do nothing.&lt;/p&gt;
&lt;p&gt;Even though the class is minimal, it allows us to create instances by calling its constructor, &lt;code&gt;Colo()&lt;/code&gt;. So, &lt;code&gt;red&lt;/code&gt; is an instance of &lt;code&gt;Color&lt;/code&gt;. Now let's make our &lt;code&gt;Color&lt;/code&gt; class more fun by adding some attributes.&lt;/p&gt;
&lt;h3&gt;Adding Class and Instance &amp;Acy;ttributes&lt;/h3&gt;
&lt;p&gt;Python classes allow you to add two types of attributes. You can have &lt;strong&gt;class&lt;/strong&gt; and &lt;strong&gt;instance attributes&lt;/strong&gt;. A class attribute belongs to its containing class. Its data is common to the class and all its instances. To access a class attribute, we can use either the class or any of its instances.&lt;/p&gt;
&lt;p&gt;Let's now add a class attribute to our &lt;code&gt;Color&lt;/code&gt; class. For example, let's say we need to keep note of how many instance of &lt;code&gt;Color&lt;/code&gt; your code creates. Then you can have a &lt;code&gt;color_count&lt;/code&gt; attribute:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; class Color:
...     color_count = 0
...     def __init__(self):
...         Color.color_count += 1
...

&amp;gt;&amp;gt;&amp;gt; red = Color()
&amp;gt;&amp;gt;&amp;gt; green = Color()

&amp;gt;&amp;gt;&amp;gt; Color.color_count
2
&amp;gt;&amp;gt;&amp;gt; red.color_count
2
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now &lt;code&gt;Color&lt;/code&gt; has a class attribute called &lt;code&gt;color_count&lt;/code&gt; that gets incremented every time we create a new instance. We can quickly access that attribute using either the class directly or one of its instances, like &lt;code&gt;red&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To follow up with this example, say that we want to represent our &lt;code&gt;Color&lt;/code&gt; objects using red, green, and blue attributes as part of the &lt;a href="https://en.wikipedia.org/wiki/RGB_color_model"&gt;RGB color model&lt;/a&gt;. These attributes should have specific values for specific instances of the class. So, they should be instance attributes.&lt;/p&gt;
&lt;p&gt;To add an instance attribute to a Python class, you must use the &lt;code&gt;.__init__()&lt;/code&gt; special method, which we introduced in the previous code but didn't explain. This method works as the instance initializer because it allows you to provide initial values for instance attributes:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; class Color:
...     color_count = 0
...     def __init__(self, red, green, blue):
...         Color.color_count += 1
...         self.red = red
...         self.green = green
...         self.blue = blue
...

&amp;gt;&amp;gt;&amp;gt; red = Color(255, 0, 0)

&amp;gt;&amp;gt;&amp;gt; red.red
255
&amp;gt;&amp;gt;&amp;gt; red.green
0
&amp;gt;&amp;gt;&amp;gt; red.blue
0

&amp;gt;&amp;gt;&amp;gt; Color.red
Traceback (most recent call last):
    ...
AttributeError: type object 'Color' has no attribute 'red'
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Cool! Now our &lt;code&gt;Color&lt;/code&gt; class looks more useful. It has the usual class attributes and also three new instance attributes. Note that, unlike class attributes, instance attributes can't be accessed through the class itself. They're specific to a concrete instance.&lt;/p&gt;
&lt;p&gt;There's something that jumps into sight in this new version of &lt;code&gt;Color&lt;/code&gt;. What is the &lt;code&gt;self&lt;/code&gt; argument in the definition of &lt;code&gt;.__init__()&lt;/code&gt;? This attribute holds a reference to the current instance. Using the name &lt;code&gt;self&lt;/code&gt; to identify the current instance is a strong convention in Python.&lt;/p&gt;
&lt;p&gt;We'll use &lt;code&gt;self&lt;/code&gt; as the first or even the only argument to instance methods like &lt;code&gt;.__init__()&lt;/code&gt;. Inside an instance method, we'll use &lt;code&gt;self&lt;/code&gt; to access other methods and attributes defined in the class. To do that, we must prepend &lt;code&gt;self&lt;/code&gt; to the name of the target attribute or method instance of the class.&lt;/p&gt;
&lt;p&gt;For example, our class has an attribute &lt;code&gt;.red&lt;/code&gt; that we can access using the syntax &lt;code&gt;self.red&lt;/code&gt; inside the class. This will return the number stored under that name. From outside the class, you need to use a concrete instance instead of &lt;code&gt;self&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Providing Behavior With Methods&lt;/h3&gt;
&lt;p&gt;A class bundles data (attributes) and behavior (methods) together in an object. You'll use the data to set the object's state and the methods to operate on that data or state.&lt;/p&gt;
&lt;p&gt;Methods are just functions that we define inside a class. Like functions, methods can take arguments, return values, and perform different computations on an object's attributes. They allow us to make our objects usable.&lt;/p&gt;
&lt;p&gt;In Python, we can define three types of methods in our classes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Instance method&lt;/strong&gt;s, which need the instance (&lt;code&gt;self&lt;/code&gt;) as their first argument&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Class methods&lt;/strong&gt;, which take the class (&lt;code&gt;cls&lt;/code&gt;) as their first argument&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Static methods&lt;/strong&gt;, which take neither the class nor the instance&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let's now talk about instance methods. Say that we need to get the attributes of our &lt;code&gt;Color&lt;/code&gt; class as a tuple of numbers. In this case, we can add an &lt;code&gt;.as_tuple()&lt;/code&gt; method like the following:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;class Color:
    representation = "RGB"

    def __init__(self, red, green, blue):
        self.red = red
        self.green = green
        self.blue = blue

    def as_tuple(self):
        return self.red, self.green, self.blue
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This new method is pretty straightforward. Since it's an instance method, it takes &lt;code&gt;self&lt;/code&gt; as its first argument. Then it returns a tuple containing the attributes &lt;code&gt;.red&lt;/code&gt;, &lt;code&gt;.green&lt;/code&gt;, and &lt;code&gt;.blue&lt;/code&gt;. Note how you need to use &lt;code&gt;self&lt;/code&gt; to access the attributes of the current instance inside the class.&lt;/p&gt;
&lt;p&gt;This method may be useful if you need to iterate over the RGB components of your color objects:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; red = Color(255, 0, 0)
&amp;gt;&amp;gt;&amp;gt; red.as_tuple()
(255, 0, 0)

&amp;gt;&amp;gt;&amp;gt; for level in red.as_tuple():
...     print(level)
...
255
0
0
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Our &lt;code&gt;as_tuple()&lt;/code&gt; method works great! It returns a tuple containing the RGB components of our color objects.&lt;/p&gt;
&lt;p&gt;We can also add class methods to our Python classes. To do this, we need to use the &lt;a href="https://docs.python.org/3/library/functions.html#classmethod"&gt;&lt;code&gt;@classmethod&lt;/code&gt;&lt;/a&gt; decorator as follows:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;class Color:
    representation = "RGB"

    def __init__(self, red, green, blue):
        self.red = red
        self.green = green
        self.blue = blue

    def as_tuple(self):
        return self.red, self.green, self.blue

    @classmethod
    def from_tuple(cls, rbg):
        return cls(*rbg)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;from_tuple()&lt;/code&gt; method takes a tuple object containing the RGB components of a desired color as an argument, creates a valid color object from it, and returns the object back to the caller:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; blue = Color.from_tuple((0, 0, 255))
&amp;gt;&amp;gt;&amp;gt; blue.as_tuple()
(0, 0, 255)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we use the &lt;code&gt;Color&lt;/code&gt; class to access the class method &lt;code&gt;from_tuple()&lt;/code&gt;. We can also access the method using a concrete instance of this class. However, in both cases, we'll get a completely new object.&lt;/p&gt;
&lt;p&gt;Finally, Python classes can also have static methods that we can define with the &lt;a href="https://docs.python.org/3/library/functions.html#staticmethod"&gt;&lt;code&gt;@staticmethod&lt;/code&gt;&lt;/a&gt; decorator:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;class Color:
    representation = "RGB"

    def __init__(self, red, green, blue):
        self.red = red
        self.green = green
        self.blue = blue

    def as_tuple(self):
        return self.red, self.green, self.blue

    @classmethod
    def from_tuple(cls, rbg):
        return cls(*rbg)

    @staticmethod
    def color_says(message):
        print(message)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Static methods don't operate either on the current instance self or the current class &lt;code&gt;cls&lt;/code&gt;. These methods can work as independent functions. However, we typically put them inside a class when they are related to the class, and we need to have them accessible from the class and its instances.&lt;/p&gt;
&lt;p&gt;Here's how the method works:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; Color.color_says("Hello from the Color class!")
Hello from the Color class!

&amp;gt;&amp;gt;&amp;gt; red = Color(255, 0, 0)
&amp;gt;&amp;gt;&amp;gt; red.color_says("Hello from the red instance!")
Hello from the red instance!
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This method accepts a message and prints it on your screen. It works independently from the class or instance attributes. Note that you can call the method using the class or any of its instances.&lt;/p&gt;
&lt;h3&gt;Writing Getter &amp;amp; Setter Methods&lt;/h3&gt;
&lt;p&gt;Programming languages like Java and C++ rely heavily on setter and getter methods to retrieve and update the attributes of a class and its instances. These methods encapsulate an attribute allowing us to get and change its value without directly accessing the attribute itself.&lt;/p&gt;
&lt;p&gt;For example, say that we have a &lt;code&gt;Label&lt;/code&gt; class with a &lt;code&gt;text&lt;/code&gt; attribute. We can make &lt;code&gt;text&lt;/code&gt; a non-public attribute and provide getter and setter methods to manipulate the attributes according to our needs:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;class Label:
    def __init__(self, text):
        self.set_text(text)

    def text(self):
        return self._text

    def set_text(self, value):
        self._text = str(value)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this class, the &lt;code&gt;text()&lt;/code&gt; method is the getter associated with the &lt;code&gt;._text&lt;/code&gt; attribute, while the &lt;code&gt;set_text()&lt;/code&gt; method is the setter for &lt;code&gt;._text&lt;/code&gt;. Note how &lt;code&gt;._text&lt;/code&gt; is a non-public attribute. We know this because it has a leading underscore on its name.&lt;/p&gt;
&lt;p&gt;The setter method calls &lt;code&gt;str()&lt;/code&gt; to convert any input value into a string. Therefore, we can call this method with any type of object. It will convert any input argument into a string, as you will see in a moment.&lt;/p&gt;
&lt;p class="admonition admonition-info"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-info"&gt;&lt;/i&gt;&lt;/span&gt;  If you come from programming languages like Java or C++, you need to know Python doesn't have the notion of &lt;strong&gt;private&lt;/strong&gt;, &lt;strong&gt;protected&lt;/strong&gt;, and &lt;strong&gt;public&lt;/strong&gt; attributes. In Python, you'll use a naming convention to signal that an attribute is non-public. This convention consists of adding a leading underscore to the attribute's name. Note that this naming pattern only indicates that the attribute isn't intended to be used directly. It doesn't prevent direct access, though.&lt;/p&gt;
&lt;p&gt;This class works as follows:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; label = Label("Python!")

&amp;gt;&amp;gt;&amp;gt; label.text()
'Python!'

&amp;gt;&amp;gt;&amp;gt; label.set_text("Classes!")
&amp;gt;&amp;gt;&amp;gt; label.text()
'Classes!'

&amp;gt;&amp;gt;&amp;gt; label.set_text(123)
&amp;gt;&amp;gt;&amp;gt; label.text()
'123'
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we create an instance of &lt;code&gt;Label&lt;/code&gt;. The original text is passed to the class constructor, &lt;code&gt;Label()&lt;/code&gt;, which automatically calls &lt;code&gt;__init__()&lt;/code&gt; to set the value of &lt;code&gt;._text&lt;/code&gt; by calling the setter method &lt;code&gt;text()&lt;/code&gt;. You can use &lt;code&gt;text()&lt;/code&gt; to access the label's text and &lt;code&gt;set_text()&lt;/code&gt; to update it. Remember that any input will be converted into a string, as we can see in the final example above.&lt;/p&gt;
&lt;p&gt;The getter and setter pattern is pretty common in languages like Java and C++.  However, this pattern is less popular among Python developers. Instead, they use the &lt;code&gt;@property&lt;/code&gt; decorator to hide attributes behind properties.&lt;/p&gt;
&lt;p&gt;Here's how most Python developer will write their &lt;code&gt;Label&lt;/code&gt; class:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;class Label:
    def __init__(self, text):
        self.text = text

    @property
    def text(self):
        return self._text

    @text.setter
    def text(self, value):
        self._text = str(value)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This class defines &lt;code&gt;.text&lt;/code&gt; as a property. This property has getter and setter methods. Python calls them automatically when we access the attribute or update its value in an assignment:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; label = Label("Python!")

&amp;gt;&amp;gt;&amp;gt; label.text
'Python!'

&amp;gt;&amp;gt;&amp;gt; label.text = "Class"
&amp;gt;&amp;gt;&amp;gt; label.text
'Class'

&amp;gt;&amp;gt;&amp;gt; label.text = 123
&amp;gt;&amp;gt;&amp;gt; label.text
'123'
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Python properties allow you to add function behavior to your attributes while permitting you to use them as normal attributes instead of as methods.&lt;/p&gt;
&lt;h3&gt;Writing Special Methods&lt;/h3&gt;
&lt;p&gt;Python supports many &lt;a href="https://docs.python.org/3/glossary.html#term-special-method"&gt;special methods&lt;/a&gt;, also known as &lt;strong&gt;dunder&lt;/strong&gt; or &lt;strong&gt;magic&lt;/strong&gt; methods, that are part of its class mechanism. We can identify these methods because their names start and end with a double underscore, which is the origin of their other name: dunder methods.&lt;/p&gt;
&lt;p&gt;These methods accomplish different tasks in Python's class mechanism. They all have a common feature: &lt;em&gt;Python calls them automatically depending on the operation we run.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For example, all Python objects are printable. We can print them to the screen using the &lt;code&gt;print()&lt;/code&gt; function. Calling &lt;code&gt;print()&lt;/code&gt; internally falls back to calling the target object's &lt;code&gt;__str__()&lt;/code&gt; special method:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; label = Label("Python!")

&amp;gt;&amp;gt;&amp;gt; print(label)
&amp;lt;__main__.Label object at 0x10354efd0&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we've printed our &lt;code&gt;label&lt;/code&gt; object. This action provides some information about the object and the memory address where it lives. However, the actual output is not very useful from the user's perspective.&lt;/p&gt;
&lt;p&gt;Fortunately, we can improve this by providing our &lt;code&gt;Label&lt;/code&gt; class with an appropriate &lt;code&gt;__str__()&lt;/code&gt; method:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;class Label:
    def __init__(self, text):
        self.text = text

    @property
    def text(self):
        return self._text

    @text.setter
    def text(self, value):
        self._text = str(value)

    def __str__(self):
        return self.text
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;__str__()&lt;/code&gt; method must return a user-friendly string representation for our objects. In this case, when we print an instance of &lt;code&gt;Label&lt;/code&gt; to the screen, the label's text will be displayed:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; label = Label("Python!")

&amp;gt;&amp;gt;&amp;gt; print(label)
Python!
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;As you can see, Python takes care of calling &lt;code&gt;__str__()&lt;/code&gt; automatically when we use the &lt;code&gt;print()&lt;/code&gt; function to display our instances of &lt;code&gt;Label&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Another special method that belongs to Python's class mechanism is &lt;code&gt;__repr__()&lt;/code&gt;. This method returns a developer-friendly string representation of a given object. Here, developer-friendly implies that the representation should allow a developer to recreate the object itself.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;class Label:
    def __init__(self, text):
        self.text = text

    @property
    def text(self):
        return self._text

    @text.setter
    def text(self, value):
        self._text = str(value)

    def __str__(self):
        return self.text

    def __repr__(self):
        return f"{type(self).__name__}(text='{self.text}')"
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;__repr__()&lt;/code&gt; method returns a string representation of the current objects. This string differs from what &lt;code&gt;__str__()&lt;/code&gt; returns:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; label = Label("Python!")
&amp;gt;&amp;gt;&amp;gt; label
Label(text='Python!')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now when you access the instance on your REPL session, you get a string representation of the current object. You can copy and paste this representation to recreate the object in an appropriate environment.&lt;/p&gt;
&lt;h2 id="reusing-code-with-inheritance"&gt;Reusing Code With Inheritance&lt;/h2&gt;
&lt;p&gt;Inheritance is an advanced topic in object-oriented programming. It allows you to create hierarchies of classes where each subclass inherits all the attributes and behaviors from its parent class or classes. Arguably, &lt;strong&gt;code reuse&lt;/strong&gt; is the primary use case of inheritance.&lt;/p&gt;
&lt;p&gt;Yes, we code a base class with a given functionality and make that functionality available to its subclass through inheritance. This way, we implement the functionality only once and reuse it in every subclass.&lt;/p&gt;
&lt;p&gt;Python classes support &lt;strong&gt;single&lt;/strong&gt; and &lt;strong&gt;multiple&lt;/strong&gt; inheritance. For example, let's say we need to create a button class. This class needs &lt;code&gt;.width&lt;/code&gt; and &lt;code&gt;.height&lt;/code&gt; attributes that define its rectangular shape. The class also needs a label for displaying some informative text.&lt;/p&gt;
&lt;p&gt;We can code this class from scratch, or we can use inheritance and reuse the code of our current &lt;code&gt;Label&lt;/code&gt; class. Here's how to do this:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;class Button(Label):
    def __init__(self, text, width, height):
        super().__init__(text)
        self.width = width
        self.height = height

    def __repr__(self):
        return (
            f"{type(self).__name__}"
            f"(text='{self.text}', "
            f"width={self.width}, "
            f"height={self.height})"
        )
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To inherit from a parent class in Python, we need to list the parent class or classes in the subclass definition. To do this, we use a pair of parentheses and a comma-separated list of parent classes. If we use several parent classes, then we're using multiple inheritance, which can be challenging to reason about.&lt;/p&gt;
&lt;p&gt;The first line in &lt;code&gt;__init__()&lt;/code&gt; calls the &lt;code&gt;__init__()&lt;/code&gt; method on the parent class to properly initialize its &lt;code&gt;.text&lt;/code&gt; attribute. To do this, we use the built-in &lt;code&gt;super()&lt;/code&gt; function. Then we define the &lt;code&gt;.width&lt;/code&gt; and &lt;code&gt;.height&lt;/code&gt; attributes, which are specific to our &lt;code&gt;Button&lt;/code&gt; class. Finally, we provide a custom implementation of &lt;code&gt;__repr__()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here's how our &lt;code&gt;Button&lt;/code&gt; class works:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; button = Button("Ok", 10, 5)

&amp;gt;&amp;gt;&amp;gt; button.text
'Ok'
&amp;gt;&amp;gt;&amp;gt; button.text = "Click Me!"
&amp;gt;&amp;gt;&amp;gt; button.text
'Click Me!'

&amp;gt;&amp;gt;&amp;gt; button.width
10
&amp;gt;&amp;gt;&amp;gt; button.height
5

&amp;gt;&amp;gt;&amp;gt; button
Button(text='Ok', width=10, height=5)
&amp;gt;&amp;gt;&amp;gt; print(button)
Click Me!
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;As you can conclude from this code, &lt;code&gt;Button&lt;/code&gt; has inherited the &lt;code&gt;.text&lt;/code&gt; attribute from &lt;code&gt;Label&lt;/code&gt;. This attribute is completely functional. Our class has also inherited the &lt;code&gt;__str__()&lt;/code&gt; method from &lt;code&gt;Label&lt;/code&gt;. That's why we get the button's text when we print the instance.&lt;/p&gt;
&lt;h2 id="wrapping-up-classes-related-concepts"&gt;Wrapping Up Classes-Related Concepts&lt;/h2&gt;
&lt;p&gt;As we've seen, Python allows us to write classes that work as templates that you can use to create concrete objects that bundle together data and behavior. The building blocks of Python classes are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Attributes&lt;/strong&gt;, which hold the data in a class&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Methods&lt;/strong&gt;, which provide the behaviors of a class&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The attributes of a class define the class's data, while the methods provide the class's behaviors, which typically act on that data.&lt;/p&gt;
&lt;p&gt;To better understand OOP and classes in Python, we should first discuss some terms that are commonly used in this aspect of Python development:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Classes&lt;/strong&gt; are blueprints or templates for creating objects -- just like a blueprint for creating a car, plane, house, or anything else. In programming, this blueprint will define the data (attributes) and behavior (methods) of the object and will allow us to create multiple objects of the same kind.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Objects&lt;/strong&gt; or &lt;strong&gt;Instances&lt;/strong&gt; are the realizations of a class. We can create objects from the blueprint provided by the class. For example, you can create John's car from a &lt;code&gt;Car&lt;/code&gt; class.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Methods&lt;/strong&gt; are functions defined within a class. They provide the behavior of an object of that class. For example, our &lt;code&gt;Car&lt;/code&gt; class can have methods to start the engine, turn right and left, stop, and so on.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Attributes&lt;/strong&gt; are properties of an object or class. We can think of attributes as variables defined in a class or object. Therefore, we can have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;class attributes&lt;/strong&gt;, which are specific to a concrete class and common to all the instances of that class. You can access them either through the class or an object of that class. For example, if we're dealing with a single car manufacturer, then our &lt;code&gt;Car&lt;/code&gt; class can have a manufacturer attribute that identifies it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;instance attributes&lt;/strong&gt;, which are specific to a concrete instance. You can access them through the specific instance. For example, our &lt;code&gt;Car&lt;/code&gt; class can have attributes to store properties such as the maximum speed, the number of passengers, the car's weight, and so on.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Instantiation&lt;/strong&gt; is the process of creating an individual &lt;em&gt;instance&lt;/em&gt; from a class. For example, we can create John's car, Jane's car, and Linda's car from our &lt;code&gt;Car&lt;/code&gt; class through instantiation. In Python, this process runs through two steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Instance creation&lt;/strong&gt;: Creates a new object and allocates memory for storing it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instance initialization&lt;/strong&gt;: Initializes all the attributes of the current object with appropriate values.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Inheritance&lt;/strong&gt; is a mechanism of code reuse that allows us to inherit attributes and methods from one or multiple existing classes. In this context, we'll hear terms like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Parent class&lt;/strong&gt;: The class we're inheriting from. This class is also known as the &lt;strong&gt;superclass&lt;/strong&gt; or &lt;strong&gt;base class&lt;/strong&gt;. If we have one parent class, then we're using &lt;strong&gt;single inheritance&lt;/strong&gt;. If we have more than one parent class, then we're using &lt;strong&gt;multiple inheritance&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Child class&lt;/strong&gt;: The class that inherits from a given parent. This class is also known as the &lt;strong&gt;subclass&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Don't feel frustrated or bad if you don't understand all these terms immediately. They'll become more familiar with use as you use them in your own Python code.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Now you know the basics of Python classes. You also learned fundamental concepts of object-oriented programming, such as inheritance.&lt;/p&gt;</content><category term="python"/></entry><entry><title>Getting started with VS Code for Python — Setting up a Development Environment for Python programming</title><link href="https://www.martinfitzpatrick.com/getting-started-vs-code-python/" rel="alternate"/><published>2022-09-21T09:00:00+00:00</published><updated>2022-09-21T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2022-09-21:/getting-started-vs-code-python/</id><summary type="html">Setting up a working development environment is the first step for any project. Your development environment setup will determine how easy it is to develop and maintain your projects over time. That makes it important to choose the &lt;em&gt;right tools for your project&lt;/em&gt;. This article will guide you through how to set up Visual Studio Code, which is a popular free-to-use, cross-platform code editor developed by Microsoft, in order to develop Python applications.</summary><content type="html">
            &lt;p&gt;Setting up a working development environment is the first step for any project. Your development environment setup will determine how easy it is to develop and maintain your projects over time. That makes it important to choose the &lt;em&gt;right tools for your project&lt;/em&gt;. This article will guide you through how to set up Visual Studio Code, which is a popular free-to-use, cross-platform code editor developed by Microsoft, in order to develop Python applications.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt; &lt;em&gt;Visual Studio Code&lt;/em&gt; is not to be confused with &lt;em&gt;Visual Studio&lt;/em&gt;, which is a separate product also offered by Microsoft. Visual Studio is a fully-fledged IDE that is mainly geared towards Windows application development using C# and the .NET Framework.&lt;/p&gt;
&lt;h2 id="setup-a-python-environment"&gt;Setup a Python environment&lt;/h2&gt;
&lt;p&gt;In case you haven't already done this, Python needs to be installed on the development machine. You can do this by going to &lt;a href="https://www.python.org/downloads/"&gt;python.org&lt;/a&gt; and grabbing the specific installer for either Windows or macOS. Python is also available for installation via Microsoft Store on Windows devices.&lt;/p&gt;
&lt;p class="admonition admonition-warning"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-exclamation-circle"&gt;&lt;/i&gt;&lt;/span&gt;  Make sure that you select the option to &lt;em&gt;Add Python to PATH&lt;/em&gt; during installation (via the installer).&lt;/p&gt;
&lt;p&gt;If you are on Linux, you can check if Python is already installed on your machine by typing &lt;code&gt;python3 --version&lt;/code&gt; in a terminal. If it returns an error, you need to install it from your distribution's repository. On Ubuntu/Debian, this can be done by typing &lt;code&gt;sudo apt install python3&lt;/code&gt;. Both &lt;code&gt;pip&lt;/code&gt; (or &lt;code&gt;pip3&lt;/code&gt;) and &lt;code&gt;venv&lt;/code&gt; are distributed as separate packages on Ubuntu/Debian and can also be installed by typing &lt;code&gt;sudo apt install python3-pip python3-venv&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="setup-visual-studio-code"&gt;Setup Visual Studio Code&lt;/h2&gt;
&lt;p&gt;First, head over to to &lt;a href="https://code.visualstudio.com/"&gt;code.visualstudio.com&lt;/a&gt; and grab the installer for your specific platform.&lt;/p&gt;
&lt;p&gt;If you are on a Raspberry Pi (with Raspberry Pi OS), you can also install VS Code by simply typing &lt;code&gt;sudo apt install code&lt;/code&gt;. On &lt;a href="https://snapcraft.io/docs/installing-snapd"&gt;Linux distributions that support Snaps&lt;/a&gt;, you can do it by typing &lt;code&gt;sudo snap install code --classic&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once VS Code is installed, head over to the &lt;em&gt;Extensions&lt;/em&gt; tab in the sidebar on the left by clicking on it or by pressing &lt;code&gt;CTRL+SHIFT+X&lt;/code&gt;. Search for the 'Python' extension published by Microsoft and click on &lt;em&gt;Install&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The Extensions tab in the left-hand sidebar" src="https://www.martinfitzpatrick.com/static/tutorials/developer/vscode/extensions.png"  loading="lazy" width="550" height="938"/&gt;
&lt;em&gt;The Extensions tab in the left-hand sidebar.&lt;/em&gt;&lt;/p&gt;
&lt;!-- Screenshot, A Python file opened in VS Code showing autocomplete suggestions and the program running in the in-built terminal. --&gt;
&lt;h2 id="usage-and-configuration"&gt;Usage and Configuration&lt;/h2&gt;
&lt;p&gt;Now that you have finished setting up VS Code, you can go ahead and create a new Python file. Remember that the Python extension only works if you open a &lt;code&gt;.py&lt;/code&gt; file or have selected the language mode for the active file as Python.&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  To change the language mode for the active file, simply press &lt;code&gt;CTRL+K&lt;/code&gt; once and then press &lt;code&gt;M&lt;/code&gt; after releasing the previous keys. This kind of keyboard shortcut is called a &lt;em&gt;chord&lt;/em&gt; in VS Code. You can see more of them by pressing &lt;code&gt;CTRL+K CTRL+S&lt;/code&gt; (another chord).&lt;/p&gt;
&lt;p&gt;The Python extension in VS Code allows you to directly run a Python file by clicking on the 'Play' button on the top-right corner of the editor (without having to type &lt;code&gt;python file.py&lt;/code&gt; in the terminal).&lt;/p&gt;
&lt;p&gt;You can also do it by pressing &lt;code&gt;CTRL+SHIFT+P&lt;/code&gt; to open the &lt;em&gt;Command Palette&lt;/em&gt; and running the &lt;code&gt;&amp;gt; Python: Run File in Terminal&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;Finally, you can configure VS Code's settings by going to &lt;code&gt;File &amp;gt; Preferences &amp;gt; Settings&lt;/code&gt; or by pressing &lt;code&gt;CTRL+COMMA&lt;/code&gt;. In VS Code, each individual setting has an unique &lt;em&gt;identifier&lt;/em&gt; which you can see by clicking on the cog wheel that appears to the left of each setting and clicking on 'Copy Setting ID'. This ID is what will be referred to while talking about a specific setting. You can also search for this ID in the search bar under &lt;em&gt;Settings&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="linting-and-formatting-support-optional"&gt;Linting and Formatting Support (Optional)&lt;/h2&gt;
&lt;p&gt;Linters make it easier to find errors and check the quality of your code. On the other hand, code formatters help keep the source code of your application compliant with PEP (Python Enhancement Proposal) standards, which make it easier for other developers to read your code and collaborate with you.&lt;/p&gt;
&lt;p&gt;For VS Code to provide linting support for your projects, you must first install a preferred linter like &lt;code&gt;flake8&lt;/code&gt; or &lt;code&gt;pylint&lt;/code&gt;.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-bash"&gt;bash&lt;/span&gt;
&lt;pre&gt;&lt;code class="bash"&gt;pip install flake8
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then, go to &lt;em&gt;Settings&lt;/em&gt; in VS Code and toggle the relevant setting (e.g. &lt;code&gt;python.linting.flake8Enabled&lt;/code&gt;) for the &lt;em&gt;Python&lt;/em&gt; extension depending on what you installed. You also need to make sure that &lt;code&gt;python.linting.enabled&lt;/code&gt; is toggled on.&lt;/p&gt;
&lt;p&gt;A similar process must be followed for code formatting. First, install something like &lt;code&gt;autopep8&lt;/code&gt; or &lt;code&gt;black&lt;/code&gt;.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-bash"&gt;bash&lt;/span&gt;
&lt;pre&gt;&lt;code class="bash"&gt;pip install autopep8
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You then need to tell VS Code which formatter to use by modifying &lt;code&gt;python.formatting.provider&lt;/code&gt; and toggle on &lt;code&gt;editor.formatOnSave&lt;/code&gt; so that it works without manual intervention.&lt;/p&gt;
&lt;p class="admonition admonition-warning"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-exclamation-circle"&gt;&lt;/i&gt;&lt;/span&gt;  If &lt;code&gt;pip&lt;/code&gt; warns that the installed modules aren't in your PATH, you may have to specify the path to their location in VS Code (under &lt;em&gt;Settings&lt;/em&gt;). Follow the method described under &lt;em&gt;Working With Virtual Environments&lt;/em&gt; to do that.&lt;/p&gt;
&lt;p&gt;Now, when you create a new Python file, VS Code automatically gives you a list of &lt;em&gt;Problems&lt;/em&gt; (&lt;code&gt;CTRL+SHIFT+M&lt;/code&gt;) in your program and formats the code on saving the file.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Identified problems in the source code." src="https://www.martinfitzpatrick.com/static/tutorials/developer/vscode/problems.png"  loading="lazy" width="2072" height="277"/&gt;
&lt;em&gt;Identified problems in the source code, along with a description and line/column numbers.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You can also find the location of identified problems from the source overview on the right hand, inside the scrollbar.&lt;/p&gt;
&lt;h2 id="working-with-virtual-environments"&gt;Working With Virtual Environments&lt;/h2&gt;
&lt;p&gt;Virtual environments are a way of life for Python developers. Most Python projects require the installation of external packages and modules (via &lt;code&gt;pip&lt;/code&gt;). Virtual environments allow you to separate one project's packages from your other projects, which may require a different version of those same packages. Hence, it allows all those projects to have the specific dependencies they require to work.&lt;/p&gt;
&lt;p&gt;The Python extension makes it easier for you by automatically activating the desired virtual environment for the in-built terminal and &lt;em&gt;Run Python File&lt;/em&gt; command after you set the path to the Python interpreter. By default, the path is set to use the system's Python installation (without a virtual environment).&lt;/p&gt;
&lt;p&gt;To use a virtual environment for your project/workspace, you need to first make a new one by opening a terminal (&lt;code&gt;View &amp;gt; Terminal&lt;/code&gt;) and typing &lt;code&gt;python -m venv .venv&lt;/code&gt;. Then, you can set the default interpreter for that project by opening the &lt;em&gt;Command Palette&lt;/em&gt; (&lt;code&gt;CTRL+SHIFT+P&lt;/code&gt;) and selecting &lt;code&gt;&amp;gt; Python: Select Interpreter&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You should now either close the terminal pane in VS Code and open a new one or type &lt;code&gt;source .venv/bin/activate&lt;/code&gt; into the existing one to start using the virtual environment. Then, install the required packages for your project by typing &lt;code&gt;pip install &amp;lt;package_name&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;VS Code, by default, looks for tools like linters and code formatters in the current Python environment. If you don't want to keep installing them over and over again for each new virtual environment you make (unless your project requires a specific version of that tool), you can specify the path to their location under &lt;em&gt;Settings&lt;/em&gt; in VS Code.
- &lt;code&gt;flake8&lt;/code&gt; - &lt;code&gt;python.linting.flake8Path&lt;/code&gt;
- &lt;code&gt;autopep8&lt;/code&gt; - &lt;code&gt;python.formatting.autopep8Path&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;To find the global location of these packages on macOS and Linux, type &lt;code&gt;which flake8&lt;/code&gt; and &lt;code&gt;which autopep8&lt;/code&gt; in a terminal. If you are on Windows, you can use &lt;code&gt;where &amp;lt;command_name&amp;gt;&lt;/code&gt;. Both these commands assume that &lt;code&gt;flake8&lt;/code&gt; and &lt;code&gt;autopep8&lt;/code&gt; are in your PATH.&lt;/p&gt;
&lt;h2 id="understanding-workspaces-in-vs-code"&gt;Understanding Workspaces in VS Code&lt;/h2&gt;
&lt;p&gt;VS Code has a concept of &lt;em&gt;Workspaces&lt;/em&gt;. Each 'project folder' (or the root/top folder) is treated as a separate workspace. This allows you to have project-specific settings and enable/disable certain extensions for that workspace. It is also what allows VS Code to quickly recover the UI state (e.g. files that were previously kept open) when you open that workspace again.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  In VS Code, each workspace (or folder) has to be 'trusted' before certain features like linters, autocomplete suggestions and the in-built terminal are allowed to work.&lt;/p&gt;
&lt;p&gt;In the context of Python projects, if you tend to keep your virtual environments outside the workspace (where VS Code is unable to detect it), you can use this feature to set the default path to the Python interpreter for that workspace. To do that, first &lt;em&gt;Open a Folder&lt;/em&gt; (&lt;code&gt;CTRL+K CTRL+O&lt;/code&gt;) and then go to &lt;code&gt;File &amp;gt; Preferences &amp;gt; Settings &amp;gt; Workspace&lt;/code&gt; to modify &lt;code&gt;python.defaultInterpreterPath&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Setting the default interpreter path for the workspace." src="https://www.martinfitzpatrick.com/static/tutorials/developer/vscode/defaultinterpreterpath.png"  loading="lazy" width="1493" height="300"/&gt;
&lt;em&gt;Setting the default interpreter path for the workspace.&lt;/em&gt;&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  In VS Code settings you can search for settings by name using the bar at the top.&lt;/p&gt;
&lt;p&gt;You can also use this approach to do things like use a different linter for that workspace or disable the code formatter for it. The workspace-specific settings you change are saved in a &lt;code&gt;.vscode&lt;/code&gt; folder inside that workspace, which you can share with others.&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  If your VS Code is not recognizing libraries you are using in your code, double check the correct interpreter is being used. You can find which Python version you're using on the command line by running &lt;code&gt;which python&lt;/code&gt; or &lt;code&gt;which python3&lt;/code&gt; on macOS/Linux, or &lt;code&gt;where python&lt;/code&gt; or &lt;code&gt;where python3&lt;/code&gt; on Windows.&lt;/p&gt;
&lt;h2 id="working-with-git-in-vs-code-optional"&gt;Working With Git in VS Code (Optional)&lt;/h2&gt;
&lt;p&gt;Using &lt;em&gt;Version Control&lt;/em&gt; is required for developing applications. VS Code does have in-built support for Git but it is pretty barebones, not allowing much more than tracking changes that you have currently made and committing/pushing those changes once you are done.&lt;/p&gt;
&lt;p&gt;For the best experience, it is recommended to use the &lt;em&gt;GitLens&lt;/em&gt; extension. It lets you view your commit history, check who made the changes and much more. To set it up, you first need to have Git set up on your machine (&lt;a href="https://git-scm.com/"&gt;go here&lt;/a&gt;) and then install &lt;em&gt;GitLens&lt;/em&gt; from the &lt;em&gt;Extensions&lt;/em&gt; tab in the sidebar on the left. You can now use those Git-related features by going to the &lt;em&gt;Git&lt;/em&gt; tab in the sidebar (&lt;code&gt;CTRL+SHIFT+G&lt;/code&gt;).&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  There are more Git-related extensions you could try as well, like &lt;em&gt;Git History&lt;/em&gt; and &lt;em&gt;GitLab Workflow&lt;/em&gt;. Give them a whirl too!&lt;/p&gt;
&lt;h2 id="community-driven-open-source-alternatives"&gt;Community-driven &amp;amp; open source alternatives&lt;/h2&gt;
&lt;p&gt;While VS Code is open source (MIT-licensed), the distributed versions include some Microsoft-specific proprietary modifications, such as telemetry (app tracking). If you would like to avoid this, there is also a community-driven distribution of Visual Studio Code called &lt;a href="https://vscodium.com/"&gt;VSCodium&lt;/a&gt; that provides freely-licensed binaries without telemetry.&lt;/p&gt;
&lt;p&gt;Due to legal restrictions, VSCodium is unable to use the official Visual Studio Marketplace for extensions. Instead, it uses a separate vendor neutral, open source marketplace called &lt;a href="https://open-vsx.org/"&gt;Open VSX Registry&lt;/a&gt;. It doesn't have every extension, especially proprietary ones, and some are not kept up-to-date but both the &lt;em&gt;Python&lt;/em&gt; and &lt;em&gt;GitLens&lt;/em&gt; extensions are available on it.&lt;/p&gt;
&lt;p&gt;You can also use the open source &lt;em&gt;Jedi&lt;/em&gt; language server for the Python extension, rather than the bundled &lt;em&gt;Pylance&lt;/em&gt; language server/extension, by configuring the &lt;code&gt;python.languageServer&lt;/code&gt; setting. You can then completely disable Pylance by going to the &lt;em&gt;Extensions&lt;/em&gt; tab. Note that, if you are on VSCodium, Jedi is used by default (as Pylance is not available on Open VSX Registry) when you install the Python extension.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Having the right tools and making sure they're set up correctly will greatly simplify your development process. While Visual Studio starts as a simple tool, it is flexible and extendable
with plugins to suit your own preferred workflow. In this tutorial we've covered the basics of setting up your environment, and you should now be ready to start &lt;a href="https://www.pythonguis.com/topics/pyqt6-foundation/"&gt;developing your own applications with Python&lt;/a&gt;!&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.mfitzp.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="python"/></entry><entry><title>PyQt6, PySide6, PyQt5 and PySide2 Books -- updated for 2022! — New editions extended and updated, now 780+ pages</title><link href="https://www.martinfitzpatrick.com/pyqt6-pyqt5-books-updated-2022/" rel="alternate"/><published>2022-05-19T09:00:00+00:00</published><updated>2022-05-19T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2022-05-19:/pyqt6-pyqt5-books-updated-2022/</id><summary type="html">Hello! Today I have released new digital editions of my PyQt5, PyQt6, PySide2 and PySide6 book &lt;em&gt;Create GUI Applications with Python &amp;amp; Qt&lt;/em&gt;.</summary><content type="html">
            &lt;p&gt;Hello! Today I have released new digital editions of my PyQt5, PyQt6, PySide2 and PySide6 book &lt;em&gt;Create GUI Applications with Python &amp;amp; Qt&lt;/em&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/pyqt6-book/"&gt;PyQt6 Book 5th Edition, Create GUI Applications with Python &amp;amp; Qt6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/pyside6-book/"&gt;PySide6 Book, 5th Edition, Create GUI Applications with Python &amp;amp; Qt6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/pyqt5-book/"&gt;PyQt5 Book 5th Edition, Create GUI Applications with Python &amp;amp; Qt5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/pyside2-book/"&gt;PySide2 Book 5th Edition, Create GUI Applications with Python &amp;amp; Qt5&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This update adds over 200 pages of Qt examples and exercises - the book is now 780 pages long! - and
continues to be updated and extended. The latest additions include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Built-in dialogs, including &lt;code&gt;QMessageBox&lt;/code&gt; and &lt;code&gt;QFileDialog&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Working with multiple windows, cross-window communication&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;QThreadPool.start()&lt;/code&gt; to execute Python functions&lt;/li&gt;
&lt;li&gt;Long-running threads with &lt;code&gt;QThread&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Using custom widgets in Qt Designer&lt;/li&gt;
&lt;li&gt;Recurring &amp;amp; single shot timers&lt;/li&gt;
&lt;li&gt;Managing data files, working with paths&lt;/li&gt;
&lt;li&gt;Packaging with PyInstaller on Windows, macOS &amp;amp; Linux&lt;/li&gt;
&lt;li&gt;Creating distributable installers on Windows, macOS &amp;amp; Linux&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This update marks the 5th Edition of the book.&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  As always, if you've previously bought a copy of the book you &lt;strong&gt;get these updates for free!&lt;/strong&gt;
Just go to &lt;a href="https://account.pythonguis.com"&gt;the downloads page&lt;/a&gt; and enter the email you used for the purchase.
If you have problems getting this update just &lt;a href="mailto:martin@pythonguis.com"&gt;get in touch&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.mfitzp.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt"/><category term="qt6"/><category term="python"/></entry><entry><title>DiffCast: Hands-free Python Screencast Creator — Create reproducible programming screencasts without typos or edits</title><link href="https://www.martinfitzpatrick.com/diffcast-python-screencast-creator/" rel="alternate"/><published>2022-01-26T11:00:00+00:00</published><updated>2022-01-26T11:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2022-01-26:/diffcast-python-screencast-creator/</id><summary type="html">Programming screencasts are a popular way to teach programming and demo tools. Typically people will open up their favorite editor and record themselves tapping away. But this has a few problems. A good setup for coding isn't necessarily a good setup for video -- with text too small, a window too big, or too many distractions. Typing code in live means mistakes, which means more time editing or confusion for the people watching.</summary><content type="html">
            &lt;p&gt;Programming screencasts are a popular way to teach programming and demo tools. Typically people will open up their favorite editor and record themselves tapping away. But this has a few problems. A good setup for coding isn't necessarily a good setup for video -- with text too small, a window too big, or too many distractions. Typing code in live means mistakes, which means more time editing or confusion for the people watching.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DiffCast&lt;/strong&gt; eliminates that, automatically generating screencasts from Python source files you prepare ahead of time.&lt;/p&gt;
&lt;p&gt;&lt;img alt="DiffCast Logo" src="icon.png"/&gt;&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  DiffCast is written in Python with PyQt6. Source code available on &lt;a href="https://github.com/mfitzp/diffcast"&gt;Github&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Given the following Python source files:&lt;/p&gt;
&lt;div class="tabbed-area multicode"&gt;&lt;ul class="tabs"&gt;&lt;li class="tab-link current" data-tab="d35fdcb9d98f46f6a59a5eff5681589e" v-on:click="switch_tab"&gt;demo1.py&lt;/li&gt;
&lt;li class="tab-link" data-tab="7300c560088b445ba0dee8287a381913" v-on:click="switch_tab"&gt;demo2.py&lt;/li&gt;
&lt;li class="tab-link" data-tab="539021b54bb542ada6903a9847399d3d" v-on:click="switch_tab"&gt;demo3.py&lt;/li&gt;
&lt;li class="tab-link" data-tab="3fa3b1afbccc49c89409f76b67de062c" v-on:click="switch_tab"&gt;demo4.py&lt;/li&gt;&lt;/ul&gt;&lt;div class="tab-content current code-block-outer" id="d35fdcb9d98f46f6a59a5eff5681589e"&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;print('Hello, world!')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="7300c560088b445ba0dee8287a381913"&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;name = input('What is your name?\n')
print(f'Hello, {name}!')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="539021b54bb542ada6903a9847399d3d"&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;friends = ['Emelia', 'Jack', 'Bernardina', 'Jaap']

name = input('What is your name?\n')

if name in friends:
    print(f'Hello, {name}!')
else:
    print("I don't know you.")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="3fa3b1afbccc49c89409f76b67de062c"&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;friends = ['Emelia', 'Jack', 'Bernardina', 'Jaap']

while True:
    name = input('What is your name?\n')

    if name in friends:
        print(f'Hello, {name}!')
    else:
        print("I don't know you.")
        friends.append(name)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;DiffCast&lt;/strong&gt; will generate the following screencast (editor can be captured separately).&lt;/p&gt;
&lt;video controls="" src="https://user-images.githubusercontent.com/126239/151336683-4f0c423a-7bd5-4580-888b-4c08fdfdd4e9.mp4"&gt;&lt;/video&gt;
&lt;p&gt;The editor view is configured to be easily readable in video, without messing with your IDE settings. Edits happen at a regular speed, without mistakes, making them easy to follow. Each &lt;em&gt;diffcast&lt;/em&gt; is completely reproducible, with the same files producing the same output every time. You can set defined window sizes, or remove the titlebar to record.&lt;/p&gt;
&lt;p&gt;Designed for creating reproducible tutoring examples, library demos or demonstrating code to a class. You can also step forwards and backwards through each file manually, using the control panel.&lt;/p&gt;
&lt;p&gt;&lt;img alt="DiffCast user interface" src="diffcast-demo-windows.png"/&gt;&lt;/p&gt;
&lt;p&gt;Finally, you can write out edits to a another file, and show the file location in a file listing for context. Run the intermediate files to demonstrate the effect of the changes.&lt;/p&gt;
&lt;p&gt;&lt;img alt="DiffCast write out edits" src="diffcast-demo-editor-filelist.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DiffCast&lt;/strong&gt; is open source (GPL licensed) &amp;amp; free to use. For bug reports, feature requests &lt;a href="https://github.com/mfitzp/diffcast/"&gt;see the Github project&lt;/a&gt;.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.mfitzp.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="python"/><category term="mac"/></entry><entry><title>PySide6 tutorial now available — Complete course, updated for PySide2 &amp; PySide6</title><link href="https://www.martinfitzpatrick.com/pyside-tutorial-now-available/" rel="alternate"/><published>2021-05-14T07:00:00+00:00</published><updated>2021-05-14T07:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2021-05-14:/pyside-tutorial-now-available/</id><summary type="html">Hello! With the release of Qt6 versions of PyQt and PySide the course was getting a little crowded. So, today I've split the PySide tutorials into their own standalone &lt;a href="https://www.pythonguis.com/pyside2-tutorial/"&gt;PySide2 course&lt;/a&gt; and &lt;a href="https://www.pythonguis.com/pyside6-tutorial/"&gt;PySide6 course&lt;/a&gt;.</summary><content type="html">
            &lt;p&gt;Hello! With the release of Qt6 versions of PyQt and PySide the course was getting a little crowded. So, today I've split the PySide tutorials into their own standalone &lt;a href="https://www.pythonguis.com/pyside2-tutorial/"&gt;PySide2 course&lt;/a&gt; and &lt;a href="https://www.pythonguis.com/pyside6-tutorial/"&gt;PySide6 course&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The tutorials have all been updated for PySide2 &amp;amp; PySide6, with some additional improvements based on the latest editions of the book.&lt;/p&gt;
&lt;p&gt;This first update includes the following PySide tutorials --&lt;/p&gt;
&lt;h4&gt;Getting started creating Python GUIs with PySide&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-creating-your-first-window/"&gt;Creating your first app with PySide&lt;/a&gt;
A simple Hello World! application with Python and Qt.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-signals-slots-events/"&gt;Signals, Slots &amp;amp; Events&lt;/a&gt;
Triggering actions in response to user behaviors and GUI events.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-widgets/"&gt;Widgets&lt;/a&gt;
Using PySide's built-in widgets to build your applications.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-layouts/"&gt;Layout management&lt;/a&gt;
Use layouts to effortlessly position widgets within the window.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-actions-toolbars-menus/"&gt;Actions &amp;mdash; Toolbars &amp;amp; Menus&lt;/a&gt;
Defining toolbars, menus and keyboard shortcuts with QAction.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-dialogs/"&gt;Dialogs and Alerts&lt;/a&gt;
Notify your users and ask for their input.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Using Qt Designer with PySide&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-first-steps-qt-designer/"&gt;First steps with Qt Designer&lt;/a&gt;
Use Qt Designer's drag and drop interface to design your GUI.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-qt-designer-gui-layout/"&gt;Laying Out Your GUIs With Qt Designer&lt;/a&gt;
Use Qt Designer to effortlessly build your application UI.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-embed-pyqtgraph-custom-widgets/"&gt;Embedding custom widgets from Qt Designer&lt;/a&gt;
Learn how to use custom widgets in your PySide applications when designing with Qt Designer.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-creating-dialogs-qt-designer/"&gt;Creating Dialogs With Qt Designer&lt;/a&gt;
Using the drag and drop editor to build PySide dialogs.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-qresource-system/"&gt;The QResource System&lt;/a&gt;
Using the QResource system to package additional data with your applications.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Extended UI features in PySide&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-system-tray-mac-menu-bar-applications/"&gt;System tray &amp;amp; Mac menu bar applications&lt;/a&gt;
Add quick access functions to your apps.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-qscrollarea/"&gt;Add scrollable regions with QScrollArea&lt;/a&gt;
Run out of space in your GUI? Add a scrollable region to your application.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-widget-search-bar/"&gt;Creating searchable widget dashboards&lt;/a&gt;
Make dashboard UIs easier to use with widget search &amp;amp; text prediction.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-transmitting-extra-data-qt-signals/"&gt;Transmitting extra data with Qt Signals&lt;/a&gt;
Modifying widget signals to pass contextual information to slots.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-creating-multiple-windows/"&gt;Creating additional windows&lt;/a&gt;  Opening new windows for your application.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Multi threading PySide applications &amp;amp; QProcess&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/multithreading-pyside-applications-qthreadpool/"&gt;Multithreading PySide applications with QThreadPool&lt;/a&gt;
Run background tasks concurrently without impacting your UI.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-qprocess-external-programs/"&gt;Using QProcess to run external programs&lt;/a&gt;
Run background programs without impacting your UI.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Qt Model Views&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-modelview-architecture/"&gt;The ModelView Architecture&lt;/a&gt;
Qt's MVC-like interface for displaying data in views.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-qtableview-modelviews-numpy-pandas/"&gt;Displaying tabular data in ModelViews&lt;/a&gt;
Create customized table views with conditional formatting, numpy and pandas data sources.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Pyside plotting &amp;amp; graphics with Matplotlib/PyQtGraph&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-plotting-pyqtgraph/"&gt;Plotting with PyQtGraph&lt;/a&gt;
Create custom plots in PySide with PyQtGraph.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-plotting-matplotlib/"&gt;Plotting with Matplotlib&lt;/a&gt;
Create PySide plots with the popular Python plotting library.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Bitmap graphics and custom widgets&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-bitmap-graphics/"&gt;QPainter and Bitmap Graphics&lt;/a&gt;
Introduction to the core features of QPainter.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-creating-your-own-custom-widgets/"&gt;Creating custom GUI widgets in PySide&lt;/a&gt;
Build a completely functional custom widget from scratch using QPainter.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Packaging (PySide2 only)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/packaging-pyside-applications-windows-pyinstaller/"&gt;Packaging PySide2 applications for Windows, with PyInstaller&lt;/a&gt;
Turn your Qt5 application into a distributable installer for Windows.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyside-packaging-apps-fbs/"&gt;Packaging PySide apps with fbs&lt;/a&gt;
Distribute cross-platform GUI applications with the fman Build System.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's all for now!&lt;/p&gt;
&lt;p&gt;You can also still access the &lt;a href="https://www.pythonguis.com/pyqt5-tutorial/"&gt;PyQt5 tutorial&lt;/a&gt; and &lt;a href="https://www.pythonguis.com/pyqt6-tutorial/"&gt;PyQt6 tutorial&lt;/a&gt;.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.mfitzp.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyside"/><category term="qt6"/><category term="python"/><category term="qt"/></entry><entry><title>PyQt6 Book now available: Create GUI Applications with Python &amp; Qt6 — The hands-on guide to making apps with Python</title><link href="https://www.martinfitzpatrick.com/pyqt6-book-create-gui-applications-python-qt6/" rel="alternate"/><published>2021-04-09T15:00:00+00:00</published><updated>2021-04-09T15:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2021-04-09:/pyqt6-book-create-gui-applications-python-qt6/</id><summary type="html">Hello! Today I have released the first PyQt6 edition of my book &lt;strong&gt;&lt;a href="https://www.pythonguis.com/pyqt6-book/"&gt;Create GUI Applications, with Python &amp;amp; Qt6&lt;/a&gt;&lt;/strong&gt;.</summary><content type="html">
            &lt;p&gt;Hello! Today I have released the first PyQt6 edition of my book &lt;strong&gt;&lt;a href="https://www.pythonguis.com/pyqt6-book/"&gt;Create GUI Applications, with Python &amp;amp; Qt6&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This update follows the 4th Edition of the &lt;a href="https://www.pythonguis.com/pyqt5-book/"&gt;PyQt5 book&lt;/a&gt; updating all the code examples and adding additional PyQt6-specific detail.
The book contains &lt;em&gt;600+ pages&lt;/em&gt; and &lt;em&gt;200+ complete code examples&lt;/em&gt; taking you from the basics of creating PyQt applications to fully functional apps.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.pythonguis.com/pyqt6-book/"&gt;&lt;img alt="PySide6 book cover" src="/static/images/products/pyqt6-book.png"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt5 see my book, &lt;a href="https://www.mfitzp.com/pyqt5-book/"&gt;Create GUI Applications with Python &amp; Qt5.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt"/><category term="qt6"/><category term="python"/></entry><entry><title>PySide6 Book now available: Create GUI Applications with Python &amp; Qt6 — The hands-on guide to making apps with Python</title><link href="https://www.martinfitzpatrick.com/pyside6-book-create-gui-applications-python-qt6/" rel="alternate"/><published>2021-03-15T07:00:00+00:00</published><updated>2021-03-15T07:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2021-03-15:/pyside6-book-create-gui-applications-python-qt6/</id><summary type="html">Hello! This morning I released the first &lt;strong&gt;Qt6 edition of my PySide book &lt;a href="https://www.pythonguis.com/pyside6-book/"&gt;Create GUI Applications, with Python &amp;amp; Qt6&lt;/a&gt;&lt;/strong&gt;.</summary><content type="html">
            &lt;p&gt;Hello! This morning I released the first &lt;strong&gt;Qt6 edition of my PySide book &lt;a href="https://www.pythonguis.com/pyside6-book/"&gt;Create GUI Applications, with Python &amp;amp; Qt6&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This update follows the 4th Edition of the &lt;a href="https://www.pythonguis.com/pyside2-book/"&gt;PySide2 book&lt;/a&gt; updating all the code examples and adding additional PySide6-specific detail.
The book contains &lt;strong&gt;600+ pages&lt;/strong&gt; and &lt;strong&gt;200+ complete code examples&lt;/strong&gt; taking you from the basics of creating PySide applications to fully functional apps.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.pythonguis.com/pyside6-book/"&gt;&lt;img alt="PySide6 book cover" src="/static/images/products/pyside6-book.png"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you have any questions or difficulty getting hold of this update, just &lt;a href="mailto:martin@pythonguis.com"&gt;get in touch&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PySide6 see my book, &lt;a href="https://www.mfitzp.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyside"/><category term="qt6"/><category term="python"/><category term="qt"/><category term="pyqt"/></entry><entry><title>Using MicroPython and uploading libraries on Raspberry Pi Pico — Using rshell to upload custom code</title><link href="https://www.martinfitzpatrick.com/using-micropython-raspberry-pico/" rel="alternate"/><published>2021-02-22T08:00:00+00:00</published><updated>2021-02-22T08:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2021-02-22:/using-micropython-raspberry-pico/</id><summary type="html">MicroPython is an implementation of the Python 3 programming language, optimized to run microcontrollers. It's one of the options available for programming your Raspberry Pi Pico and a nice friendly way to get started with microcontrollers.</summary><content type="html">&lt;p&gt;MicroPython is an implementation of the Python 3 programming language, optimized to run microcontrollers. It's one of the options available for programming your Raspberry Pi Pico and a nice friendly way to get started with microcontrollers.&lt;/p&gt;
&lt;p&gt;MicroPython can be installed easily on your Pico, by following &lt;a href="https://www.raspberrypi.org/documentation/pico/getting-started/"&gt;the instructions on the Raspberry Pi website&lt;/a&gt; (click the &lt;em&gt;Getting Started with MicroPython&lt;/em&gt; tab and follow the instructions).&lt;/p&gt;
&lt;p&gt;After that point you might get a bit stuck. The &lt;a href="https://datasheets.raspberrypi.org/pico/raspberry-pi-pico-python-sdk.pdf"&gt;Pico documentation&lt;/a&gt; covers connecting to the Pico from a Pi, so if you're wanting to code from your own computer you'll need something else. One option is the &lt;a href="https://projects.raspberrypi.org/en/projects/getting-started-with-the-pico/2"&gt;Thonny IDE&lt;/a&gt; which you use to write and upload code to your Pico. It's got a nice friendly interface for working with Python.&lt;/p&gt;
&lt;p&gt;But if you don't want to change your IDE, or want a way to communicate with your Pico from the command line? You're in luck: there is a simple tool for accessing the MicroPython REPL on your Pico and uploading custom Python scripts or libraries you may wish to use: &lt;strong&gt;rshell&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this tutorial I'll take you through working with MicroPython in &lt;strong&gt;rshell&lt;/strong&gt;, coding live and uploading custom scripts to your Pico.&lt;/p&gt;
&lt;h2 id="installing-rshell"&gt;Installing rshell&lt;/h2&gt;
&lt;p&gt;Rshell itself is built on Python (not MicroPython) and can be installed and run locally on your main machine. You can install it like any other Python library.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-bash"&gt;bash&lt;/span&gt;
&lt;pre&gt;&lt;code class="bash"&gt;python -m pip install rshell
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Unfortunately, the current version of &lt;code&gt;rshell&lt;/code&gt; does not always play nicely with the Raspberry Pico. If you have problems you can install a fixed version in the pico branch from &lt;a href="https://github.com/dhylands/rshell/tree/pico"&gt;the rshell repository&lt;/a&gt;. You can install this directly from Github with the following --&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-bash"&gt;bash&lt;/span&gt;
&lt;pre&gt;&lt;code class="bash"&gt;python -m pip install https://github.com/dhylands/rshell/archive/pico.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This will download the latest version of the &lt;code&gt;pico&lt;/code&gt; branch (as a &lt;code&gt;.zip&lt;/code&gt;) and install this in your Python environment.&lt;/p&gt;
&lt;p&gt;Once installed, you will have access to a new command line tool &lt;code&gt;rshell&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="the-rshell-interface"&gt;The rshell interface&lt;/h2&gt;
&lt;p&gt;To use rshell from the command line, enter &lt;code&gt;rshell&lt;/code&gt; at your command prompt. You will see a welcome message and the prompt will turn green, to indicate you're in &lt;strong&gt;rshell&lt;/strong&gt; mode.&lt;/p&gt;
&lt;p&gt;&lt;img alt="rshell on Windows 10" src="https://www.martinfitzpatrick.com/using-micropython-raspberry-pico/rshell-windows.png"/&gt;
&lt;em&gt;The rshell interface on Windows 10&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="rshell on macOS" src="https://www.martinfitzpatrick.com/using-micropython-raspberry-pico/rshell-mac.png"/&gt;
&lt;em&gt;The rshell interface on macOS&lt;/em&gt;&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  If previous &lt;code&gt;pip install&lt;/code&gt; worked but the &lt;code&gt;rshell&lt;/code&gt; command &lt;em&gt;doesn't&lt;/em&gt; work, then you may have a problem with your Python paths.&lt;/p&gt;
&lt;p&gt;To see the commands available in rshell, enter &lt;code&gt;help&lt;/code&gt; and press Enter.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;help

Documented commands (type help &amp;lt;topic&amp;gt;):
========================================
args    cat  connect  date  edit  filesize  help  mkdir  rm     shell
boards  cd   cp       echo  exit  filetype  ls    repl   rsync

Use the exit command to exit rshell.
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  You can exit &lt;code&gt;rshell&lt;/code&gt; at any time by entering &lt;code&gt;exit&lt;/code&gt; or pressing &lt;code&gt;Ctrl-C&lt;/code&gt;. Once exited the prompt will turn white.&lt;/p&gt;
&lt;p&gt;The basic file operation commands are shown below.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cd &amp;lt;dirname&amp;gt;&lt;/code&gt; change directory&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cp &amp;lt;from&amp;gt; &amp;lt;to&amp;gt;&lt;/code&gt; copy a file&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ls&lt;/code&gt; list current directory&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rm &amp;lt;filename&amp;gt;&lt;/code&gt; remove (delete) a file&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filesize &amp;lt;filename&amp;gt;&lt;/code&gt; give the size of a file in bytes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you type &lt;code&gt;ls&lt;/code&gt; and press enter you will see a listing of your current folder &lt;em&gt;on your host computer&lt;/em&gt;. The same goes
for any of the other file operations, until we've connect a board and opened it's file storage -- so be careful!
We'll look at how to connect to a MicroPython board and work with the files on it next.&lt;/p&gt;
&lt;h2 id="connecting-to-your-pico-with-rshell"&gt;Connecting to your Pico with rshell&lt;/h2&gt;
&lt;p&gt;Enter &lt;code&gt;boards&lt;/code&gt; to see a list of MicroPython boards connected to your computer. If you don't
have any connected boards, you'll see the message &lt;code&gt;No boards connected&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If your board isn't connected&lt;/strong&gt; plug your Pico in now. You &lt;em&gt;can&lt;/em&gt; use the &lt;code&gt;connect&lt;/code&gt; command to connect to the board,
but for that you'll need to know which port it is on. Save yourself some effort and just restart &lt;strong&gt;rshell&lt;/strong&gt; to connect
automatically. To do this, type &lt;code&gt;exit&lt;/code&gt; and press Enter (or press &lt;code&gt;Ctrl-C&lt;/code&gt;) to exit and then restart &lt;strong&gt;rshell&lt;/strong&gt; by entering &lt;code&gt;rshell&lt;/code&gt; again at the prompt.&lt;/p&gt;
&lt;p&gt;If a board is connected when you start &lt;strong&gt;rshell&lt;/strong&gt; you will see something like the following...&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;C:\Users\Gebruiker&amp;gt;rshell
Connecting to COM4 (buffer-size 128)...
Trying to connect to REPL  connected
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Or an equivalent on macOS...&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;Martins-Mac: ~ mfitzp$ rshell
Connecting to /dev/cu.usbmodem0000000000001 (buffer-size 128)
Trying to connect to REPL  connected
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;...which shows you've connected to the MicroPython REPL on the Raspberry Pi Pico. Once connected the &lt;code&gt;boards&lt;/code&gt; command
will return some information about the connected board, like the following.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;pyboard @ COM4 connected Epoch: 1970 Dirs:
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The name on the left is the &lt;em&gt;type&lt;/em&gt; of board (Pico appears as &lt;code&gt;pyboard&lt;/code&gt;) and connected port (here COM4). The label at the end &lt;code&gt;Dirs:&lt;/code&gt; will list any files
on the Pico -- currently none.&lt;/p&gt;
&lt;h2 id="starting-a-repl"&gt;Starting a REPL&lt;/h2&gt;
&lt;p&gt;With the board connected, you can enter the Pico's REPL by entering the &lt;code&gt;repl&lt;/code&gt; command. This will return something like the following&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;repl
Entering REPL. Use Control-X to exit.
&amp;gt;
MicroPython v1.14 on 2021-02-14; Raspberry Pi Pico with RP2040
Type "help()" for more information.
&amp;gt;&amp;gt;&amp;gt;
&amp;gt;&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You are now writing Python on the Pico! Try entering &lt;code&gt;print("Hello!")&lt;/code&gt; at the REPL prompt.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;MicroPython v1.14 on 2021-02-14; Raspberry Pi Pico with RP2040
Type "help()" for more information.
&amp;gt;&amp;gt;&amp;gt;
&amp;gt;&amp;gt;&amp;gt; print("Hello!")
Hello!
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;As you can see, MicroPython works just like normal Python. If you enter &lt;code&gt;help()&lt;/code&gt; and press Enter,
you'll get some basic help information about MicroPython on the Pico. Helpfully, you also get a
small reference to how the pins on the Pico are numbered and the different ways you have to control them.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;Type "help()" for more information.
&amp;gt;&amp;gt;&amp;gt; help()
Welcome to MicroPython!

For online help please visit https://micropython.org/help/.

For access to the hardware use the 'machine' module.  RP2 specific commands
are in the 'rp2' module.

Quick overview of some objects:
  machine.Pin(pin) -- get a pin, eg machine.Pin(0)
  machine.Pin(pin, m, [p]) -- get a pin and configure it for IO mode m, pull mode p
    methods: init(..), value([v]), high(), low(), irq(handler)
  machine.ADC(pin) -- make an analog object from a pin
    methods: read_u16()
  machine.PWM(pin) -- make a PWM object from a pin
    methods: deinit(), freq([f]), duty_u16([d]), duty_ns([d])
  machine.I2C(id) -- create an I2C object (id=0,1)
    methods: readfrom(addr, buf, stop=True), writeto(addr, buf, stop=True)
             readfrom_mem(addr, memaddr, arg), writeto_mem(addr, memaddr, arg)
  machine.SPI(id, baudrate=1000000) -- create an SPI object (id=0,1)
    methods: read(nbytes, write=0x00), write(buf), write_readinto(wr_buf, rd_buf)
  machine.Timer(freq, callback) -- create a software timer object
    eg: machine.Timer(freq=1, callback=lambda t:print(t))

Pins are numbered 0-29, and 26-29 have ADC capabilities
Pin IO modes are: Pin.IN, Pin.OUT, Pin.ALT
Pin pull modes are: Pin.PULL_UP, Pin.PULL_DOWN

Useful control commands:
  CTRL-C -- interrupt a running program
  CTRL-D -- on a blank line, do a soft reset of the board
  CTRL-E -- on a blank line, enter paste mode

For further help on a specific object, type help(obj)
For a list of available modules, type help('modules')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  You can run &lt;code&gt;help()&lt;/code&gt; in the REPL any time you need a reminder.&lt;/p&gt;
&lt;p&gt;While we're here, lets flash the LED on the Pico board.&lt;/p&gt;
&lt;p&gt;Enter the following at the REPL prompt...&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from machine import Pin
led = Pin(25, Pin.OUT)

led.toggle()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Every time you call &lt;code&gt;led.toggle()&lt;/code&gt; the LED will toggle from ON to OFF or OFF to ON.&lt;/p&gt;
&lt;p class="admonition admonition-important"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-exclamation"&gt;&lt;/i&gt;&lt;/span&gt;  To exit the REPL at any time press &lt;code&gt;Ctrl-X&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="uploading-a-file"&gt;Uploading a file&lt;/h2&gt;
&lt;p&gt;MicroPython comes with a lot of built-in support for simple devices and communication protocols -- enough to build some quite fun things just by hacking in the REPL. But there are also a lot of libraries available for working with more complex hardware. To use these, you need to be able to upload them to your Pico! Once you can upload files, you can also edit your own code locally on your own computer and upload it from there.&lt;/p&gt;
&lt;p&gt;To keep things simple, lets create our own "library" that adjusts the brightness of the LED on the Pico board -- exciting I know. This library contains a single function &lt;code&gt;ledon&lt;/code&gt; which accepts a single parameter &lt;code&gt;brightness&lt;/code&gt; between 0 and 65535.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from machine import Pin, PWM

led = PWM(Pin(25))

def ledon(brightness=65535):
    led.duty_u16(brightness)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  Don't worry if you don't understand it, we'll cover how this works later. The important bit now is getting this on your Pico.&lt;/p&gt;
&lt;p&gt;Take the code above and save it in a file named &lt;code&gt;picoled.py&lt;/code&gt; on your main computer, in the same folder you're executing &lt;strong&gt;rshell&lt;/strong&gt; from. We'll upload this file to the Pico next.&lt;/p&gt;
&lt;!--
TIP: If you want to try with something more exciting, try [this max7219 library](https://github.com/mcauser/micropython-max7219/blob/master/max7219.py) which can be used to control 8x8 LED matrixes with the MAX7219 chipset.
--&gt;
&lt;p&gt;Start &lt;strong&gt;rshell&lt;/strong&gt; if you are not already in it -- look for the green prompt. Enter &lt;code&gt;boards&lt;/code&gt; at the prompt to get a list of connected boards.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-bash"&gt;bash&lt;/span&gt;
&lt;pre&gt;&lt;code class="bash"&gt;pyboard @ COM4 connected Epoch: 1970 Dirs:
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To see the directory contents of the &lt;code&gt;pyboard&lt;/code&gt; device, you can enter:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-bash"&gt;bash&lt;/span&gt;
&lt;pre&gt;&lt;code class="bash"&gt;ls /pyboard
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You &lt;em&gt;should&lt;/em&gt; see nothing listed. The path &lt;code&gt;/pyboard&lt;/code&gt; works like a &lt;em&gt;virtual folder&lt;/em&gt; meaning you can copy files to this
location to have the uploaded to your Pico. It is only available by a &lt;em&gt;pyboard&lt;/em&gt; is connected. To upload a file, we
copy it to this location. Enter the following at the prompt.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-bash"&gt;bash&lt;/span&gt;
&lt;pre&gt;&lt;code class="bash"&gt;cp picoled.py /pyboard/picoled.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;After you press Enter you'll see a message confirming the copy is taking place&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;C:\Users\Gebruiker&amp;gt; cp picoled.py /pyboard
Copying 'C:\Users\Gebruiker/picoled.py' to '/pyboard/picoled.py' ...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Once the copy is complete, run &lt;code&gt;boards&lt;/code&gt; again at the prompt and you'll see the file listed after the &lt;code&gt;Dirs:&lt;/code&gt; section, showing that it's on the board.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-bash"&gt;bash&lt;/span&gt;
&lt;pre&gt;&lt;code class="bash"&gt;C:\Users\Gebruiker&amp;gt; boards
pyboard @ COM4 connected Epoch: 1970 Dirs: /picoled.py /pyboard/picoled.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can also enter &lt;code&gt;ls /pyboard&lt;/code&gt; to see the listing directly.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-bash"&gt;bash&lt;/span&gt;
&lt;pre&gt;&lt;code class="bash"&gt;C:\Users\Gebruiker&amp;gt; ls /pyboard
picoled.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  If you ever need to upload multiple files, just repeat the upload steps until everything is where it needs to be. You can always drop in and out of the REPL to make sure things work.&lt;/p&gt;
&lt;h2 id="using-uploaded-libraries"&gt;Using uploaded libraries&lt;/h2&gt;
&lt;p&gt;Now we've uploaded our library, we can use it from the REPL. To get to the MicroPython REPL enter the &lt;code&gt;repl&lt;/code&gt; command in &lt;code&gt;rshell&lt;/code&gt; as before.
To use the library we uploaded, we can import it, just like any other Python library.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;MicroPython v1.14 on 2021-02-14; Raspberry Pi Pico with RP2040
Type "help()" for more information.
&amp;gt;&amp;gt;&amp;gt;
&amp;gt;&amp;gt;&amp;gt; import picoled
&amp;gt;&amp;gt;&amp;gt; picoled.ledon(65535)
&amp;gt;&amp;gt;&amp;gt; picoled.ledon(30000)
&amp;gt;&amp;gt;&amp;gt; picoled.ledon(20000)
&amp;gt;&amp;gt;&amp;gt; picoled.ledon(10000)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Or to pulse the brightness of the LED...&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; import picoled
&amp;gt;&amp;gt;&amp;gt; import time
&amp;gt;&amp;gt;&amp;gt; while True:
...     for a in range(0, 65536, 10000):
...         picoled.ledon(a)
...         time.sleep(0.1)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;video class="" controls="true" loop="" style="max-width: 100%; min-height: 546px;"&gt;&lt;source src="//i.imgur.com/vXiiuwM.mp4" type="video/mp4"/&gt;&lt;/video&gt;
&lt;h2 id="auto-running-python"&gt;Auto-running Python&lt;/h2&gt;
&lt;p&gt;So far we've been uploading code and running it manually, but once you start building projects you'll want your code
to run automatically.&lt;/p&gt;
&lt;p&gt;When it starts, MicroPython runs two scripts by default: &lt;code&gt;boot.py&lt;/code&gt; and &lt;code&gt;main.py&lt;/code&gt;, in that order. By uploading your own
script with the name &lt;code&gt;main.py&lt;/code&gt; it will run automatically every time the Raspberry Pico starts.&lt;/p&gt;
&lt;p&gt;Let's update our "library" to become an auto script that runs at startup. Save the following code to a script named &lt;code&gt;main.py&lt;/code&gt;.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from machine import Pin, PWM
from time import sleep

led = PWM(Pin(25))

def ledon(brightness=65535):
    led.duty_u16(brightness)


while True:
    for a in range(0, 65536, 10000):
        ledon(a)
        sleep(0.1)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In &lt;strong&gt;rshell&lt;/strong&gt; run the command to copy the file to &lt;code&gt;main.py&lt;/code&gt; on the board.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-bash"&gt;bash&lt;/span&gt;
&lt;pre&gt;&lt;code class="bash"&gt;cp main.py /pyboard/main.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p class="admonition admonition-important"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-exclamation"&gt;&lt;/i&gt;&lt;/span&gt;  Don't copy this file to &lt;code&gt;boot.py&lt;/code&gt; -- the loop will block the REPL startup and you won't be able to connect to your Pico to delete it again!
If you &lt;em&gt;do&lt;/em&gt; this, use the &lt;a href="https://www.raspberrypi.org/documentation/pico/getting-started/"&gt;Resetting Flash memory&lt;/a&gt; instructions to clear your Pico. You will need to re-install MicroPython afterwards.&lt;/p&gt;
&lt;p&gt;Once the &lt;code&gt;main.py&lt;/code&gt; file is uploaded, restart your Pico -- either unplug and re-plug it, or press &lt;code&gt;Ctrl-D&lt;/code&gt; in the REPL -- and the LED will start pulsing automatically.
The script will continue running until it finishes, or the Pico is reset. You can replace the &lt;code&gt;main.py&lt;/code&gt; script at any time to
change the behavior, or delete it with.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-bash"&gt;bash&lt;/span&gt;
&lt;pre&gt;&lt;code class="bash"&gt;rm /pyboard/main.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="whats-next"&gt;What's next?&lt;/h2&gt;
&lt;p&gt;Now you can upload libraries to your Pico you can get experimenting with the many MicroPython libraries that are available.&lt;/p&gt;
&lt;p&gt;If you're looking for some more things to do with MicroPython on your Pico, there are &lt;a href="https://github.com/raspberrypi/pico-micropython-examples"&gt;some MicroPython examples&lt;/a&gt; available from Raspberry Pi themselves, and also &lt;a href="http://docs.micropython.org/en/latest/index.html"&gt;the MicroPython documentation&lt;/a&gt; for language/API references.&lt;/p&gt;</content><category term="python"/><category term="micropython"/><category term="electronics"/></entry><entry><title>Writing Snake in 10 lines of SAM Coupé BASIC — Simple Snake game in 10 lines of BASIC</title><link href="https://www.martinfitzpatrick.com/writing-snake-in-10-lines-of-basic/" rel="alternate"/><published>2021-02-03T19:00:00+00:00</published><updated>2021-02-03T19:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2021-02-03:/writing-snake-in-10-lines-of-basic/</id><summary type="html">The &lt;a href="https://gkanold.server.deerpower.de/"&gt;Basic 10 Liner contest&lt;/a&gt; has been running for 11 years. This year (2021) the 10th competition,
and the first time I've heard of it, thanks to &lt;a href="http://www.breakintoprogram.co.uk/"&gt;Dean Belfield&lt;/a&gt;.</summary><content type="html">&lt;p&gt;The &lt;a href="https://gkanold.server.deerpower.de/"&gt;Basic 10 Liner contest&lt;/a&gt; has been running for 11 years. This year (2021) the 10th competition,
and the first time I've heard of it, thanks to &lt;a href="http://www.breakintoprogram.co.uk/"&gt;Dean Belfield&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The competition is simple: write a game in &lt;em&gt;some BASIC language&lt;/em&gt; in &lt;em&gt;10 lines of code&lt;/em&gt;.
Like the &lt;a href="/microbit-space-invaders/"&gt;arcade games for the micro:bit&lt;/a&gt; the fun comes from trying to squeeze as much of a game as possible out
of impossible limits.&lt;/p&gt;
&lt;p&gt;Since many BASIC dialects allow you to stack multiple statements onto the same line (SAM BASIC is no exception) there are
additional category limits on &lt;em&gt;characters per line&lt;/em&gt;, e.g.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Category "PUR-80": Program a game in 10 lines (max 80 characters per logical line, abbreviations are allowed).&lt;/li&gt;
&lt;li&gt;Category "PUR-120": Program a game in 10 lines (max 120 characters per logical line, abbreviations are allowed)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I aimed for the PUR-80 category and got there with Snake.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  This isn't a notable achievement, go &lt;a href="https://gkanold.wixsite.com/homeputerium/games-list-2020"&gt;see some of the great games from previous years&lt;/a&gt; like &lt;a href="http://plus4world.powweb.com/software/Feels_Like_A_City"&gt;Sim City&lt;/a&gt;! But for a first attempt, I'm pretty chuffed.&lt;/p&gt;
&lt;h2 id="playing"&gt;Playing&lt;/h2&gt;
&lt;p&gt;You control the Snake with the keys Q, A, O &amp;amp; P for up, down, left, right. Eating food gains you a point and extends your Snake length by 1.
If you chomp your own tail you die.&lt;/p&gt;
&lt;p&gt;@chipper:l@ I mean, it's Snake, what do you expect?&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  You can download a MGT disk image containing &lt;a href="https://download.martinfitzpatrick.com/basic10.mgt"&gt;10 line Snake&lt;/a&gt;.
To play, you can use the &lt;a href="http://www.simcoupe.org/"&gt;SimCoupe&lt;/a&gt; emulator. Insert disk image with File&amp;gt;Open. Load &amp;amp; Run with &lt;code&gt;LOAD "snake80" LINE 1&lt;/code&gt;. Press &lt;code&gt;ESC&lt;/code&gt; to break to source.&lt;/p&gt;
&lt;h2 id="readable-code"&gt;Readable code&lt;/h2&gt;
&lt;p&gt;The complete code is shown below with inline comments.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-basic"&gt;basic&lt;/span&gt;
&lt;pre&gt;&lt;code class="basic"&gt;# vars x, y: current coords
#      xs, ys: current x/y speed (always -1, 0 or 1)
#      l: length, starts at 1
#      fx, fy: x, y position of food
#      die: 1 if dead
LET x=10, y=10, xs=1, ys=0, l=1, fx=0, fy=0, die=0

# Pre-fill the tail memory with the starting location.
POKE &amp;amp;8000,x,y,x,y

# Setup the flashing effect for the food. Alternates color 11 between palette 50 (pink) &amp;amp; 127 (white).
# flashing every 5/50ths of a second.
PALETTE 11, 50, 127: POKE &amp;amp;5a08,5

DO
    # Move the head by current xs &amp;amp; ys.
    LET x=x+xs, y=y+ys

    # Wrap around on screen edges.
    IF x&amp;lt;0: LET x=31: ELSE IF x&amp;gt;31: LET x=0: END IF
    IF y&amp;lt;0: LET y=18: ELSE IF y&amp;gt;18: LET y=0: END IF

    # Draw the head. Only the head is drawn.
    PEN 12: PRINT AT PEEK &amp;amp;8000,  PEEK &amp;amp;8001;"&amp;block;":

    # Blank out the square behind the tail.
    PRINT AT PEEK (&amp;amp;8000+l*2),  PEEK (&amp;amp;8000+l*2+1);" "

    # If our head is at the same position as the food, eat it. Update score.
    # ZAP is a built-in SAM BASIC sound effect. fx is set to zero when no food.
    IF x=fx AND y=fy: ZAP : LET l=l+1: LET fx=0: PEN 15: PRINT AT 0,0; l: END IF

    # Move the tail memory down 2 bytes, making way for the new head position.
    POKE &amp;amp;8002, MEM$ (&amp;amp;8000 TO &amp;amp;8000+l*2)

    # Poke current head location to the top of the tail memory.
    # Poked as 2 bytes, y then x (cols by rows, because we're printing).
    POKE &amp;amp;8000, y, x

    # Collision check (using pixels) the position in front of the snake. If we
    # see ourselves, we're dead.
    IF POINT (x*8+4, (19-y)*9)=12: LET die=1: END IF

    # If fx is zero (no food) add some randomly on the map and print it.
    IF fx=0: LET fx= RND (25)+3: LET fy= RND (10)+4: PEN 11: PRINT AT fy,fx;"▟": END IF

    # Scan the keyboard for input &amp;amp; update directions.
    LET i$= INKEY$ :
        IF i$="q": LET xs=0, ys=-1:
        ELSE IF i$="a": LET xs=0, ys=1
        ELSE IF i$="o": LET xs=-1, ys=0:
        ELSE IF i$="p": LET xs=1, ys=0:
    END IF

    # Exit when die=1.
LOOP UNTIL die

# POW is a built-in SAM BASIC sound effect.
POW

# Restart the game.
RUN
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="how-it-works"&gt;How it works&lt;/h2&gt;
&lt;p&gt;Each tick the head of the Snake moves 1 block in whichever direction it is going, by incrementing the &lt;code&gt;x&lt;/code&gt; &amp;amp; &lt;code&gt;y&lt;/code&gt; variables.
We &lt;code&gt;PRINT&lt;/code&gt; the head to the screen on each move. Since things remain where they are printed, we don't need to re-print the
tail. But we &lt;em&gt;do&lt;/em&gt; need to keep track of where it is both for collisions and to clear up after the tail.&lt;/p&gt;
&lt;p&gt;To do this we &lt;code&gt;POKE&lt;/code&gt; our current x &amp;amp; y positions into memory using one byte for each for simplicity sakes. On each tick
we shift &lt;em&gt;2 x length&lt;/em&gt; bytes from &lt;code&gt;&amp;amp;8000&lt;/code&gt; to &lt;code&gt;&amp;amp;8002&lt;/code&gt; and poke the new y &amp;amp; x position into &lt;code&gt;&amp;amp;8000&lt;/code&gt; and &lt;code&gt;&amp;amp;8001&lt;/code&gt; respectively.
By reading memory at &lt;code&gt;&amp;amp;8000+2*l&lt;/code&gt; and &lt;code&gt;&amp;amp;8000+2*l+1&lt;/code&gt; we can get the &lt;em&gt;last&lt;/em&gt; segment of our current length tail and delete it
from the map by printing an empty space.&lt;/p&gt;
&lt;p&gt;&lt;img alt="PEEK POKE" src="https://www.martinfitzpatrick.com/static/invent/basic10/peekpoke.png"  loading="lazy" width="483" height="241"/&gt;
&lt;em&gt;Tail is POKEd to memory, shifted right 2 bytes on each tick &amp;amp; the current x &amp;amp; y coords are POKEd to the head. The value shifted off the end is used to clear up after the tail.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Food is placed randomly on the map and we compare x &amp;amp; y positions of the food, to that of the Snakes head to register an "eat".&lt;/p&gt;
&lt;p&gt;We're using two SAM-specific features here. First, is the use of the SAM palette flashing to flicker the food. By setting &lt;code&gt;PALETTE 11, 30, 127&lt;/code&gt;
we color palette 11 (which we are using for our food &lt;code&gt;PEN&lt;/code&gt;) to alternate between system color 30 &amp;amp; 27. The &lt;code&gt;POKE &amp;amp;5a08, 5&lt;/code&gt; sets
the rate of alternating to every 5/50ths of a second to highlight the food. Second, is the use of the SAM BASIC built-in sound effects &lt;code&gt;POW&lt;/code&gt; and &lt;code&gt;ZAP&lt;/code&gt;
which make pow and zap sounds, kind of. One negative is that doing this freezes execution of the BASIC program, so there is a small
freeze when picking up food. But it's not too bad.&lt;/p&gt;
&lt;h2 id="pur-80"&gt;PUR 80&lt;/h2&gt;
&lt;p&gt;To squeeze the game down to fit the 80 character line limit needed the following tricks.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The &amp;amp;8000 address was stored in a variable &lt;code&gt;o&lt;/code&gt; saving 4 chars each, or 2 for &lt;code&gt;o+1&lt;/code&gt;, &lt;code&gt;o+2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Statements were folded back into lines using &lt;code&gt;:&lt;/code&gt;, including breaking &lt;code&gt;IF&lt;/code&gt; and &lt;code&gt;ELSE&lt;/code&gt; blocks across lines. SAM BASIC doesn't care about line grouping.&lt;/li&gt;
&lt;li&gt;Re-order statements to fit, e.g. &lt;code&gt;PEN 12&lt;/code&gt; is moved to the &lt;code&gt;DO..&lt;/code&gt; line.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I thought I'd need to drop the palette cycling effects at first, but by jiggling statements around it came in under.
The final code is --&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-basic"&gt;basic&lt;/span&gt;
&lt;pre&gt;&lt;code class="basic"&gt;1LET x=10,y=10,xs=1,ys=0,l=1,fx=0,fy=0,o=&amp;amp;8000,d=0:PALETTE 11,50,127
2POKE o,x,y,x,y:POKE &amp;amp;5a08,5:DO:LET x=x+xs,y=y+ys:PEN 12:IF x&amp;lt;0:LET x=31
3ELSE IF x&amp;gt;31:LET x=0:END IF:IF y&amp;lt;0:LET y=18:ELSE IF y&amp;gt;18:LET y=0:END IF
4PRINT AT PEEK o,PEEK (o+1);"&amp;dcaron;":PRINT AT PEEK (o+l*2),PEEK (o+l*2+1);" "
5IF x=fx AND y=fy:ZAP:LET l=l+1:LET fx=0:PEN 15:PRINT AT 0,0; l:END IF
6POKE o+2,MEM$(o TO o+l*2):POKE o,y,x:IF POINT(x*8+4,(19-y)*9)=12:LET d=1
7END IF:IF fx=0:LET fx=RND(25)+3:LET fy=RND(10)+4:PEN 11
8PRINT AT fy,fx;"&amp;ccaron;":END IF:LET i$=INKEY$:IF i$="q":LET xs=0,ys=-1
9ELSE IF i$="a":LET xs=0,ys=1:ELSE IF i$="o":LET xs=-1,ys=0:ELSE IF i$="p"
10LET xs=1,ys=0:END IF:LOOP UNTIL d:POW:RUN
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;I wasn't originally packing the BASIC as compact as it can go, and this still isn't
as small as it can go: you can even do &lt;code&gt;FOR a=1to10&lt;/code&gt; for example. As a result the longest
line is line 9, with 74 chars.&lt;/p&gt;
&lt;p&gt;The game is available on an disk image &lt;a href="https://download.martinfitzpatrick.com/basic10.mgt"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="bugs"&gt;Bugs&lt;/h2&gt;
&lt;p&gt;When placing food there is no check to make sure it's not going placed in the tail. Since we only draw the food when first
placed, this means it disappears once the tail moves over (the end-tail blanking removes it). You have to try and remember where it is.&lt;/p&gt;
&lt;p&gt;If you're looking for a reference to SAM BASIC you can take a look at the original &lt;a href="https://sam.speccy.cz/basic/sam-coupe_use-guide.pdf"&gt;User Manual&lt;/a&gt; and
the &lt;a href="https://sam.speccy.cz/basic/sam-basic_complete_guide.pdf"&gt;Complete Guide to SAM Basic&lt;/a&gt; by Graham Burtenshaw.&lt;/p&gt;</content><category term="sam-coupe"/><category term="basic"/><category term="8bit"/><category term="games"/></entry><entry><title>Writing a SAM Coupé SCREEN$ Converter in Python — Interrupt optimizing image converter</title><link href="https://www.martinfitzpatrick.com/writing-a-sam-coupe-screen-converter-in-python/" rel="alternate"/><published>2021-01-28T14:00:00+00:00</published><updated>2021-01-28T14:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2021-01-28:/writing-a-sam-coupe-screen-converter-in-python/</id><summary type="html">The &lt;a href="/tag/sam-coupe"&gt;SAM Coup&amp;eacute;&lt;/a&gt; was a British 8 bit home computer that was pitched as a successor to the ZX Spectrum, featuring improved graphics and sound and higher processor speed.</summary><content type="html">&lt;p&gt;The &lt;a href="/tag/sam-coupe"&gt;SAM Coup&amp;eacute;&lt;/a&gt; was a British 8 bit home computer that was pitched as a successor to the ZX Spectrum, featuring improved graphics and sound and higher processor speed.&lt;/p&gt;
&lt;p&gt;The SAM Coup&amp;eacute;'s high-color MODE4 could manage 256x192 resolution graphics, with 16 colors from a choice of 128. Each pixel can be set individually, rather than using PEN/PAPER attributes as on the Spectrum. But there's more. The SAM also supports &lt;em&gt;line interrupts&lt;/em&gt; which allow palette entries to be changed on particular scan lines: a single palette entry can display multiple colors.&lt;/p&gt;
&lt;p&gt;The limitation that color can only be changed per &lt;em&gt;line&lt;/em&gt; means it's not really useful for games, or other moving graphics. But it does allow you to use a completely separate palette for "off screen" elements like panels. For static images, such as photos, it's more useful - assuming that the distribution of color in the image is favorable&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1" rel="footnote"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Demonstration SCREEN$ were a regular feature of SAM Coup&amp;eacute; disk magazines, but interrupts were rarely used since the placement had to be done manually.
Now we're living in the future, I wanted to have a crack at automating line interrupts to squeeze out as many colors as possible &amp;amp; let the SAM show off it's capabilities.&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  If you just want the converter, &lt;a href="https://github.com/mfitzp/scrimage"&gt;you can get it here&lt;/a&gt;. It is written in Python, using Pillow for image color conversion.&lt;/p&gt;
&lt;p&gt;First a quick look at the SAM Coup&amp;eacute; screen modes to see what we're dealing with.&lt;/p&gt;
&lt;h2 id="sam-coupe-screen-modes"&gt;Sam Coupe Screen Modes&lt;/h2&gt;
&lt;p&gt;There are 4 screen modes on the SAM Coup&amp;eacute;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MODE 1 is the ZX Spectrum compatible mode, with 8x8 blocks which can contain 2 colors PAPER (background) and PEN (foreground). The framebuffer in MODE 1 is non-linear, in that line 1 is followed by line 8.&lt;/li&gt;
&lt;li&gt;MODE 2 also uses attributes, with PAPER and PEN, but the cells are 8x1 pixels and the framebuffer is linear. This MODE wasn't used a great deal, but was the fastest mode on the SAM due to Spectrum-compatibility delays in MODE 1.&lt;/li&gt;
&lt;li&gt;MODE 3 is high resolution, with double the X pixels but only 4 colours -- making it good for &lt;a href="/article/samcoupe-reader"&gt;reading text&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;MODE 4 is the high color mode, with 256x192 and independent coloring of every pixel from a palette of 16. Most games/software used this mode.&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Dimensions&lt;/th&gt;
&lt;th&gt;Framebuffer&lt;/th&gt;
&lt;th&gt;bpp&lt;/th&gt;
&lt;th&gt;Colors&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;256&amp;times;192&lt;/td&gt;
&lt;td&gt;linear&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;24 KB&lt;/td&gt;
&lt;td&gt;High color&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;512&amp;times;192&lt;/td&gt;
&lt;td&gt;linear&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;24 KB&lt;/td&gt;
&lt;td&gt;High resolution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;256&amp;times;192&lt;/td&gt;
&lt;td&gt;linear&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;12 KB&lt;/td&gt;
&lt;td&gt;Color attributes for each 8x1 block&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;256&amp;times;192&lt;/td&gt;
&lt;td&gt;non-linear&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;6.75KB&lt;/td&gt;
&lt;td&gt;Color attributes for each 8&amp;times;8 block; matches ZX Spectrum&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Most SAM Coupe SCREEN$ were in MODE 4, so that's what we'll be targeting. It would be relatively easy to support MODE 3 on top of this&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2" rel="footnote"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id="the-screen-format"&gt;The SCREEN$ format&lt;/h2&gt;
&lt;p&gt;The format itself is fairly simple, consisting of the following bytes.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bytes&lt;/th&gt;
&lt;th&gt;Content&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;24576&lt;/td&gt;
&lt;td&gt;Pixel data, Mode 4 4bpp: 1 byte=2 pixels; Mode 3 2bpp: 1 byte = 4 pixels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Mode 4 Palette A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Mode 3 Palette A store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Mode 4 Palette B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Mode 3 Palette B store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Variable&lt;/td&gt;
&lt;td&gt;Line interrupts 4 bytes per interrupt (see below)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;FF termination byte&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In MODE 4 the pixel data is 4bbp, that is 1 byte = 2 pixels (16 possible colors). To handle this we can create our image as 16 colors and bit-shift the values before packing adjacent pixels into a single byte.&lt;/p&gt;
&lt;h3&gt;Palette A &amp;amp; B&lt;/h3&gt;
&lt;p&gt;As shown in the table above the SAM actually supports two simultaneous palettes (here marked A &amp;amp; B). These are full palettes which are alternated between, by default 3 times per second, to create flashing effects. The entire palette is switched, but you can opt to only change a single color. The rate of flashing is configurable with:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-basic"&gt;basic&lt;/span&gt;
&lt;pre&gt;&lt;code class="basic"&gt;POKE &amp;amp;5A08, &amp;lt;value&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;value&amp;gt;&lt;/code&gt; is the time between swaps of alternate palettes, in 50ths of a second. This is only
generally useful for creating flashing cursor effects &lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3" rel="footnote"&gt;3&lt;/a&gt;&lt;/sup&gt;. For converting &lt;em&gt;to&lt;/em&gt; SAM SCREEN$ we'll be ignoring this and just duplicating the palette.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  The exporter supports palette flash for GIF export.&lt;/p&gt;
&lt;h3&gt;MODE 3 Store&lt;/h3&gt;
&lt;p&gt;When switching between MODE 3 and MODE 4. The palettes of MODE 3 &amp;amp; 4 are separate, but palette operations on the same CLUT. When changing mode 4 colors are aside to a temporary store, and replaced when switching back. These values are also saved when saving SCREEN$ files (see "store" entries above), so you can replace the MODE 3 palette by loading a MODE 4 screen. It's a bit odd.&lt;/p&gt;
&lt;p&gt;We can ignore this for our conversions and just write a default set of bytes.&lt;/p&gt;
&lt;h3&gt;Interrupts&lt;/h3&gt;
&lt;p&gt;Interrupts define locations on the screen where a given palette entry (0-15) changes to a different color from the 128 color system palette. They are encoded with 4 bytes per interrupt, with multiple interrupts appended one after another.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bytes&lt;/th&gt;
&lt;th&gt;Content&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Y position, stored as 172-y (see below)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Color to change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Palette A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Palette B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Interrupt coordinates set from BASIC are calculated from -18 up to 172 at the top of the screen. The plot range in BASIC is actually 0..173, but interrupts can't affect the first pixel (which makes sense, since this is handled through the main palette).&lt;/p&gt;
&lt;p&gt;When stored in the file, line interrupts are stored as 172-y. For example, a line interrupt at 150 is stored in the file as 22. The line interrupt nearest the top of the screen (1st row down, interrupt position 173) would be stored as 172-172=0.&lt;/p&gt;
&lt;p&gt;This sounds complicated, but actually means that to get our interrupt Y byte we can just subtract 1 from the Y coordinate in the image.&lt;/p&gt;
&lt;h2 id="converting-image-to-screen"&gt;Converting Image to SCREEN$&lt;/h2&gt;
&lt;p&gt;We now have all the information we need to convert an image into a SCREEN$ format. The tricky bit (and what takes most of the work) is optimising the placement of the interrupts to maximise the number of colors in the image.&lt;/p&gt;
&lt;h3&gt;Pre-processing&lt;/h3&gt;
&lt;p&gt;Processing is done using Pillow package for Python. Input images are resized and cropped to fit, using using the &lt;code&gt;ImageOps.fit()&lt;/code&gt; method, with centering.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;SAM_COUPE_MODE4 = (256, 192, 16)
WIDTH, HEIGHT, MAX_COLORS = SAM_COUPE_MODE4

im = Image.open(fn)

# Resize with crop to fit.
im = ImageOps.fit(im, (WIDTH, HEIGHT), Image.ANTIALIAS, 0, (0.5, 0.5))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If the above crop is bad, you can adjust it by pre-cropping/sizing the image beforehand. There isn't the option to shrink without cropping as any border area would waste a palette entry to fill the blank space.&lt;/p&gt;
&lt;h3&gt;Interrupts&lt;/h3&gt;
&lt;p&gt;This is the bulk of the process for generating optimized images: the &lt;em&gt;optimize&lt;/em&gt; method is shown below -- this shows the high level steps taken to reach optimal number of colors using interrupts to compress colors.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def optimize(im, max_colors, total_n_colors):
    """
    Attempts to optimize the number of colors in the screen using interrupts. The
    result is a dictionary of color regions, keyed by color number
    """
    optimal_n_colors = max_colors
    optimal_color_regions = {}
    optimal_total_interrupts = 0

    for n_colors in range(max_colors, total_n_colors+1):
        # Identify color regions.
        color_regions = calculate_color_regions(im, n_colors)

        # Compress non-overlapping colors together.
        color_regions = compress_non_overlapping(color_regions)

        # Simplify our color regions.
        color_regions = simplify(color_regions)

        total_colors = len(color_regions)

        # Calculate home many interrupts we're using, length drop initial.
        _, interrupts = split_initial_colors(color_regions)
        total_interrupts = n_interrupts(interrupts)

        print("- trying %d colors, with interrupts uses %d colors &amp;amp; %d interrupts" % (n_colors, total_colors, total_interrupts))

        if total_colors &amp;lt;= max_colors and total_interrupts &amp;lt;= MAX_INTERRUPTS:
            optimal_n_colors = n_colors
            optimal_color_regions = color_regions
            optimal_total_interrupts = total_interrupts
            continue
        break

    print("Optimized to %d colors with %d interrupts (using %d palette slots)" % (optimal_n_colors, optimal_total_interrupts, len(optimal_color_regions)))
    return optimal_n_colors, optimal_color_regions
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The method accepts the image to compress, a &lt;code&gt;max_colors&lt;/code&gt; argument, which is the number of colors supported by the screen mode (16). This is a lower bound, the minimum number of colors we should be able to get in the image. The argument &lt;code&gt;total_n_colors&lt;/code&gt; contains the total number of colors in the image, capped at 128 -- the number of colors in the SAM palette. This is the upper bound, the maximum number of colors we can use. If the &lt;code&gt;total_n_colors&lt;/code&gt; &amp;lt; 16 we'll skip optimization.&lt;/p&gt;
&lt;p&gt;Each optimization round is as follows -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;calculate_color_regions&lt;/code&gt; generates a dictionary of color regions in the image. Each region is a &lt;code&gt;(start, end)&lt;/code&gt; tuple of y positions in the image where a particular color is found. Each color will usually have many blocks.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;compress_non_overlapping&lt;/code&gt; takes colors with few blocks and tries to combine them with other colors with no overlapping regions: transitions between colors will be handled by interrupts&lt;/li&gt;
&lt;li&gt;&lt;code&gt;simplify&lt;/code&gt; takes the resulting color regions and tries to simplify them further, grouping blocks back with their own colors if they can and then combining adjacent blocks&lt;/li&gt;
&lt;li&gt;&lt;code&gt;total_colors&lt;/code&gt; the length of the &lt;code&gt;color_regions&lt;/code&gt; is now the number of colors used&lt;/li&gt;
&lt;li&gt;&lt;code&gt;split_initial_colors&lt;/code&gt; removes the first block, to get total number of interrupts&lt;/li&gt;
&lt;/ul&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  The &lt;code&gt;compress_non_overlapping&lt;/code&gt; algorithm makes no effort to find the &lt;em&gt;best&lt;/em&gt; compression of regions - I experimented with this a bit and it just explodes the number of interrupts for little real gain in image quality.&lt;/p&gt;
&lt;p&gt;The optimization process is brute force - step forward, increase the number of colors by 1 and perform the optimization steps above. If the number of colors &amp;gt; 16 we've gone too far: we return the last successful result, with colors &amp;lt;= 16.&lt;/p&gt;
&lt;h3&gt;SAM Coup&amp;eacute; Palette&lt;/h3&gt;
&lt;p&gt;Once we have the colors for the image we map the image over to the SAM Coup&amp;eacute; palette. Every pixel in the image must have a value between 0-15 -- pixels for colors controlled by interrupts are mapped to their "parent" color. Finally, all the colors are mapped across from their RGB values to the nearest SAM palette number equivalent.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  This is sub-optimal, since the choice of colors should really be informed by the colors available. But I couldn't find a way to get Pillow to quantize to a fixed palette without dithering.&lt;/p&gt;
&lt;p&gt;The mapping is done by calculating the distance in RGB space for each color to each color in the SAM 128 color palette, using the usual RGB color distance algorithm.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def convert_image_to_sam_palette(image, colors=16):
    new_palette = []
    rgb = image.getpalette()[:colors*3]
    for r, g, b in zip(rgb[::3], rgb[1::3], rgb[2::3]):

        def distance_to_color(o):
            return distance(o, (r, g, b))

        spalette = sorted(SAM_PALETTE, key=distance_to_color)
        new_palette.append(spalette[0])

    palette = [c for i in new_palette for c in i]
    image.putpalette(palette)
    return image
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Packing bits&lt;/h3&gt;
&lt;p&gt;Now our image contains pixels of values 0-15 we can pack the bits and export the data. we can iterate through the flattened data in steps of 2, and pack into a single byte:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;pixels = np.array(image16)

image_data = []
pixel_data = pixels.flatten()
# Generate bytestream and palette; pack to 2 pixels/byte.
for a, b in zip(pixel_data[::2], pixel_data[1::2]):
    byte = (a &amp;lt;&amp;lt; 4) | b
    image_data.append(byte)

image_data = bytearray(image_data)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The operation &lt;code&gt;a &amp;lt;&amp;lt; 4&lt;/code&gt; shifts the bits of integer &lt;code&gt;a&lt;/code&gt; left by 4, so 15 (00001111) becomes 240 (11110000), while &lt;code&gt;|&lt;/code&gt; ORs the result with &lt;code&gt;b&lt;/code&gt;. If &lt;code&gt;a = 0100&lt;/code&gt; and &lt;code&gt;b = 0011&lt;/code&gt; the result would be &lt;code&gt;01000011&lt;/code&gt; with both values packed into a single byte.&lt;/p&gt;
&lt;h3&gt;Writing the SCREEN$&lt;/h3&gt;
&lt;p&gt;Finally, the image data is written out, along with the palette data and line interrupts.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;        # Additional 4 bytes 0, 17, 34, 127; mode 3 temporary store.
        bytes4 = b'\x00\x11\x22\x7F'

        with open(outfile, 'wb') as f:
            f.write(image_data)
            # Write palette.
            f.write(palette)

            # Write extra bytes (4 bytes, 2nd palette, 4 bytes)
            f.write(bytes4)
            f.write(palette)
            f.write(bytes4)

            # Write line interrupts
            f.write(interrupts)

            # Write final byte.
            f.write(b'\xff')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To actually view the result, I recommend the &lt;a href="http://www.speccy.pl/archive/prod.php?id=377"&gt;SAM Coup&amp;eacute; Advanced Disk Manager&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can see the source code for the &lt;code&gt;img2sam&lt;/code&gt; converter &lt;a href="https://github.com/mfitzp/scrimage"&gt;on Github&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Examples&lt;/h3&gt;
&lt;p&gt;Below are some example images, converted from PNG/JPG source images to SAM Coup&amp;eacute; MODE 4 SCREEN$ and
then &lt;em&gt;back&lt;/em&gt; into PNGs for display. The palette of each image is restricted to the SAM Coup&amp;eacute;'s 128
colors and colors are modified using interrupts.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Pool" src="https://www.martinfitzpatrick.com/static/tools/sam-coupe-image/pool-noi.png"  loading="lazy" width="256" height="192"/&gt;
&lt;em&gt;Pool 16 colors, no interrupts&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Pool" src="https://www.martinfitzpatrick.com/static/tools/sam-coupe-image/pool.png"  loading="lazy" width="256" height="192"/&gt;
&lt;em&gt;Pool 24 colors, 12 interrupts (compare gradients)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This image pair shows the effect on line interrupts on a image without dither. The separation between the differently colored pool balls makes this a good candidate.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Leia" src="https://www.martinfitzpatrick.com/static/tools/sam-coupe-image/leia.png"  loading="lazy" width="256" height="192"/&gt;
&lt;em&gt;Leia 26 colors, 15 interrupts&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Tully" src="https://www.martinfitzpatrick.com/static/tools/sam-coupe-image/tully.png"  loading="lazy" width="256" height="192"/&gt;
&lt;em&gt;Tully 22 colors, 15 interrupts&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The separation between the helmet (blue, yellow components) and horizontal line in the background
make this work out nicely. Same for the second image of Tully below.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Isla" src="https://www.martinfitzpatrick.com/static/tools/sam-coupe-image/isla.png"  loading="lazy" width="256" height="192"/&gt;
&lt;em&gt;Isla 18 colors, 6 interrupts&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Tully (2)" src="https://www.martinfitzpatrick.com/static/tools/sam-coupe-image/tully2.png"  loading="lazy" width="256" height="192"/&gt;
&lt;em&gt;Tully (2) 18 colors, 5 interrupts&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Dana" src="https://www.martinfitzpatrick.com/static/tools/sam-coupe-image/dana-d.png"  loading="lazy" width="256" height="192"/&gt;
&lt;em&gt;Dana 17 colors, 2 interrupts&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Lots of images that don't compress well because the same shades are used throughout the image. This is made worse by the conversion to the SAM's limited palette of 128.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Interstellar" src="https://www.martinfitzpatrick.com/static/tools/sam-coupe-image/interstellar.png"  loading="lazy" width="256" height="192"/&gt;
&lt;em&gt;Interstellar 17 colors, 3 interrupts&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Blade Runner" src="https://www.martinfitzpatrick.com/static/tools/sam-coupe-image/bladerunner2.png"  loading="lazy" width="256" height="192"/&gt;
&lt;em&gt;Blade Runner 16 colors (11 used), 18 interrupts&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This last image doesn't manage to squeeze more than 16 colors out of the image,
but &lt;em&gt;does&lt;/em&gt; reduce the number of colors used for those 16 to just 11.
This gives you 5 spare colors to add something else to the image.&lt;/p&gt;
&lt;h2 id="converting-screen-to-image"&gt;Converting SCREEN$ to Image&lt;/h2&gt;
&lt;p&gt;Included in the &lt;em&gt;scrimage&lt;/em&gt; package is the &lt;code&gt;sam2img&lt;/code&gt; converter, which will take a SAM MODE4 SCREEN$ and convert it to an image. The conversion process respects interrupts and when exporting to GIF will export flashing palettes as animations.&lt;/p&gt;
&lt;p&gt;The images above were all created using &lt;code&gt;sam2img&lt;/code&gt; on SCREEN$ created with &lt;code&gt;img2sam&lt;/code&gt;. The following two GIFs are examples of export from SAM Coupe SCREEN$ with flashing palettes.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Flashing palette" src="https://www.martinfitzpatrick.com/static/tools/sam-coupe-image/flash.gif"/&gt;
&lt;em&gt;Flashing palette&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Flashing palette" src="https://www.martinfitzpatrick.com/static/tools/sam-coupe-image/flashi.gif"/&gt;
&lt;em&gt;Flashing palette and flashing Line interrupts&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You can see the source code for the &lt;code&gt;sam2img&lt;/code&gt; converter &lt;a href="https://github.com/mfitzp/scrimage"&gt;on Github&lt;/a&gt;.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;An ideal image either has gradients down the image, or regions of isolated non-overlapping color. But it's hard to predict as conversion to the SAM palette can run some colors together.&amp;nbsp;&lt;a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text"&gt;&amp;larrhk;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;I experimented a bit with converting &lt;em&gt;to&lt;/em&gt; MODE 3, but only 4 colors meant not very exciting results.&amp;nbsp;&lt;a class="footnote-backref" href="#fnref:2" rev="footnote" title="Jump back to footnote 2 in the text"&gt;&amp;larrhk;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;With faster flash speeds (1 50th/second) you &lt;em&gt;can&lt;/em&gt; use it to sort of merge nearby colors to create additional shades, while giving yourself a headache.&amp;nbsp;&lt;a class="footnote-backref" href="#fnref:3" rev="footnote" title="Jump back to footnote 3 in the text"&gt;&amp;larrhk;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="sam-coupe"/><category term="python"/></entry><entry><title>Preserving the FRED disk magazine's text by decoding the Entropy Reader — Writing a SAM Coupé Reader parser in Python</title><link href="https://www.martinfitzpatrick.com/preserving-the-fred-disk-magazine-by-decoding-the-entropy-reader/" rel="alternate"/><published>2021-01-22T14:00:00+00:00</published><updated>2021-01-22T14:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2021-01-22:/preserving-the-fred-disk-magazine-by-decoding-the-entropy-reader/</id><summary type="html">FRED was the most popular disk magazine for the &lt;a href="/tag/sam-coupe"&gt;SAM Coup&amp;eacute;&lt;/a&gt; 8 bit home computer.Published by &lt;a href="https://www.worldofsam.org/people/colin-macdonald"&gt;Colin MacDonald&lt;/a&gt; out of sunny Monifieth, Scotland, the magazine ran from it's first issue in 1990 through to it's last (82) in 1998.</summary><content type="html">&lt;p&gt;FRED was the most popular disk magazine for the &lt;a href="/tag/sam-coupe"&gt;SAM Coup&amp;eacute;&lt;/a&gt; 8 bit home computer.Published by &lt;a href="https://www.worldofsam.org/people/colin-macdonald"&gt;Colin MacDonald&lt;/a&gt; out of sunny Monifieth, Scotland, the magazine ran from it's first issue in 1990 through to it's last (82) in 1998.&lt;/p&gt;
&lt;p&gt;For the SAM networking project I was hoping there might be some information on how the network worked in one of the issues -- maybe someone had done the groundwork for me. Disk images for all issues are available on &lt;a href="https://www.worldofsam.org/products/fred"&gt;World of Sam&lt;/a&gt; and can be loaded up in the emulator. But that made it hard to find anything specific -- you'd have to load up each disk, page through the magazine hoping not to nod off before you found what you're looking for. It would be easier if I could say search for "network" and find the issues where it was mentioned.&lt;/p&gt;
&lt;p&gt;So I set about extracting the text files from the magazine -- which in this case meant re-implementing the decoder. If you find that kind of thing interesting, read on!&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  The extracted magazine text files (with addresses, etc. redacted) are now available on &lt;a href="https://www.worldofsam.org/products/fred"&gt;World of Sam&lt;/a&gt;!&lt;/p&gt;
&lt;h2 id="fred-magazine"&gt;FRED magazine&lt;/h2&gt;
&lt;p&gt;Each issue of FRED (from 4 onwards) had at least 2 text slots on the disk -- the editorial and the letters/reviews. These were viewable within readers on the disk, reachable from the main menu in slots A &amp;amp; B.&lt;/p&gt;
&lt;p&gt;&lt;img alt="FRED28 Menu Screen" src="https://www.martinfitzpatrick.com/static/tools/sam-coupe-reader/FRED28menu.png"  loading="lazy" width="576" height="480"/&gt;
&lt;em&gt;&lt;strong&gt;FRED 28 Main Menu&lt;/strong&gt; Christmas '92 - the first issue of FRED I got with my Sam&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Early issues of the magazine are in plain text (SAM Coup&amp;eacute; ASCII) stored as simple data files with all pages in consecutive order.
The following snippet is from &lt;em&gt;FRED 16&lt;/em&gt; the last issue with the &lt;em&gt;Axe&lt;/em&gt; plain text reader.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-text"&gt;text&lt;/span&gt;
&lt;pre&gt;&lt;code class="text"&gt;                          Freditorial

  Greetings one and all from bonny Scotland (Och Aye The noo!)
  A free FRED to the first person who can tell me what it
means!!  Coming  up  to  Xmas  you'd expect loadsa new SAM games
wouldn't  you.  Unfortunately, the only one I think MIGHT be out
in  time  for  Xmas is Prince Of Persia! Although I did say last
month  that  it  had  been  released it seems there was a slight
miscalculation at Revelation (mmmm..that makes a change).
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Each line is wrapped at 64 characters, with line-ends padded out to the full 64 characters. Blank lines are not empty and contain 64 space characters, including when they're at the end of the page. This is obviously pretty wasteful for memory/disk space, but has the advantage that each page takes up precisely the same amount of space, making paging forwards and backwards very easy. Displaying the text on the screen is simply a case of starting reading at &lt;code&gt;page * page_size&lt;/code&gt; and dumping the correct amount of chars to screen.&lt;/p&gt;
&lt;p&gt;Each page of text is 21 lines long, leaving a bit of space for controls (the heading is part of the text). That means each page is 21 * 64 = 1344 bytes long. A magazine 20 pages long would take 26,880 bytes or 26.25 KB. As the magazine grew, particularly the letters &amp;amp; reviews pages, this would take up an increasing % of the limited disk space (780 KB per issue), leaving less space for software. The solution was the new compressing Entropy Reader by &lt;a href="http://simoncooke.com/"&gt;Simon Cooke&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="entropy-reader"&gt;Entropy Reader&lt;/h2&gt;
&lt;p&gt;The Entropy Reader was used on FRED issues 17 (1992) onwards, right up to the final issue -- although it was tweaked a few times in between. Magazine text was compressed down to data files on disk by a compressor and then decompressed back to memory by the reader.&lt;/p&gt;
&lt;p&gt;Starting this project I had no idea of the compression scheme. There are two simply ways to compress data -- tokenization and run-length encoding. The first uses a substitution table, where you can replace a entire word, or parts of words, with a control code. This relies on either calculating or knowing the most common words in the text. The second substitutes runs of identical characters with another. This isn't usually very effective in text because the occurrence of repeated letters is pretty low&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1" rel="footnote"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="FRED17 magazine" src="https://www.martinfitzpatrick.com/static/tools/sam-coupe-reader/FRED17reader.png"  loading="lazy" width="576" height="480"/&gt;
&lt;em&gt;&lt;strong&gt;FRED 17 Magazine&lt;/strong&gt; the first issue with the Entropy Reader&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The magazine is stored in &lt;code&gt;.mag&lt;/code&gt; files, separate from the reader program. There reader program was included on disk and didn't change between issues, although there were 3 variants through the lifetime of FRED magazine. Opening the data files in a hex editor gives the following garbled plaintext.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-text"&gt;text&lt;/span&gt;
&lt;pre&gt;&lt;code class="text"&gt;FRED man&amp;Acirc;&amp;scaron;m&amp;Abreve;.t accept no&amp;Acirc;.Pontardaw&amp;Abreve;&amp;scaron;&amp;Acirc;.&amp;Abreve;sponsibilf&amp;Abreve;&amp;Abreve;injuries&amp;Acirc;.Swans
&amp;Abreve;&amp;ogon;&amp;Acirc;incu&amp;Abreve;&amp;sacute;&amp;Abreve;&amp;breve; try&amp;Acirc;&amp;deg; &amp;Abreve;&amp;Abreve;&amp;Acirc;&amp;sacute;no&amp;Abreve;c&amp;Abreve;&amp;scaron;&amp;Acirc;Wales&amp;Acirc;.&amp;Abreve;is line.&amp;Abreve;&amp;Acirc;UAnybod&amp;Abreve;&amp;scedil;&amp;Abreve;mem&amp;Abreve;
r a l&amp;Acirc;&amp;cedil;abl&amp;Abreve;&amp;scaron;l&amp;Abreve;&amp;lcaron;tl&amp;Abreve;&amp;scaron;gam&amp;Abreve;&amp;scaron;on &amp;Acirc;old S&amp;Abreve;&amp;Scedil;cc&amp;Abreve;&amp;scedil;call&amp;Abreve;&amp;breve;Bould&amp;Abreve;da&amp;Abreve;&amp;Abreve; If so&amp;Abreve;you&amp;Acirc;&amp;sect;su&amp;Abreve;&amp;Abreve;
 &amp;Abreve; pl&amp;Abreve;&amp;ogon;s&amp;Abreve;&amp;breve; &amp;Abreve; h&amp;Abreve;&amp;ogon;r &amp;Abreve;&amp;Abreve;&amp;caron; a SAMgamesimilar&amp;Abreve;&amp;Abreve;&amp;Abreve;&amp;Abreve;is ty&amp;Abreve;&amp;Scedil; is j&amp;Abreve; t about &amp;Abreve;ady
&amp;Abreve;&amp;Acirc;&amp;Zcaron;&amp;Acirc;mom&amp;Abreve;.t &amp;Abreve;&amp;lcaron;goes&amp;Abreve;b&amp;Abreve;&amp;scedil; &amp;Acirc;nam&amp;Abreve;&amp;scaron;of "Wop Gamma"&amp;Abreve;&amp;Abreve;is is j&amp;Abreve;t a w&amp;Abreve;k&amp;Acirc;&amp;deg; t&amp;Abreve;&amp;lcaron;le,a
&amp;Abreve;&amp;Zdot;a&amp;Abreve;nt&amp;Abreve;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In amongst the nonsense control codes there are clearly readable parts of words. The name of a game "Wop Gamma" is readable in it's entirety, as is the country "Wales", but the city Swansea appears as &lt;code&gt;Swans&amp;Abreve;&amp;ogon;&amp;Acirc;&lt;/code&gt; and consists of the following bytes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;s w a n s (242) (128) (22)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Skipping through the file we can see other similar substitutions e.g.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Simon Cooke -&amp;gt; S i m o n C (227) k e
subscribers -&amp;gt; s u b s c r i (217) r s
I've finally started getting -&amp;gt; I'v (249) f i n a l (209) s t a r t (226) g e t t (176)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;From this we can start building a substitution table&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dec&lt;/th&gt;
&lt;th&gt;Hex&lt;/th&gt;
&lt;th&gt;Substitution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;176&lt;/td&gt;
&lt;td&gt;B0&lt;/td&gt;
&lt;td&gt;ing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;177&lt;/td&gt;
&lt;td&gt;B1&lt;/td&gt;
&lt;td&gt;een&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;195&lt;/td&gt;
&lt;td&gt;C3&lt;/td&gt;
&lt;td&gt;er&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;208&lt;/td&gt;
&lt;td&gt;D0&lt;/td&gt;
&lt;td&gt;ly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;209&lt;/td&gt;
&lt;td&gt;D1&lt;/td&gt;
&lt;td&gt;th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;217&lt;/td&gt;
&lt;td&gt;D9&lt;/td&gt;
&lt;td&gt;be&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;221&lt;/td&gt;
&lt;td&gt;DD&lt;/td&gt;
&lt;td&gt;re\s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;222&lt;/td&gt;
&lt;td&gt;DE&lt;/td&gt;
&lt;td&gt;en&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;226&lt;/td&gt;
&lt;td&gt;E2&lt;/td&gt;
&lt;td&gt;ed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;227&lt;/td&gt;
&lt;td&gt;E3&lt;/td&gt;
&lt;td&gt;oo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;241&lt;/td&gt;
&lt;td&gt;F1&lt;/td&gt;
&lt;td&gt;d\sd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;249&lt;/td&gt;
&lt;td&gt;F8&lt;/td&gt;
&lt;td&gt;e\s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;251&lt;/td&gt;
&lt;td&gt;FB&lt;/td&gt;
&lt;td&gt;ic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;It doesn't look like there is any particular pattern here, but maybe something will come out.&lt;/p&gt;
&lt;h2 id="the-reader-program"&gt;The reader program&lt;/h2&gt;
&lt;p&gt;We could carry on like this manually, building the table. But there are two problems -- (1) it's boring, (2) if the substitutions vary between issues, we'll have to do that again.&lt;/p&gt;
&lt;p&gt;Thankfully, we don't need to do that. The FRED disks contain the reader program &lt;code&gt;DOCREADER&lt;/code&gt;. As well as providing the reader interface, this program is responsible for decompressing the data back to plain text in memory -- meaning the code &lt;em&gt;must&lt;/em&gt; contain the substitution table somewhere.&lt;/p&gt;
&lt;p&gt;Opening the &lt;code&gt;DOCREADER&lt;/code&gt; program in a hex editor, we can see a block that looks interesting.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-text"&gt;text&lt;/span&gt;
&lt;pre&gt;&lt;code class="text"&gt;address&amp;Acirc;screens&amp;Acirc;screen&amp;Acirc; issue&amp;Acirc;memory&amp;Acirc;screen&amp;Acirc; don
't&amp;Acirc; SAMCO&amp;Acirc; SAMCo&amp;Acirc;Coupe&amp;Acirc; FRED&amp;Acirc;bytes&amp;Acirc; data&amp;Acirc; it's&amp;Acirc; from&amp;Acirc; SAM&amp;Acirc; 19&amp;Acirc;&amp;scaron;code&amp;Acirc;
Code&amp;Acirc;Data&amp;Acirc;ould&amp;Acirc; out&amp;Acirc; had&amp;Acirc;Coup&amp;Abreve;&amp;Lcaron;SAMC&amp;Abreve;SAMC&amp;Abreve;&amp;Zdot;The&amp;Acirc;the&amp;Acirc;tio&amp;Abreve;&amp;Zcaron; at&amp;Acirc;emp&amp;Abreve;&amp;acute;19&amp;Acirc;&amp;scaron;c
om&amp;Abreve;&amp;deg;Com&amp;Abreve;&amp;deg;con&amp;Abreve;&amp;lstrok;Con&amp;Abreve;&amp;lstrok;.&amp;Acirc; yo&amp;Abreve;&amp;lcaron;'ll&amp;Acirc;ere&amp;Acirc;You&amp;Acirc; it&amp;Acirc;.)&amp;Acirc;n'&amp;Abreve;&amp;acute;it&amp;Abreve;&amp;scaron;At&amp;Acirc;19&amp;Acirc;&amp;scaron;in&amp;Abreve;&amp;sect;ee&amp;Abreve;&amp;Zcaron;an&amp;Abreve;
&amp;curren;An&amp;Abreve;&amp;curren;gh&amp;Abreve;&amp;acute;ma&amp;Abreve;&amp;sect;pr&amp;Abreve;&amp;Zdot;ou&amp;Abreve;&amp;shy;ov&amp;Abreve;&amp;Lcaron;ag&amp;Abreve;&amp;Lcaron; -&amp;Acirc;'m&amp;Acirc;'s&amp;Acirc;Yo&amp;Abreve;&amp;lcaron; I&amp;Acirc;an&amp;Abreve;&amp;acute;ia&amp;Abreve;&amp;Zacute;  &amp;Acirc; &amp;Acirc;&amp;uml;e&amp;Abreve;&amp;ogon;,&amp;Acirc; &amp;Acirc;.&amp;Acirc;!&amp;Acirc;?&amp;Acirc;
A&amp;Acirc;o&amp;Abreve;&amp;ogon;s&amp;Abreve;&amp;lstrok;e&amp;Abreve;&amp;Lcaron;c&amp;Abreve;&amp;uml;s&amp;Abreve;&amp;uml;u&amp;Abreve;&amp;Zcaron;l&amp;Abreve;&amp;scaron;t&amp;Abreve;&amp;uml;T&amp;Abreve;&amp;uml;T&amp;Abreve;&amp;Zdot;t&amp;Abreve;&amp;Zdot;o&amp;Abreve;&amp;caron;q&amp;Abreve;&amp;lcaron;Q&amp;Abreve;&amp;lcaron;B&amp;Abreve;&amp;Lcaron;b&amp;Abreve;&amp;Lcaron;U&amp;Abreve;&amp;deg;u&amp;Abreve;&amp;deg;R&amp;Abreve;&amp;Lcaron;r&amp;Abreve;&amp;Lcaron;e&amp;Abreve;&amp;Zcaron;E&amp;Abreve;&amp;Zcaron;u&amp;Abreve;&amp;lstrok;U
&amp;Abreve;&amp;lstrok;e&amp;Abreve;&amp;curren;o &amp;Abreve;&amp;Zdot;.&amp;Acirc;&amp;breve;!&amp;Acirc;&amp;breve;?&amp;Acirc;&amp;breve;;&amp;Acirc;:&amp;Acirc;)&amp;Acirc;p&amp;Abreve;&amp;Lcaron;P&amp;Abreve;&amp;Lcaron;i&amp;Abreve;&amp;ogon;I&amp;Abreve;&amp;ogon;m&amp;Abreve;&amp;scaron;p&amp;Abreve;&amp;deg;I&amp;Acirc;d&amp;Abreve;&amp;curren;e&amp;Abreve;&amp;Aogon;f&amp;Abreve;&amp;Sacute;s&amp;Abreve;&amp;lstrok;i&amp;Abreve;&amp;acute;r&amp;Abreve;&amp;ogon;a&amp;Abreve;&amp;acute;A&amp;Abreve;&amp;acute;e&amp;Acirc;
y&amp;Acirc;i&amp;Abreve;&amp;Lstrok;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The most obvious block is beginning with the word &lt;em&gt;address&lt;/em&gt; (3rd line). This consists of a series of words delimited with hex &lt;code&gt;A0&lt;/code&gt; (dec 160)&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2" rel="footnote"&gt;2&lt;/a&gt;&lt;/sup&gt;. But this &lt;em&gt;isn't&lt;/em&gt; the same partial substitutions we found earlier. If these worked in the same way, we would be able to find them in the reader code -- for example, the longest partial string we found &lt;code&gt;ing&lt;/code&gt;. But it isn't there (neither is &lt;code&gt;ly&lt;/code&gt;, except in plaintext help message at the end). So, something is going on here.&lt;/p&gt;
&lt;p&gt;At the beginning of the block we see a series of single letters and numbers (but not &lt;em&gt;all&lt;/em&gt; the letters and numbers) arranged, separated by hex &lt;code&gt;01&lt;/code&gt; (dec 1). Notice that the list includes all vowels -- my first thought was that this is a list of the &lt;em&gt;most common&lt;/em&gt; letters in English, though why we'd be substituting single characters is a mystery.&lt;/p&gt;
&lt;h2 id="converting"&gt;Converting&lt;/h2&gt;
&lt;p&gt;Despite the unanswered questions, we have enough information to get started on our converter. First, we can get whole-word substitution map from the &lt;code&gt;DOCREADER&lt;/code&gt; with the following code (byte offsets hardcoded for now):&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;with open('DOCREADER', 'rb') as f:
    data = f.read()

#&amp;Acirc;data is bytestring, isolate substitutions (byte 918 onwards)
data = data[918:918+222]
subs = data.split(b'\xa0')
print(subs)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Which produces the following output.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;[b'address', b'screens', b'screen', b' issue', b'memory', b'screen', b" don't", b' SAMCO', b' SAMCo', b'Coupe', b' FRED', b'bytes', b' data', b" it's", b' from', b' SAM', b'\x7f 19\xb9code', b'Code', b'Data', b'ould', b' out', b' had', b'Coup\xe5SAMC\xcfSAMC\xefThe', b'the', b'tio\xee at', b'emp\xf4\x7f19\xb9com\xf0Com\xf0con\xf3Con\xf3.', b" yo\xf5'll", b'ere', b'You', b' it', b'.)']
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We don't know the substitution codes for these yet, but we can compare the output of &lt;code&gt;DOCREADER&lt;/code&gt; with the input mag file to work them out. Loading up FRED 28 we don't need to look far to find a match -- &lt;em&gt;issue&lt;/em&gt; is in the first line.&lt;/p&gt;
&lt;p&gt;&lt;img alt="FRED28 magazine reader, first page" src="https://www.martinfitzpatrick.com/static/tools/sam-coupe-reader/FRED28reader.png"  loading="lazy" width="576" height="480"/&gt;
&lt;em&gt;&lt;strong&gt;FRED 28 Magazine&lt;/strong&gt; with the reader showing the first page.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The text reads:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;...And what better way to start an issue than with a compliment, heh heh. Ah yes, the Christmas issue.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The word &lt;em&gt;issue&lt;/em&gt; is in our substitution table, and appears twice on the first page. Here is the data from the mag file.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-text"&gt;text&lt;/span&gt;
&lt;pre&gt;&lt;code class="text"&gt;FRED&amp;Abreve;&amp;Abreve;&amp;Abreve;&amp;caron;&amp;Acirc;&amp;zacute;YOU &amp;Abreve;&amp;Abreve;&amp;caron; is!!!&amp;Abreve;&amp;Acirc;P.&amp;Acirc;&amp;lstrok; wh&amp;Abreve;&amp;caron; &amp;Abreve;tt&amp;Abreve; wa&amp;Abreve;&amp;scedil;&amp;Abreve; start an&amp;Acirc;&amp;Abreve;an w&amp;Abreve;&amp;lcaron;h
a &amp;Acirc;&amp;Aogon;lim&amp;Abreve;.t,heh&amp;Abreve;heh&amp;Abreve; Ah&amp;Abreve;yes&amp;Abreve;&amp;Acirc;Christmas i&amp;Abreve;ue&amp;Abreve;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;em&gt;first&lt;/em&gt; issue is substituted with &lt;code&gt;84&lt;/code&gt; (dec 132) , &lt;code&gt;start an&amp;Acirc;&amp;Abreve;an&lt;/code&gt; (it must be the first character after the first 'an'). The &lt;em&gt;second&lt;/em&gt; isn't substituted, but instead has it's double-s substituted with &lt;code&gt;CB&lt;/code&gt; (dec 203). This suggests the word substitution only works on isolated 'whole words' surrounded by space -- the second issue is followed by a full-stop and a space, which has been substituted by &lt;code&gt;C6&lt;/code&gt; (dec 198). That gives us a few more manual entries for the substitution table.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dec&lt;/th&gt;
&lt;th&gt;Hex&lt;/th&gt;
&lt;th&gt;Substitution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;188&lt;/td&gt;
&lt;td&gt;BC&lt;/td&gt;
&lt;td&gt;'s\s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;203&lt;/td&gt;
&lt;td&gt;CB&lt;/td&gt;
&lt;td&gt;ss&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;198&lt;/td&gt;
&lt;td&gt;C6&lt;/td&gt;
&lt;td&gt;.\s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I would expect the word substitution table to be stored ordered in the reader file (so the next word uses a code 1 greater than the previous). To check this we can assign the codes in order and then compare another of the substituted words that appears multiple times -- for example &lt;em&gt;the&lt;/em&gt; which is replaced with &lt;code&gt;9C&lt;/code&gt; (dec 156).&lt;/p&gt;
&lt;p&gt;The following will assign the codes in order from 129 up to however many items there are in &lt;code&gt;subs&lt;/code&gt;.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;dec = range(129, 300)  # too long, will truncate
word_map = dict(zip(dec, subs))
print(word_map)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Which gives the following output...&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;{129: b'address', 130: b'screens', 131: b'screen', 132: b' issue', 133: b'memory', 134: b'screen', 135: b" don't", 136: b' SAMCO', 137: b' SAMCo', 138: b'Coupe', 139: b' FRED', 140: b'bytes', 141: b' data', 142: b" it's", 143: b' from', 144: b' SAM', 145: b'\x7f 19\xb9code', 146: b'Code', 147: b'Data', 148: b'ould', 149: b' out', 150: b' had', 151: b'Coup\xe5SAMC\xcfSAMC\xefThe', 152: b'the', 153: b'tio\xee at', 154: b'emp\xf4\x7f19\xb9com\xf0Com\xf0con\xf3Con\xf3.', 155: b" yo\xf5'll", 156: b'ere', 157: b'You', 158: b' it', 159: b'.)'}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;With &lt;em&gt;issue&lt;/em&gt; lined up at 132, that gives 152 for 'the', rather than the 156 we wanted. Bugger.&lt;/p&gt;
&lt;p&gt;But there are a few weird entries in there, containing something other than plain text -- other codes &amp;gt; 127 ASCII in the table, e.g. &lt;code&gt;Coup\xe5SAMC\xcfSAMC\xefThe&lt;/code&gt;, and what's &lt;code&gt;yo\xf5'll&lt;/code&gt;? There is some additional structure to this lookup table that I'm missing so far. Pressing on I identified a few more manual entries.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dec&lt;/th&gt;
&lt;th&gt;Hex&lt;/th&gt;
&lt;th&gt;Substitution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;176&lt;/td&gt;
&lt;td&gt;B0&lt;/td&gt;
&lt;td&gt;ing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;177&lt;/td&gt;
&lt;td&gt;B1&lt;/td&gt;
&lt;td&gt;een&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;178&lt;/td&gt;
&lt;td&gt;B2&lt;/td&gt;
&lt;td&gt;and&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;179&lt;/td&gt;
&lt;td&gt;B3&lt;/td&gt;
&lt;td&gt;And&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;B4&lt;/td&gt;
&lt;td&gt;ght&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;181&lt;/td&gt;
&lt;td&gt;B5&lt;/td&gt;
&lt;td&gt;mag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;182&lt;/td&gt;
&lt;td&gt;B6&lt;/td&gt;
&lt;td&gt;pro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;C5&lt;/td&gt;
&lt;td&gt;double space&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;As substitutions are added it becomes progressively easier to map the remaining letter groups. For example&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;N\xf7ural  Disasters  -  Earth\xd6ake  and  Typhoon\xba for&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It's obvious that &lt;code&gt;0xD6&lt;/code&gt; is "qu" and &lt;code&gt;0xF7&lt;/code&gt; is "at". After a few more manual assignments to the table we're getting there, and the text is almost readable.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Stevie  T forgotten to write it? Has he been kidnapped by a gangof  rebellious  tin-openers?  Or  is  our  postal  system simplyfalling to pieces? Who knows? \xd3 make up for this utter tragedy,we\'ve  decided to rewardyou all for being such loyal readers by(a) being \xcfnecessarily corny and ingratiating and (b) by givingyou part 2 of "Rachel!  Now, canyou honestly say we\'re not goodtoyou? (Andyou\'d better be thinking "Gosh, how jolly generous"otherwise we will NOT be pleased).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In some places the words are run together. Some of these e.g. &lt;em&gt;BrianMcConnell&lt;/em&gt; are due to the word-wrapping on the SAM Mode 3 screen -- no space is inserted at the end of a line. But in others e.g. &lt;em&gt;goodtoyou&lt;/em&gt; it's a problem -- the "to" might probably be "to " to "you" to " you". There is no way to check these bar comparing with the output of the reader line by line, and this is getting a bit tedious.&lt;/p&gt;
&lt;p&gt;At this point I thought I'd go looking for the source code for the compressor/reader, but landed on something far more helpful -- &lt;a href="http://simoncooke.com/samcoupe/infobase/docs/docreader.html"&gt;documentation&lt;/a&gt;&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3" rel="footnote"&gt;3&lt;/a&gt;&lt;/sup&gt;. It's even rather good.&lt;/p&gt;
&lt;h2 id="the-compressor"&gt;The compressor&lt;/h2&gt;
&lt;p&gt;Below is a snippet from the documentation.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Document text in a MAG file is compressed using a combination of run-length compression
and tokenised strings. Each page is 1344 bytes long (64 characters per line, 21 lines per page). Data
bytes below 128 are passed directly to the output routine, bytes with the value 128 are passed onto the
run-length subroutine, and the rest (129-255) are passed onto the detokenisation routine. Arguably,
better compression could be provided by allowing tokens to also have the value 0-31 -- increasing the
total number of tokens available from 127 to 159 -- but as this is not done by the Document Reader, and
there are no further incarnations of the original reader program planned, this is a moot point.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, the compressor actually uses &lt;em&gt;both&lt;/em&gt; tokenisation and run-length compression, but the run-length applies &lt;em&gt;only&lt;/em&gt; to spaces in the text. That explains why it wasn't more noticeable in the generated output.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The compressor works by looking for a string of 3 or more spaces in the document text.
so this can lead to considerable savings). Thus, whenever a code of &amp;amp;80 hex is found in the compressed
text, the next byte is taken, and this is used as a counter to print spaces to the screen. Occurences of two consecutive spaces are compressed by the tokenising routine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This last point makes sense because run-length encoding would compress a 2-space block to 2 bytes, a waste of time &lt;code&gt;[space-marker][2]&lt;/code&gt;, while replacing 2 spaces with a 1 byte token nets a (small) saving.&lt;/p&gt;
&lt;p&gt;The run-length encoding is simple to implement in Python, the code below gives an example. We read a byte at a time, if the the byte has a value of 128 (80 hex) it's a space marker. We read the next byte and add that many spaces to the output, skipping ahead an extra byte.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;result = b''
i = 0
while i &amp;lt; len(data):
    byte = data[i]

    if byte == 128:
        # Run length compression, get another byte.
        i += 1
        length = data[i]
        result += b' ' * length
    else:
        char = sub_map.get(
            byte,
            bytes([byte])
        ) #&amp;Acirc;substitute if exists, fallback to existing.
        result += char #.decode('utf8')

    i += 1

print(result)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The addition of the run-length decompression makes the text look a lot more like it should, here wrapped at 64 characters.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-text"&gt;text&lt;/span&gt;
&lt;pre&gt;&lt;code class="text"&gt;In  eremeantime though, Merry Christmas, and\xbehopeyou all getn
ice FRED software articles inyour stockings\xc7





BM                          Credits

Editor:- Brian McConnell
Publisher:- FREDPublishing
                                                               T
hanks this month to:-

                Charles Hawes\xc1\xd8n Wyatt
                   Calvin Allett\xc1Banzai\xc7
                              Mork\xc7  Hipposoft
                          Simon Cooke   BTB
                         Steve Taylor\xc1Ian Slavin
                             Andy Monk\xc1Electron Aff in\xad


           REMEMBER:-\xc1 yo\xf5\'llexpression "kill 2 birds wit
h one stone" is notmeant  to be taken literally. It is, however,
 quite easy, ifyoucatch them in a large net first\xa5
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;em&gt;eremeantime&lt;/em&gt; looks a mis-tokenisation by me and there are others. The documentation actually contains a copy of the entire substitution table(!) but it would still be nice to be able to extract this directly from the MAG file -- it'll allow us to decompress magazines without knowing which version of the reader they are using.&lt;/p&gt;
&lt;p&gt;Thankfully the documentation gives a hint where I was going wrong.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If a token is found, 129 is added to the token's dictionary reference number, and it takes the
place of the equivalent text in the compressed data. Thus all it's necessary to do to decompress the tokens, is to have a copy of the appropriate dictionary, and then to use the token data to access that table.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;...and...&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Each token has bit 7 set on the last character, to mark that the end of the token has been reached.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This explains what's going on with the substitution table and why we couldn't just split it using &lt;code&gt;0xA0&lt;/code&gt;. The &lt;em&gt;last character&lt;/em&gt; of each entry in the table has it's high (127) bit set. The value &lt;code&gt;0xA0&lt;/code&gt; is actually a space &lt;code&gt;0x20&lt;/code&gt; (dec 32) with it's high bit set (&lt;code&gt;0x80 + 0x20 = 0xA0&lt;/code&gt;)
Some of the tokens end with a space, others do not!&lt;/p&gt;
&lt;p&gt;With that crucial bit of information we can now parse the token table out of the reader file. We can start iterating from the some position (here 129, which I think is the beginning of the table) and look for the high bit of a byte to be set, using &lt;code&gt;byte &amp;amp; 0x80&lt;/code&gt;.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;data = data[918:918+408]
tokenix = 129 # tokens start at 129.
tokens = {}
token = b''
i = 0
while i &amp;lt; len(data):

    byte = data[i]

    if byte &amp;amp; 0x80:
        # High bit set, subtract, store, and get ready for next token.
        byte -= 0x80
        token += bytes([byte])
        tokens[tokenix] = token
        tokenix += 1
        token = b''
    else:
        token += bytes([byte])

    i += 1

print(tokens)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This generates the following complete token table, with "the" assigned the expected value of 156.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;{129: b'address ', 130: b'screens ', 131: b'screen ', 132: b' issue ', 133: b'memory ', 134: b'screen ', 135: b" don't ", 136: b' SAMCO ', 137: b' SAMCo ', 138: b'Coupe ', 139: b' FRED ', 140: b'bytes ', 141: b' data ', 142: b" it's ", 143: b' from ', 144: b' SAM ', 145: b'\x7f 199', 146: b'code ', 147: b'Code ', 148: b'Data ', 149: b'ould ', 150: b' out ', 151: b' had ', 152: b'Coupe', 153: b'SAMCO', 154: b'SAMCo', 155: b'The ', 156: b'the ', 157: b'tion', 158: b' at ', 159: b'empt', 160: b'\x7f199', 161: b'comp', 162: b'Comp', 163: b'cons', 164: b'Cons', 165: b'. ', 166: b' you', 167: b"'ll ", 168: b'ere ', 169: b'You ', 170: b' it ', 171: b'.) ', 172: b"n't", 173: b'ity', 174: b'At ', 175: b'199', 176: b'ing', 177: b'een', 178: b'and', 179: b'And', 180: b'ght', 181: b'mag', 182: b'pro', 183: b'oum', 184: b'ove', 185: b'age', 186: b' - ', 187: b"'m ", 188: b"'s ", 189: b'You', 190: b' I ', 191: b'ant', 192: b'ial', 193: b'   ', 194: b' (', 195: b'er', 196: b', ', 197: b'  ', 198: b'. ', 199: b'! ', 200: b'? ', 201: b'A ', 202: b'or', 203: b'ss', 204: b'ee', 205: b'ch', 206: b'sh', 207: b'un', 208: b'ly', 209: b'th', 210: b'Th', 211: b'To', 212: b'to', 213: b'ow', 214: b'qu', 215: b'Qu', 216: b'Be', 217: b'be', 218: b'Up', 219: b'up', 220: b'Re', 221: b're', 222: b'en', 223: b'En', 224: b'us', 225: b'Us', 226: b'ed', 227: b'oo', 228: b'."', 229: b'!"', 230: b'?"', 231: b'; ', 232: b': ', 233: b') ', 234: b'pe', 235: b'Pe', 236: b'ir', 237: b'Ir', 238: b'my', 239: b'pp', 240: b'I ', 241: b'dd', 242: b'ea', 243: b'ff', 244: b'ss', 245: b'it', 246: b'rr', 247: b'at', 248: b'At', 249: b'e ', 250: b'y ', 251: b'ic'}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Running this substitution table and the run-length encoding against the compressed text, produces the following perfectly decompressed output.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Colin  "Fuhrer"  Macdonald here has just ordered me to point outthat you, the reader, should always make cheques payable to FREDPublishing.  Some people, you see, have been practising what canonly  be  called "creative cheque writing", and have been makingthem  out  to  all sorts of strange companies like FRED SoftwareLtd.,  and  Belgian  Massage  Inc., (okay, so the last one was ajoke. As far as I know, anyway).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="page-wrap"&gt;Page wrap&lt;/h2&gt;
&lt;p&gt;Each page is 1344 bytes long (64 characters per line, 21 lines per page). So far we've been ignoring line breaks but to get completely readable text we need to add them in.&lt;/p&gt;
&lt;p&gt;Splitting lines is simply a case of iterating through the data, in 64 byte chunks, but first we need to be sure we have the correct start position in the data file. For v1.1 and v1.2 the files have a short header which contains the offset at which the data starts. The following will extract and calculate this offset for us (based on the documentation).&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;offset = int.from_bytes(data[0:2], byteorder='little') - 38233
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Using this as our start position we can now iterate forwards in 64 byte blocks to get well-formatted output.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-text"&gt;text&lt;/span&gt;
&lt;pre&gt;&lt;code class="text"&gt;b'BM                            News                              '
b'                                                                '
b"The  biggest  piece of news this month is that you don't have to"
b"put  up  with a knackered SAM anymore!! (Although, if your SAM's"
b"knackered I don't know how you're supposed to read this and find"
b'out.  Hmmm).  Yes,  those  two faithful SAM people Adrian Parker'
b'(from MGT, Blue Alpha, and SAMCo) and Mark Hall (from MGT, SAMCo'
b'and  SAMTech)  have  joined  together and resurrected Blue Alpha'
b'Electronics!  Already  in  operation, this "new" company can now'
b"repair all those little problems you've been bothered by. Prices"
b'for repairs are:-                                               '
b'                                                                '
b'SAM (not including disk drive) - `30                            '
b'Interfaces                     - `18                            '
b'Disk Drive                     - `18                            '
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  The text above shows all the remaining trailing spaces in place, but we could also truncate the lines.&lt;/p&gt;
&lt;p&gt;We &lt;em&gt;also&lt;/em&gt; need to insert blank lines at the end of every page for readability -- since the original reader displayed the text page by page, it is possible for there to be text at the very bottom of one page that would run against the header on the following page. It's not a disaster, but it's ugly. To solve this we can just count every 21 lines and insert a blank (empty) line into the output.&lt;/p&gt;
&lt;p&gt;Finally, can now convert our text to strings and make some simple substitutions to account for the differences between the SAM Coup&amp;eacute; ASCII table and UTF-8.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;.replace('\x5E', '&amp;uarr;') # is ^ by default,
.replace('\x5F', '_')
.replace('\x60', '&amp;pound;')
.replace('\x7F', '&amp;copy;')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The output is now ready to be written to text files.&lt;/p&gt;
&lt;h2 id="different-versions"&gt;Different versions&lt;/h2&gt;
&lt;p&gt;If all the versions of the reader were the same, this would be the end of the matter. But there are actually 3 different versions used on FRED magazine. The different versions all use different subsitution tables, store them in slightly different locations in the file, and change the format of the mag file header.&lt;/p&gt;
&lt;p&gt;To handle these cases two alternatives were added to the converter --&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;if the original reader executable is available, this can be passed to the converter
and will be used as the source for the dictionary. This table is located in different places
in the different versions, so a series of long &lt;em&gt;probably-unique&lt;/em&gt; bytes are used to find the
table -- there are limited versions out there, so this is fine&lt;/li&gt;
&lt;li&gt;the known dictionaries are included in the converter, and can be specified from the command line&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="final-steps"&gt;Final steps&lt;/h2&gt;
&lt;p&gt;With these modifications it was possible to convert all the FRED magazine text (and letters, and other
content) to plaintext, with the following steps --&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the FRED magazine DSK files were downloaded&lt;/li&gt;
&lt;li&gt;the magazine text and reader files were extracted from the the disk images using &lt;a href="https://github.com/petemoore/samfile"&gt;samfile&lt;/a&gt; then copied to a series of folders&lt;/li&gt;
&lt;li&gt;the files were processed to plain text, passing in the reader file for the specific magazine&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &lt;a href="https://github.com/mfitzp/screader"&gt;screader converter tool&lt;/a&gt; has been released as a Python package. You can install it with&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-bash"&gt;bash&lt;/span&gt;
&lt;pre&gt;&lt;code class="bash"&gt;pip3 install screader
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Run the tool with &lt;code&gt;screader -h&lt;/code&gt; to get the command line help.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-text"&gt;text&lt;/span&gt;
&lt;pre&gt;&lt;code class="text"&gt;screader -h
usage: screader [-h] [--reader READER] [--readerversion {0,1,2}] [--skipinstructions] [--format {text,markdown}] [--outfile OUTFILE]
                mag [mag ...]

Extract Sam Coupe Entropy Reader files.

positional arguments:
  mag                   source MAG file(s) to process.

optional arguments:
  -h, --help            show this help message and exit
  --reader READER, -r READER
                        Path to the reader executable (token table will be extracted).
  --readerversion {0,1,2}, -rv {0,1,2}
                        Version of compressor token table to use (0, 1, 2 for v1.0, v1.1, v1.2 respectively).
  --skipinstructions, -s
                        Skip first page (instructions)
  --format {text,markdown}, -f {text,markdown}
                        Output format, one of (text, markdown).
  --outfile OUTFILE, -o OUTFILE
                        Output file (filename will be used for format, if not specified with -f. Output to &amp;lt;infile&amp;gt;.txt if not provided.)

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can &lt;a href="https://github.com/mfitzp/screader"&gt;see the full source code on Github&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="uploading"&gt;Uploading&lt;/h2&gt;
&lt;p&gt;Once all the files were converted to plaintext, the content was uploading onto &lt;a href="https://www.worldofsam.org/"&gt;World of Sam&lt;/a&gt;, the home of all things SAM Coup&amp;eacute;. For an example see &lt;a href="https://www.worldofsam.org/products/fred-28"&gt;FRED28&lt;/a&gt; --- scroll down for the magazine text. You can find &lt;a href="https://www.worldofsam.org/products/fred"&gt;all issues of FRED here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Each magazine was manually redacted to remove personal info such as addresses, email addresses (later) and phone numbers. This was by far the most time-consuming part of the process, as it wasn't fully automatable. I just read the lot and built a table of regex searches for things that look like addresses, in case I'd nodded off the first time. Hopefully I got them all.&lt;/p&gt;
&lt;p&gt;I still haven't got to the issues &amp;lt; 17 -- which are arguably simpler, since they're just in plain text. Something for a rainy day.&lt;/p&gt;
&lt;h2 id="and-the-network"&gt;...and the Network?&lt;/h2&gt;
&lt;p&gt;No, I didn't find anything about the network, beyond what was already on &lt;a href="https://www.worldofsam.org/products/networking"&gt;World of Sam&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Oh well.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Or is it?&amp;nbsp;&lt;a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text"&gt;&amp;larrhk;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;160 is 32 + 128... Wait for it.&amp;nbsp;&lt;a class="footnote-backref" href="#fnref:2" rev="footnote" title="Jump back to footnote 2 in the text"&gt;&amp;larrhk;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;I'd searched for this before, but probably thought it was called "FRED reader".&amp;nbsp;&lt;a class="footnote-backref" href="#fnref:3" rev="footnote" title="Jump back to footnote 3 in the text"&gt;&amp;larrhk;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="sam-coupe"/></entry><entry><title>Squeezing Space Invaders onto the BBC micro:bit's 25 pixels — MicroPython retro game in just 25 pixels</title><link href="https://www.martinfitzpatrick.com/squeezing-space-invaders-onto-the-bbc-microbit/" rel="alternate"/><published>2021-01-21T07:00:00+00:00</published><updated>2021-01-21T07:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2021-01-21:/squeezing-space-invaders-onto-the-bbc-microbit/</id><summary type="html">How much game can you fit into 25 pixels? Quite a bit it turns out.</summary><content type="html">&lt;p&gt;How much game can you fit into 25 pixels? Quite a bit it turns out.&lt;/p&gt;
&lt;p&gt;This is a mini clone of arcade classic &lt;em&gt;Space Invaders&lt;/em&gt; for the &lt;strong&gt;BBC micro:bit&lt;/strong&gt; microcomputer.
Using the accelerometer and two buttons for input, to can beat off wave after wave of aliens
that advance towards you.&lt;/p&gt;
&lt;p&gt;The screen limits the detail we can manage: each sprite takes up a single pixel on the screen.
The aliens don't drop bombs, as you'd have only 1-2 pixels to react. The buildings/defences at
the bottom are missing, as they'd serve no purpose without the bombs. But it's still a lot of fun!&lt;/p&gt;
&lt;p&gt;The aliens advance down the screen when they reach the edge. Some waves cover the entire width
of the screen and will move down straight away -- shoot the edges to slow them down.&lt;/p&gt;
&lt;p&gt;The controls are as follows --&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tilt left/right to control your fighter, tilt further to move faster.&lt;/li&gt;
&lt;li&gt;Button A - shoot gun&lt;/li&gt;
&lt;li&gt;Button B - fire thermonuclear bomb&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bomb? Yes! In this version you get a big bomb to save yourself when things get tricky. The blast wipes out 50% of the aliens currently on screen, but leaves you blinded for a couple of moves. Use wisely!&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  The game is written in MicroPython. If you have a &lt;strong&gt;BBC micro:bit&lt;/strong&gt; just copy and paste the code into the &lt;a href="https://python.microbit.org/v/2"&gt;Python web editor&lt;/a&gt;
and then flash it to your device.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from microbit import *
import random

MIN_COORD, MAX_COORD = 0, 4  # Range of valid coordinates for the display.
MAX_MISSILES = 5             # Number of missiles player can have on screen at once.
DIFFICULTY_INCREASE = 0.25   # Increase in difficulty between waves.

ALIEN_START_POSITIONS = [
    # Each row is a unique start pattern, defined as tuples of x,y coordinates.
    [(1, 0), (2, 0), (3, 0), (1, 1), (2, 1), (3, 1)],
    [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (2, 1)],
    [(1, 0), (2, 0), (3, 0), (0, 1), (2, 1), (4, 1)],
    [(1, 0), (2, 0), (3, 0), (1, 1), (3, 1), (2, 2)],
]

def wait_for_button():
    # Wait for either button to be pressed.
    while not (button_a.was_pressed() or button_b.was_pressed()):
        sleep(1)

def move(sprite, x, y):
    """
    Move the given sprite by the given x &amp;amp; y amounts.
    """
    return sprite[0] + x, sprite[1] + y

def in_bounds(pos):
    """
    Return True if the position is within the valid screen coordinates.
    """
    if pos[0] &amp;lt; MIN_COORD or pos[0] &amp;gt; MAX_COORD:
        return False
    if pos[1] &amp;lt; MIN_COORD or pos[1] &amp;gt; MAX_COORD:
        return False
    return True


class Game:
    """
    Game class holds the current game state.
    """

    def reset(self):
        # Initial values
        self.x = 2  # Player x coordinate start (middle).
        self.xf = 2.0  # x coordinate float, allows us to use tilt for move speed.

        self.missiles = []  # Active missles on screen.
        self.aliens = []    # Active aliens on screen.
        self.alien_velocity_x = 1  # Horizontal speed of aliens.

        self.bombs = 3        # Number of bombs the player has.
        self.active_bomb = 0  # Countdown timer for the current active bomb.

        self.score = 0        # Player score.

        self.tick = 0         # Game loop tick.
        self.level = 0        # Current game level.
        self.difficulty = 20  # Is in reverse, decrement to increase.


    def handle_input(self):
        self.tick += 1
        acc_x = accelerometer.get_x()

        # Use the accelerometer / 512 so the player can move x at speed by tilting more.
        if acc_x &amp;lt; 0:
            self.xf += acc_x / 512
        if acc_x &amp;gt; 0:
            self.xf += acc_x / 512

        # Constrain to the screen dimensions.
        if self.xf &amp;gt; MAX_COORD:
            self.xf = MAX_COORD

        if self.xf &amp;lt; MIN_COORD:
            self.xf = MIN_COORD

        self.x = int(self.xf)

        if button_a.was_pressed():
            # Add missile, at players current x position.
            self.missiles.append((self.x, 4))

        if button_b.was_pressed() and self.bombs:
            # Fire bomb. Flash + remove half the aliens.
            # randint(0,1) will be 50% 1, 50% 0 ..if 0 (False) alien will be skipped.
            self.aliens = [alien for alien in self.aliens if random.randint(0,1)]
            self.active_bomb = 3 # Reduces 1 per tick. Screen at 3 * bright.
            self.bombs -= 1

    def add_aliens(self):
        # We need to copy, or we'll me modifying the original lists.
        alien_position = self.level % len(ALIEN_START_POSITIONS)
        self.aliens = ALIEN_START_POSITIONS[alien_position].copy()
        self.tick = 0

    def advance_aliens(self):
        """
        If aliens have reached the screen edge, advance them all downwards.
        """
        for alien in self.aliens:
            if (
                (self.alien_velocity_x == -1 and alien[0] == MIN_COORD) or
                (self.alien_velocity_x == +1 and alien[0] == MAX_COORD)
            ):
                # If any aliens are at the far edge, increment y, and reverse.
                self.alien_velocity_x = -self.alien_velocity_x
                self.aliens = [move(alien, 0, 1) for alien in self.aliens]
                # This can happen if detached alien slips past bottom.
                self.aliens = [alien for alien in self.aliens if in_bounds(alien)]
                return True  # No other move this time.

    def aliens_can_move(self):
        if self.tick &amp;gt; self.difficulty:
            self.tick = 0
            return True

    def move_aliens(self):
        # Move aliens horizontally.
        self.aliens = [move(alien, self.alien_velocity_x, 0) for alien in self.aliens]

    def move_missiles(self):
        # Advance positions of missiles (upwards)
        self.missiles = [move(missile, 0, -1) for missile in self.missiles]
        self.missiles = [missile for missile in self.missiles if in_bounds(missile)]

    def check_collisions(self):
        for missile in self.missiles[:]:  # Iterate a copy.
            if missile in self.aliens:
                # Since we store by coordinates, we can remove using the missile coords.
                self.aliens.remove(missile)
                self.missiles.remove(missile)
                self.score += 1

        if not self.aliens:
            # Wave complete? Increase difficulty (decrement) and add new aliens.
            self.difficulty -= DIFFICULTY_INCREASE
            self.level += 1
            self.bombs += 1
            self.add_aliens()

    def draw(self):
        display.clear()

        if self.active_bomb:
            # Bomb is drawn as an overlay of gradually decaying light.
            for dx in range(MAX_COORD + 1):
                for dy in range(MAX_COORD + 1):
                    display.set_pixel(dx, dy, self.active_bomb * 3)

            # Decrement so next draw is fainter.
            self.active_bomb -= 1

        # Draw all the aliens.
        for pos in self.aliens:
            display.set_pixel(pos[0], pos[1], 9)

        # Draw all the current player missles.
        for pos in self.missiles:
            display.set_pixel(pos[0], pos[1], 5)

        # Draw the players spaceship.
        display.set_pixel(self.x, 4, 9)

    def game_over(self):
        return (self.x, 4) in self.aliens



game = Game() # Create our game object.


while True:

    display.show(Image.TARGET)
    wait_for_button()

    game.reset() # Reset the game state.
    game.add_aliens()

    # Main loop
    while not game.game_over():
        game.handle_input()
        if game.aliens_can_move():
            if not game.advance_aliens():
                game.move_aliens()
        game.move_missiles()
        game.draw()
        game.check_collisions()

        sleep(100)

    display.show(Image.ANGRY)
    sleep(1000)
    display.scroll(game.score)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can adjust the difficulty by adjusting the &lt;code&gt;difficulty&lt;/code&gt; setting, or &lt;code&gt;speed&lt;/code&gt;. You could also modify the bomb to only destroy 33% of aliens, or less, to make it less of a certain life-saver.&lt;/p&gt;
&lt;p&gt;The video below shows a short playthrough and end of game score.&lt;/p&gt;
&lt;video class="" controls="true" loop="" style="max-width: 100%; min-height: 546px;"&gt;&lt;source src="//i.imgur.com/K6BXnNl.mp4" type="video/mp4"/&gt;&lt;/video&gt;
&lt;p&gt;Have fun!&lt;/p&gt;</content><category term="python"/><category term="micropython"/><category term="games"/></entry><entry><title>Network Interface Design — Details of the SAM Coupé networking circuitry</title><link href="https://www.martinfitzpatrick.com/samcoupe-network-schematics/" rel="alternate"/><published>2020-10-23T09:00:00+00:00</published><updated>2020-10-23T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2020-10-23:/samcoupe-network-schematics/</id><summary type="html">In the &lt;a href="/samcoupe-network"&gt;first part&lt;/a&gt; we looked at the information about the SAM Coupe network available in the user manual, technical manual and other examples of networking systems that may have influenced the design on the SAM Coupe. Next I'm going to look at the electrical design of the SAM Coupe and the other devices, to see if they really are compatible and whether we can knock together an interface to start reading the wire protocol.</summary><content type="html">&lt;p&gt;In the &lt;a href="/samcoupe-network"&gt;first part&lt;/a&gt; we looked at the information about the SAM Coupe network available in the user manual, technical manual and other examples of networking systems that may have influenced the design on the SAM Coupe. Next I'm going to look at the electrical design of the SAM Coupe and the other devices, to see if they really are compatible and whether we can knock together an interface to start reading the wire protocol.&lt;/p&gt;
&lt;p&gt;We may as well start with the only peripheral I know of that plugs into the SAM coupe Network ports: the Messenger.&lt;/p&gt;
&lt;h2 id="messenger"&gt;Messenger&lt;/h2&gt;
&lt;p&gt;The &lt;em&gt;Messenger&lt;/em&gt; was an MGT device which allows snapshots of software running on a ZX Spectrum to be downloaded (via the network port) to the Sam Coupe to be run there.&lt;/p&gt;
&lt;p&gt;The full schematic by &lt;a href="https://www.worldofsam.org/people/miguel-angel-rodriguez-jodar"&gt;Miguel Angel Rodr&amp;iacute;guez J&amp;oacute;dar&lt;/a&gt; is available &lt;a href="http://www.zxprojects.com/images/stories/messenger_sam_coupe/messenger_interface_sam_coupe.png"&gt;on zxprojects.com&lt;/a&gt;. The particular bit we're interested in (optocoupler/network port) is copied below.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Messenger network port schematic" src="https://www.martinfitzpatrick.com/static/decode/sam-coupe-network/messenger_schematic_crop.png"  loading="lazy" width="1422" height="986"/&gt;&lt;/p&gt;
&lt;p&gt;This shows pins 4 &amp;amp; 5 being fed into an optocoupler (serial in) and serial out coming via pin 3 &amp;amp; 1 (with pin 1 pulled high, not grounded). So, data from the Sam to the Messenger appears to be coming via the MIDI pins, not the network. While data from the Messenger to the Sam &lt;em&gt;does&lt;/em&gt; use the network pins. Odd.&lt;/p&gt;
&lt;p&gt;IDEA: This might be necessary on the Messenger because it only has a single cable, plugging into the &lt;a href="https://www.worldofsam.org/products/messenger"&gt;outermost network port&lt;/a&gt; on the Sam.&lt;/p&gt;
&lt;h2 id="disciple-d"&gt;DISCiPLE +D&lt;/h2&gt;
&lt;p&gt;As we've already looked at in the previous part, the DISCiPLE was an MGT device for the ZX Spectrum which added an easier to use disk drive and IF1-compatible networking.&lt;/p&gt;
&lt;p&gt;The schematic (found on &lt;a href="https://k1.spdns.de/Vintage/Sinclair/82/Peripherals/Disc%20Interfaces/DiSCiPLE%20%26%20Plus%20D%20(MGT%2C%20Datel)/Technical%20stuff/PALs%20-%20DiSCIPLE%20%26%20MGT%20Plus%20D/"&gt;spdns.de&lt;/a&gt;) shows how the IF1 network was wired. Below is a crop from the bottom-right of the full schematic, showing the microphone-socket inputs. By default the ports are grounded, but inserting a plug latches that open, allowing the data to be read. This matches up with what we read in the ZX Interface 1 handbook &amp;ndash; machines shouldn't be connected in a loop, as the empty sockets at each end terminate the network.&lt;/p&gt;
&lt;p&gt;&lt;img alt="DISCiPLE schematic, cropped" src="https://www.martinfitzpatrick.com/static/decode/sam-coupe-network/disciple-schematic-crop.gif"/&gt;&lt;/p&gt;
&lt;p&gt;Note that receive/transmit is on the same wire &amp;ndash; there is only one wire.  Both network plugs are wired together (giving pass through) and transmission pulls this line high (+5V).&lt;/p&gt;
&lt;p&gt;Since the Sam Coupe sockets (DIN plugs) don't have physical latches, there would need some other mechanism to terminate the network (if it's not a loop).&lt;/p&gt;
&lt;p&gt;IDEA: I'm wondering now if cable (with the paired wires on one side) acts to un-terminate the network somehow.&lt;/p&gt;
&lt;h2 id="sam-coupe"&gt;Sam Coupe&lt;/h2&gt;
&lt;p&gt;The full schematic (corrected) of the Sam Coupe is available from &lt;a href="https://velesoft.speccy.cz/samcoupe_schematics-cz.htm"&gt;VELESOFT&lt;/a&gt;. The network (MIDI) part is shown below. This is a bit confusing as the pins are shown in order (1-7) rather than socket order.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Sam Coupe schematic" src="https://www.martinfitzpatrick.com/static/decode/sam-coupe-network/samcoupe-schematic-cropped.png"  loading="lazy" width="898" height="907"/&gt;&lt;/p&gt;
&lt;p&gt;The output is driven by the line on the far left MIDOUT and input is returned to the system via the line in the top right MIDIIN. The labelling on the sockets shows that many of the pins (e.g. &lt;code&gt;NET+&lt;/code&gt; or &lt;code&gt;MI+&lt;/code&gt;) are simply wired through/identical on the two sockets.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  I've confirmed these connections using a multimeter.&lt;/p&gt;
&lt;p&gt;Putting the pin labels into a table with the original socket information (left from the User Manual/right schematic) we get the following listing.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pin&lt;/th&gt;
&lt;th&gt;Network IN&lt;/th&gt;
&lt;th&gt;Network OUT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;NET- / MN-****&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;NET- / MN-&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;nc&lt;/td&gt;
&lt;td&gt;0V&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;NET + / MI+&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;NET+ / MI+&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;MI+&lt;/td&gt;
&lt;td&gt;MO+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;MI-&lt;/td&gt;
&lt;td&gt;MO-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;NET - / 0V&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;NET- / 0V&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;NET+ / NET+&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;NET+ / NET+&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;On the MIDI diagram in the User manual the let hand image was labelled MIDI OUT but there was no equivalent label on the network. Looking at the schematic we can see that MIDI and the network function identically (same circuit) just with the addition of resistors on the network side. So are the ports network IN and OUT? Not really, the wiring is identical for both ports, meaning you can use either to connect the SAM.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Comparing the network wiring" src="https://www.martinfitzpatrick.com/static/decode/sam-coupe-network/network-wiring-compare.png"  loading="lazy" width="764" height="429"/&gt;
&lt;em&gt;Note that this diagram doesn't show the correct socket numbering (it's backwards), do not wire your cable like this.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So, if direction is irrelevant, what is the effect of wiring together pins 6&amp;amp;1 and 3&amp;amp;7 on the network cable? An annotated version of the schematic is shown below, with &lt;code&gt;NET-&lt;/code&gt; lines highlighted in blue (ground) and &lt;code&gt;NET+&lt;/code&gt; lines highlighted in red (+5V). The signal input and output are shown by the grey arrow line.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Annotated version of the network schematic" src="https://www.martinfitzpatrick.com/static/decode/sam-coupe-network/samcoupe-schematic-annotated.png"  loading="lazy" width="935" height="943"/&gt;&lt;/p&gt;
&lt;p&gt;As mentioned, &lt;code&gt;NET-&lt;/code&gt; and &lt;code&gt;NET+&lt;/code&gt; (pins 6&amp;amp;7) are wired through from one socket to another, and &lt;code&gt;NET+&lt;/code&gt; is driven by the computer (via MIDOUT). So, pins 6&amp;amp;7 are sufficient to transmit data and share it with all computers on the network, but each computer needs pins 1&amp;amp;3 connected (to 6&amp;amp;7 respectively) to be able to &lt;em&gt;read&lt;/em&gt; the incoming data.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  This makes the SamCo Messenger even weirder, since it's possible to communicate using just one network port, and MIDI &amp;amp; network are electrically connected why use both pins? Maybe to save a resistor in the device.&lt;/p&gt;
&lt;p&gt;Looking at the highlighted part of the schematic, we can see that the &lt;code&gt;NET+&lt;/code&gt; line is pulled high by default, and pulled low (to &lt;code&gt;GND&lt;/code&gt;) for each bit. The base of transistor N2 is connected through a resistor, to 5V, pulling it high and conducting 5V to &lt;code&gt;NET+&lt;/code&gt;. Signal to the base of N1 opens the transistor, connecting the base of N2 to &lt;code&gt;GND&lt;/code&gt;, switching it off.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Network line is high, pulled low for data" src="https://www.martinfitzpatrick.com/static/decode/sam-coupe-network/samcoupe-schematic-linehigh.png"  loading="lazy" width="864" height="317"/&gt;&lt;/p&gt;
&lt;p&gt;High-off, low-data is in common with standard UART data transmission, and also the standard
for MIDI communication. Is the SAM network just a MIDI network?&lt;/p&gt;
&lt;p&gt;@chipper:l@ TL;DR yes.&lt;/p&gt;
&lt;h2 id="network-topology"&gt;Network topology&lt;/h2&gt;
&lt;p&gt;Since the sockets are wired through, pins 1&amp;amp;3 can be connected on either socket. However, &lt;em&gt;every&lt;/em&gt; SAM in the network needs to have those pins connected on at least one socket in order to read the data. The options for network topology are &amp;ndash;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;loop of machines, using the cable with pins 1-&amp;gt;6 &amp;amp; 3-&amp;gt;7 wired together, each Sam getting one magic end of the cable each&lt;/li&gt;
&lt;li&gt;linear network, with a terminator plug on the end (one end only) which makes this connection (a dangling cable would also work)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Since I've never heard of plug terminators for the Sam Coupe, or a dangling network cable, it seems like the loop was the intended setup.&lt;/p&gt;
&lt;p&gt;There is enough information here now to rig up a serial device and see what we can see on the net.&lt;/p&gt;
&lt;p&gt;Based on what we've found, the SAM network looks to be using the built in MIDI support, at least electrically. The MIDI standard uses a UART connection at 31.25 Kbaud, so that would be a good place to start.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  Turns out on &lt;a href="https://twitter.com/Recta_Pete/status/1359687343674970114"&gt;the Atari ST&lt;/a&gt; it  was also possible to use the MIDI network for basic networking.&lt;/p&gt;
&lt;p&gt;In the previous part we discovered that the network data is sent on pins 6&amp;amp;7, and read from pins 1&amp;amp;3. The data is transmitted line-high, that is 5V is off and 0V is on, in keeping with UART standard. The &lt;a href="https://en.wikipedia.org/wiki/ZX_Interface_1"&gt;ZX Interface 1 Wikipedia article&lt;/a&gt; gives us a speed of 100 kbit/s for the network, which seemed a good place to start. The Sam Coupe Technical manual lists 31.25 kbit/s for MIDI, but it's not clear if this also applies to the network (it does).&lt;/p&gt;
&lt;h3&gt;Flashing a LED, or &lt;em&gt;how I found out the port numbering is backwards&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;To test that we can read data out of the network port I hooked up a simple circuit to the output pins on the network socket. From the schematics we know that a signal on the MIDI out pin drives our output pin high, to 5V. To see this in action we can connect a LED and resistor across this pin to ground.&lt;/p&gt;
&lt;p&gt;It was at this point that I discovered that the diagram in the main body of the user manual has the pin order precisely backwards. The correct order is shown in the appendix.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Pin order from the Technical manual" src="https://www.martinfitzpatrick.com/static/decode/sam-coupe-network/techmanual-midi.png"  loading="lazy" width="763" height="727"/&gt;&lt;/p&gt;
&lt;p&gt;With that fixed, we can trigger output to the network by setting the current device number of the machine with &lt;code&gt;device n1&lt;/code&gt; &lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1" rel="footnote"&gt;1&lt;/a&gt;&lt;/sup&gt; and then saving the current (empty) BASIC program with &lt;code&gt;save "test"&lt;/code&gt;. As you can see below, this creates two flashes on the LED.&lt;/p&gt;
&lt;p&gt;@chipper;l Why two? We'll find out in a minute.&lt;/p&gt;
&lt;p&gt;Moving this circuit to the other port confirmed that the MIDI in and MIDI out ports are electrically connected on the network pins. Our little dongle works just the same connected to either port -- since network out is available on the &lt;em&gt;in&lt;/em&gt; port, we should be able to do full 2-way communication with a single port. We'll find out later if it matters which for the input.&lt;/p&gt;
&lt;h2 id="line-specification"&gt;Line specification&lt;/h2&gt;
&lt;p&gt;In the previous part we discovered that the network data is sent on pins 6&amp;amp;7, and read from pins 1&amp;amp;3. The data is transmitted line-high, that is 5V is off and 0V is on, in keeping with UART standard. The &lt;a href="https://en.wikipedia.org/wiki/ZX_Interface_1"&gt;ZX Interface 1 Wikipedia article&lt;/a&gt; gives us a speed of 100 kbit/s for the network, which seemed a good place to start. The Sam Coupe Technical manual lists 31.25 kbit/s for MIDI, but I wasn't sure if this also applied to the network.&lt;/p&gt;
&lt;p&gt;@chipper:l@ TL;DR it does.&lt;/p&gt;
&lt;p&gt;Next we'll look at sending data to and from the SAM coupe from the Raspberry Pi.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;actual network number is irrelevant as we'll discover later, this just enables network mode&amp;nbsp;&lt;a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text"&gt;&amp;larrhk;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="sam-coupe"/></entry><entry><title>SAM Coupé Network — Figuring out the SAM Coupé 8 bit home computer's networking abilities</title><link href="https://www.martinfitzpatrick.com/samcoupe-network/" rel="alternate"/><published>2020-09-23T07:00:00+00:00</published><updated>2020-09-23T07:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2020-09-23:/samcoupe-network/</id><summary type="html">The &lt;a href="/tag/sam-coupe"&gt;SAM Coup&amp;eacute;&lt;/a&gt; was a British 8 bit home computer that was pitched as a successor to the ZX Spectrum, alongside improved graphics and sound and higher processor speed
it also had in built support for MIDI and networking. The manual talked up how the SAM was &lt;em&gt;ready to expand&lt;/em&gt; and the accompanying image captured my imagination, thinking of all the possibilities this little box had in it.</summary><content type="html">&lt;p&gt;The &lt;a href="/tag/sam-coupe"&gt;SAM Coup&amp;eacute;&lt;/a&gt; was a British 8 bit home computer that was pitched as a successor to the ZX Spectrum, alongside improved graphics and sound and higher processor speed
it also had in built support for MIDI and networking. The manual talked up how the SAM was &lt;em&gt;ready to expand&lt;/em&gt; and the accompanying image captured my imagination, thinking of all the possibilities this little box had in it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Sam Coupe, Ready to Expand" src="https://www.martinfitzpatrick.com/static/decode/sam-coupe-network/manual-ready-to-expand.png"  loading="lazy" width="757" height="917"/&gt;
&lt;em&gt;This diagram seems to suggest the network is a ring network.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;25 years later and my Sam Coupe is still yet to connect to a network (or scanner/digitiser, VCR, modem, light pen...) and I thought it was time to do something about that.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  There is now an Ethernet networking option available for the Sam Coupe &amp;ndash; the &lt;a href="https://www.samcoupe.com/hardtrin.htm"&gt;Trinity Ethernet Interface&lt;/a&gt; from Quazar. If you want to get your Sam Coupe online that's probably a far more sensible option &amp;ndash; this is about getting the original hardware to do what it was intended.&lt;/p&gt;
&lt;h2 id="user-manual"&gt;User Manual&lt;/h2&gt;
&lt;p&gt;The Sam Coupe User Manual mentions the networking capabilities, under a section titled "Edication" (p118) about using the Sam Coupe in an educational network setting.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Sam Coupe, educational networks" src="https://www.martinfitzpatrick.com/static/decode/sam-coupe-network/manual-edication.png"  loading="lazy" width="732" height="552"/&gt;&lt;/p&gt;
&lt;p&gt;This setup is a bit different to how we think of networks now, with a single host #0 (teacher) being the controller of the network, broadcasting data to the others to &lt;code&gt;LOAD&lt;/code&gt;. Oddly, after starting talking about networks, over the page (p119) it then starts talking about &lt;em&gt;streams&lt;/em&gt; &amp;ndash; a related, but separate thing, which allow you to direct output to devices. Maybe this allows you to send data to network stations by doing something like &lt;code&gt;OPEN #6; "n5"&lt;/code&gt; (it doesn't &lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1" rel="footnote"&gt;1&lt;/a&gt;&lt;/sup&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img alt="Sam Coupe, reading/writing streams" src="https://www.martinfitzpatrick.com/static/decode/sam-coupe-network/manual-network-streams.png"  loading="lazy" width="713" height="457"/&gt;&lt;/p&gt;
&lt;p&gt;In the list of BASIC commands (p140) there is a mention of using &lt;code&gt;DEVICE n&amp;lt;number&amp;gt;&lt;/code&gt; to select which network device (computer) to communicate with (&lt;code&gt;LOAD&lt;/code&gt; from or &lt;code&gt;SAVE&lt;/code&gt; to).&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;DEVICE N5 use Network Station 5
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The glossary (p153) there is this helpful note &amp;ndash;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Network&lt;/strong&gt; Up to 16 Sam Coup&amp;eacute;s can be linked in a network, with Channel 0 as the broadcast station and Channels 1 - 15 as personal operator stations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This gives us a maximum size of the network (16 machines) and that individual machines have their own channel number. But we're none the wiser in how to actually wire multiple machines together &amp;ndash; is the network is intended to be set up as a loop (as in the first diagram above) or linear. How are network station numbers assigned?&lt;/p&gt;
&lt;p&gt;Finally, the appendix (p171) has a wiring diagram for creating a network cable. Note that MIDI is on the same port, but using different pins.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Sam coupe, network port wiring" src="https://www.martinfitzpatrick.com/static/decode/sam-coupe-network/manual-network-wiring-withmidi.png"  loading="lazy" width="786" height="435"/&gt;&lt;/p&gt;
&lt;p&gt;@chipper:l@ Don't wire your cable like this, kids. I find out later it's upside down.&lt;/p&gt;
&lt;p&gt;This is interesting, since it shows that pins 6 &amp;amp; 1 &lt;code&gt;NET-&lt;/code&gt; should be joined together, and to the screen &amp;ndash; suggesting that these are ground pins. The pins 3 &amp;amp; 7 are also joined together and provide the signal. What isn't clear is what is meant by "FROM NETWORK PLUG" since both sides of this cable are going to be plugged into a Sam (whether in a loop or linear topology).&lt;/p&gt;
&lt;p&gt;That's basically it for the User Manual, next up the technical manual.&lt;/p&gt;
&lt;h2 id="technical-manual"&gt;Technical Manual&lt;/h2&gt;
&lt;p&gt;Unfortunately, there's even less information about the network in the technical manual than in the user manual, with the only useful thing being a pin diagram for the two network ports (shared with MIDI in and out) matching what is in the user manual. The section about MIDI gives the maximum data rate as 31.25Kbaud (MIDI standard) although it's not clear if this also applies to the network &amp;ndash; everything else here is about MIDI. There is no information here about voltages, transmission/network protocols, or how to wire the SAMs together.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Sam coupe, network port wiring" src="https://www.martinfitzpatrick.com/static/decode/sam-coupe-network/techmanual-midi.png"  loading="lazy" width="763" height="727"/&gt;&lt;/p&gt;
&lt;h2 id="networking-article-by-malcolm-perry-fred40"&gt;Networking Article by Malcolm Perry, FRED40&lt;/h2&gt;
&lt;p&gt;In &lt;a href="https://www.worldofsam.org/products/fred-40"&gt;FRED issue 40&lt;/a&gt; there is a article about Sam Coupe networking by &lt;a href="https://www.worldofsam.org/people/malcolm-perry"&gt;Malcolm Perry&lt;/a&gt;.  The &lt;a href="https://www.worldofsam.org/index.php/products/networking"&gt;full text&lt;/a&gt; is worth a read &amp;ndash; I'll copy the key parts here.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Firstly  the  connecting  lead. The handbook (p171) shows a lead
that  has pins 1&amp;amp;6, 3&amp;amp;7 joined at one end but only pins 6&amp;amp;7 used
at  the  other.  This single lead will give data transfer in one
direction only.&lt;/p&gt;
&lt;p&gt;A  clue! P108 vaguely shows the SAMs connected in a closed loop,
use  a second lead and providing they're connected the right way
round  so that each SAM has an end that has the pins linked then
two way transfer is possible.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This suggests that the machines must be connected in a loop for the network to function. If true that would mean that pin 7 is a passthrough in one direction only (there is no way for data to get back up the line).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Incidentally,  the data leaves the SAM via pins 6&amp;amp;7 and receives
via pins 1&amp;amp;3. I have not worked out the implications of the same
SAM having data fed back into itself by the linking of pins 3&amp;amp;7.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Based on the wiring diagram I though pins 1&amp;amp;6 were just grounded, will need to check this on the machine itself. There seem to be problems with this setup when actually using software that was supposed to support the network, for example &amp;ndash;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Unfortunately,  a  lot  of  programs seemed to lock up when both
leads  were  connected.  I  found that many routines trigger the
Network  output  and  leave  it  high. It seems that if pin 3 is
taken  high then the keyboard is locked out, no doubt to prevent
data  corruption  during  network  use.  However,  as  pin  3 is
connected  to  pin  7  then when the programs drive an output on
network  and  leave  it  there  things  come  to  a halt and the
computer is locked up.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The article has a reply from &lt;a href="https://www.worldofsam.org/index.php/people/colin-macdonald"&gt;Colin Macdonald&lt;/a&gt; (FRED editor) describing using the network, and potential problems with only two SAMs in a closed loop.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When  at  SAMCo last Summer, Adrian, Charles and myself did some
experimenting  and  besides  having  fun we did actually produce
some interesting results!&lt;/p&gt;
&lt;p&gt;Yes,  there does seem to be a problem with two SAM's in a closed
loop  but  we  didn't  investigate as far as Malcolm as done. We
successfully  "daisy-chained"  four  SAMs together with each one
being a different station number.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So there is a problem with 2 computers in a loop. This might be a timing problem with data being received back to the originating station too quickly, while it's still broadcasting. Or maybe you don't want a loop at all? The phrase &lt;em&gt;daisy-chained&lt;/em&gt; is a bit ambiguous here.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  Looking for more info on networking was one of my motivations for &lt;a href="/samcoupe-reader/"&gt;extracting all the text from FRED issues&lt;/a&gt;, but this was all there was!&lt;/p&gt;
&lt;h2 id="zx-net"&gt;ZX Net&lt;/h2&gt;
&lt;p&gt;The last bit of the puzzle I could find online was on the &lt;a href="http://www.keprt.cz/sam/description.php"&gt;The Computer Sam Coupe&lt;/a&gt; page by &lt;a href="http://www.keprt.cz/"&gt;Ale&amp;scaron; Keprt&lt;/a&gt;, which lists some basic technical specifications of the computer. Under Network, the Sam Coupe network is described as "Built-in ZX Network-based serial system, supported by DOS".&lt;/p&gt;
&lt;p&gt;&lt;img alt="ZX Interface 1" src="https://www.martinfitzpatrick.com/static/decode/sam-coupe-network/interface1_back.gif"/&gt;&lt;/p&gt;
&lt;p&gt;The ZX Network was a Sinclair ZX Spectrum networking system, available on the &lt;a href="https://en.wikipedia.org/wiki/ZX_Interface_1"&gt;Interface 1&lt;/a&gt;. Miles Gordon Technology was founded by former employees of Sinclair Research (Alan Miles &amp;amp; Bruce Gordon) so it wouldn't be too far a stretch that the SAM Coupe networking system was based on this earlier system.&lt;/p&gt;
&lt;p&gt;Looking at the &lt;a href="http://www.retro8bitcomputers.co.uk/Downloads/ZXMicrodriveZXMicrodiveandZXInterface1Manual"&gt;ZX Microdrive and ZX Interface 1 Manual&lt;/a&gt; there are certainly some similarities, at least in capabilities.&lt;/p&gt;
&lt;p&gt;&lt;img alt="ZX Interface 1 Manual" src="https://www.martinfitzpatrick.com/static/decode/sam-coupe-network/zxinterface1.jpg"  loading="lazy" width="1136" height="1608"/&gt;
&lt;em&gt;ZX Spectrum Microdrive &amp;amp; Interface 1 Manual cover, you can almost smell the 80s&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Chapter 5 has a mention of streams, which sound awfully familiar (p 21 &amp;amp; 22).&lt;/p&gt;
&lt;p&gt;&lt;img alt="ZX Interface 1 streams" src="https://www.martinfitzpatrick.com/static/decode/sam-coupe-network/zxinterface1-streams.png"  loading="lazy" width="618" height="295"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="ZX Interface 1 streams" src="https://www.martinfitzpatrick.com/static/decode/sam-coupe-network/zxinterface1-streams2.png"  loading="lazy" width="632" height="186"/&gt;&lt;/p&gt;
&lt;p&gt;The network is covered in Chapter 7, and also sounds very similar to the networking capabilities in the Sam Coupe manual. On the first page (p29) we see the following diagram showing 3 ZX Spectrum computers connected in a line (not a loop!) together.&lt;/p&gt;
&lt;p&gt;&lt;img alt="ZX Interface 1 network" src="https://www.martinfitzpatrick.com/static/decode/sam-coupe-network/zxinterface1-network.png"  loading="lazy" width="812" height="546"/&gt;&lt;/p&gt;
&lt;p&gt;The accompanying text says&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Using the lead supplied with each Interface you can link up as few as two and as many as sixty-four Spectrum computers, as shown below. Note, however, that you and your friends should not form a loop of computers: the computers at each end of the net should never be connected to each other. Each should be left with one net socket free.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, this network can be up to &lt;em&gt;64&lt;/em&gt; stations (I wonder if anyone ever managed that!).&lt;/p&gt;
&lt;h2 id="disciple-d"&gt;DISCiPLE +D&lt;/h2&gt;
&lt;p&gt;The DISCiPLE +D was an improved disk interface for the ZX Spectrum developed by MGT, the same guys behind the SAM Coupe itself. The SAM disk operating system SAMDOS was also based on the DISCiPLE's GDOS: it seems likely at least some ideas for networking would have carried over. The DISCiPLE +D network is ZX NET (Interface compatible).&lt;/p&gt;
&lt;p&gt;Looking at the DISCiPLE manual chapter on &lt;a href="http://ramsoft.bbk.org.omegahg.com/tech/mgt_tech.txt"&gt;networking&lt;/a&gt; (section 9.1) it says the DISCiPLE is IF1 (ZX NET) compatible, using the standard 64 base stations.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;GDOS (DISCiPLE) implements an IF1 compatible network, with some enhancements.
Up to 63 Spectrums can be connected together and share their resources (files
and printers) simply through a 3.5 mm mono jack cable.
Each station is given an unique station number ranging from 1 to 63.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As on the SAM there are roles assigned to these stations, and #0 is broadcast -- although on the SAM the &lt;em&gt;master station&lt;/em&gt; was expected to broadcast.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Number&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Broadcasting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Master station&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2-9&lt;/td&gt;
&lt;td&gt;Assistants (they load the system file)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10-63&lt;/td&gt;
&lt;td&gt;Pupils or assistants (pupils do not load the system file)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The commands used in GDOS for loading and saving over the network are also familiar, although using &lt;code&gt;d&lt;/code&gt; for stations (which is used for disks on the SAM) and
devices for networking loading and saving are included with the filename on SAMDOS.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-basic"&gt;basic&lt;/span&gt;
&lt;pre&gt;&lt;code class="basic"&gt;LOAD d1;"pippo"           first load the file, a Basic program in this case
SAVE N16                  send it to station 16
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The GDOS description also mentions being able to get a directory of another stations disk using:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-basic"&gt;basic&lt;/span&gt;
&lt;pre&gt;&lt;code class="basic"&gt;CAT d1
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Which (as far as I know) isn't possible in SAMDOS.&lt;/p&gt;
&lt;h2 id="atari-st"&gt;Atari ST&lt;/h2&gt;
&lt;p&gt;I've also discovered -- thanks to &lt;a href="https://twitter.com/Recta_Pete/status/1359687343674970114"&gt;this tweet by Peter Fletcher&lt;/a&gt; -- that the MIDI ports on the Atari ST were also sometimes used for networking, as in the game &lt;a href="https://en.wikipedia.org/wiki/MIDI_Maze"&gt;MIDI maze&lt;/a&gt;. That page mentions that &lt;em&gt;Up to 16 computers can be networked in a "MIDI Ring" by daisy chaining MIDI ports that are built into the Atari ST series.&lt;/em&gt; &lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2" rel="footnote"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id="so-far"&gt;So far...&lt;/h2&gt;
&lt;p&gt;With everything we've read so far, we can be reasonably sure that --&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the network has 16 stations, each with their own channel number&lt;/li&gt;
&lt;li&gt;channel 0 is the broadcast station, 1-15 are the personal operator stations (it's not clear if channel 0 has to be dedicated station, or all operator stations can broadcast)&lt;/li&gt;
&lt;li&gt;there are 2 network ports, and we have a wiring diagram&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Superficially the description of the SAM network matches with the ZX Net, aside from the number of stations. But we still don't know if the ZX protocol is actually used, or how the network should be wired together.&lt;/p&gt;
&lt;p&gt;Next I'm going to dig into the schematics of the Interface 1, DISCiPLE &amp;amp; Sam Coupe to see if we can settle how the networking works electronically. Once we have that we rig something up to start reading/writing the protocol on the wire.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;The command is accepted, but it doesn't open a network stream.&amp;nbsp;&lt;a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text"&gt;&amp;larrhk;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Makes me wonder if the MIDI-networking on the SAM was inspired by the ST? MIDI Maze was released in 1987.&amp;nbsp;&lt;a class="footnote-backref" href="#fnref:2" rev="footnote" title="Jump back to footnote 2 in the text"&gt;&amp;larrhk;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="sam-coupe"/></entry><entry><title>Create GUI Applications with Python &amp; Qt5, 4th Edition now available (PyQt5 &amp; PySide2) — The hands-on guide to make apps with Python</title><link href="https://www.martinfitzpatrick.com/pyqt5-pyside2-book-4th-edition/" rel="alternate"/><published>2020-06-25T12:06:00+00:00</published><updated>2020-06-25T12:06:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2020-06-25:/pyqt5-pyside2-book-4th-edition/</id><summary type="html">Hello! This morning I released a new update to my PyQt5 book &lt;a href="https://www.pythonguis.com/pyqt5-book"&gt;Create GUI Applications, with Python &amp;amp; Qt5&lt;/a&gt;. This is an enormous update, expanding it from 258 to &lt;strong&gt;665 pages&lt;/strong&gt; and adding &lt;strong&gt;211 complete code&lt;/strong&gt; examples.</summary><content type="html">
            &lt;p&gt;Hello! This morning I released a new update to my PyQt5 book &lt;a href="https://www.pythonguis.com/pyqt5-book"&gt;Create GUI Applications, with Python &amp;amp; Qt5&lt;/a&gt;. This is an enormous update, expanding it from 258 to &lt;strong&gt;665 pages&lt;/strong&gt; and adding &lt;strong&gt;211 complete code&lt;/strong&gt; examples.&lt;/p&gt;
&lt;p&gt;To celebrate the milestone the book is &lt;a href="https://www.pythonguis.com/pyqt5-book"&gt;available this week with 20% off&lt;/a&gt;. Readers get access to all future updates for free, so it's a great time to snap it up!&lt;/p&gt;
&lt;p&gt;&lt;img alt="render_pyqt5_small.png" class="richtext-image full-width" height="316" src="https://cdn.mfitzp.com/media/images/render_pyqt5_small_ZHDkFw6.width-800.png" width="224"/&gt;&lt;/p&gt;
&lt;p&gt;This is the &lt;a href="https://www.pythonguis.com/pyqt5-book"&gt;4th Edition of the book&lt;/a&gt; and adds a number of entirely new chapters &lt;em&gt;including&lt;/em&gt; MVC-like model views, SQL database-views, plotting with matplotlib &amp;amp; PyQtGraph, custom widgets &amp;amp; bitmap graphics, concurrent programming with threads and processes and theming Qt applications.&lt;/p&gt;
&lt;p&gt;Existing chapters have been expanded with more examples and step-by-step guides to using them. All source code shown in the book is available as standalone, runnable, examples to try out and experiment with.&lt;/p&gt;
&lt;p&gt;&lt;img alt="book-pages.png" class="richtext-image left" height="371" src="https://cdn.mfitzp.com/media/images/book-pages.width-500.png" width="500"/&gt;&lt;/p&gt;
&lt;p&gt;Chapters have been updated and expanded with new examples and more detailed explanations and diagrams. Cross-platform screenshots show how your application will look on different systems. Starting from the basic principles of GUI development in Python and building up to more complex topics.&lt;/p&gt;
&lt;p&gt;&lt;img alt="modelviews.png" class="richtext-image right" height="372" src="https://cdn.mfitzp.com/media/images/modelviews.width-500.png" width="446"/&gt;&lt;/p&gt;
&lt;p&gt;Learn how to use Qt's MVC-like model views architecture to sync data with widgets, including querying and editing SQL databases from within your applications.&lt;/p&gt;
&lt;p&gt;&lt;img alt="themes.png" class="richtext-image left" height="416" src="https://cdn.mfitzp.com/media/images/themes.width-500.png" width="488"/&gt;&lt;/p&gt;
&lt;p&gt;Tired of the default application look? Use styles, palettes and Qt Style Sheets to completely customize the look and feel of your applications.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Examples of customised bars." class="richtext-image full-width" height="296" src="https://cdn.mfitzp.com/media/images/power-examples.width-800.png" width="800"/&gt;&lt;/p&gt;
&lt;p&gt;Discover how to create your own widgets to use in your Qt applications. Starting from the basics of bitmap graphics and building up to a fully-customizable widgets.&lt;/p&gt;
&lt;p&gt;&lt;img alt="concurrent.png" class="richtext-image right" height="410" src="https://cdn.mfitzp.com/media/images/concurrent.width-500.png" width="500"/&gt;&lt;/p&gt;
&lt;p&gt;Use threads an processes to perform long-running calculations or communicate with remote services, without locking up your applications. Receive data back from threads and processes and display progress bars. Stop and pause running jobs from your UI.&lt;/p&gt;
&lt;p&gt;&lt;img alt="plotting.png" class="richtext-image right" height="336" src="https://cdn.mfitzp.com/media/images/plotting.width-500.png" width="500"/&gt;&lt;/p&gt;
&lt;p&gt;Visualize data inside your applications, using matplotlib or PyQtGraph. Sync plots with external threads to create live updating dashboards to visualize data in real-time.&lt;/p&gt;
&lt;p&gt;There is also a &lt;a href="https://www.pythonguis.com/pyside2-book"&gt;PySide2 edition of the book&lt;/a&gt; available, which features largely the same content, with examples converted to "Qt for Python". Purchasing either book gets you access to both editions, including any future updates of either.&lt;/p&gt;
&lt;p&gt;Feedback, as always is very welcome!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.pythonguis.com/pyqt5-book"&gt;Get the PyQt5 book&lt;/a&gt;
&lt;a href="https://www.pythonguis.com/pyside2-book"&gt;Get the PySide2 book&lt;/a&gt;&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt5 see my book, &lt;a href="https://www.mfitzp.com/pyqt5-book/"&gt;Create GUI Applications with Python &amp; Qt5.&lt;/a&gt;&lt;/p&gt;
            </content><category term="qt"/><category term="pyqt"/><category term="pyside"/></entry><entry><title>LearnPyQt — One year in, and much more to come. — A quick retrospective on 2019</title><link href="https://www.martinfitzpatrick.com/learn-pyqt-one-year-in-and-more-to-come/" rel="alternate"/><published>2019-12-31T00:12:00+00:00</published><updated>2019-12-31T00:12:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2019-12-31:/learn-pyqt-one-year-in-and-more-to-come/</id><summary type="html">It's been a very good year.</summary><content type="html">
            &lt;p&gt;It's been a very good year.&lt;/p&gt;
&lt;p&gt;Back in May I was looking through my collection of PyQt tutorials and videos and trying to decide what to do with them. They were pretty popular, but being hosted on multiple sites meant they lacked structure between them and were less useful than they could be. I needed somewhere to put them.&lt;/p&gt;
&lt;p&gt;Having looked the options available for hosting tutorials and courses I couldn't find something that fit my requirements. So I committed the #1 programmer mistake of building my own. &lt;a href="https://www.pythonguis.com/"&gt;pythonguis.com&lt;/a&gt; was born, and it turned out pretty great.&lt;/p&gt;
&lt;p&gt;Built on the Django-based Wagtail CMS it has been extended with some custom apps into a fully-fledged learning management system_._ But it's far from complete. Plans include adding progress tracking, certificates and some lightweight gamification. The goal here is to provide little hooks and challenges, to keep you inspired and experimenting with PyQt (and Python). The site uses a freemium model &amp;mdash; detailed tutorials, with an upgrade to buy video courses and books for those that want them.&lt;/p&gt;
&lt;p&gt;The availability of free tutorials is key &amp;mdash; not everyone wants videos or books and not wanting those things shouldn't stop you learning. Even so, the upgrade is a one-off payment to keep it affordable for as many people as possible, no subscriptions here!&lt;/p&gt;
&lt;h4&gt;New Tutorials&lt;/h4&gt;
&lt;p&gt;Once the existing tutorials and videos were up and running I set about creating more. These new tutorials were modelled on the popular &lt;a href="https://www.pythonguis.com/tutorials/multithreading-pyqt-applications-qthreadpool/"&gt;multithreading&lt;/a&gt; tutorial, taking frequently asked PyQt5 questions and pain points and tackling them in detail together with working examples. This led first to the (often dreaded) &lt;a href="https://www.pythonguis.com/tutorials/modelview-architecture/"&gt;ModelView architecture&lt;/a&gt; which really isn't that bad and then later to &lt;a href="https://www.pythonguis.com/tutorials/bitmap-graphics/"&gt;bitmap graphics&lt;/a&gt; which unlocks the power of &lt;code&gt;QPainter&lt;/code&gt; giving you the ability to &lt;a href="https://www.pythonguis.com/tutorials/creating-your-own-custom-widgets/"&gt;create your own custom widgets&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Custom widgets" class="richtext-image full-width" height="296" src="https://cdn.mfitzp.com/media/images/power-examples.width-800.png" width="800"/&gt;&lt;/p&gt;
&lt;p&gt;As the list of obvious targets dries up I'll be adding a topic-voting system on site to allow students to request and vote for their particular topics of interest, to keep me on topic with what people actually want.&lt;/p&gt;
&lt;h4&gt;New Videos&lt;/h4&gt;
&lt;p&gt;The video tutorials were where it all started, however in the past year these have fallen a little behind. This will be rectified in the coming months, with new video tutorials recorded for the advanced tutorials and updates to the existing videos following shortly after. The issue has been balancing between writing new content and recording new content, but that problem is solved now we have...&lt;/p&gt;
&lt;h4&gt;New Writers&lt;/h4&gt;
&lt;p&gt;With the long list of things to tackle I was very happy to be joined this year by a new writer &amp;mdash;&amp;nbsp;John Lim. John is a Python developer from Malaysia, who's been developing with PyQt5 for over 2 years and still remembers all the pain points getting started. His first tutorials covered &lt;a href="https://www.pythonguis.com/tutorials/embed-pyqtgraph-custom-widgets-qt-app/"&gt;embedding custom widgets from Qt Designer&lt;/a&gt; and basic &lt;a href="https://www.pythonguis.com/tutorials/plotting-pyqtgraph/"&gt;plotting with PyQtGraph&lt;/a&gt; both of which were a huge success.&lt;/p&gt;
&lt;p&gt;If you're interested in becoming a writer, &lt;a href="https://www.pythonguis.com/write/"&gt;you can&lt;/a&gt;! You get paid, and &amp;mdash;&amp;nbsp;assuming you enjoy writing about PyQt &amp;mdash;&amp;nbsp;it's a lot of fun.&lt;/p&gt;
&lt;h4&gt;New Types of Content&lt;/h4&gt;
&lt;p&gt;In addition to all the new tutorials and videos, we've been experimenting with new types of content on the site. First of all we have been working on a set of &lt;a href="https://www.pythonguis.com/examples/"&gt;example apps&lt;/a&gt; and &lt;a href="https://www.pythonguis.com/widgets/"&gt;widgets&lt;/a&gt; which you can use for inspiration &amp;mdash;&amp;nbsp;or just plain use the code from &amp;mdash;&amp;nbsp;for your own projects. Everything on the site is open source and free to use.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Goodforbitcoin desktop image" class="richtext-image full-width" height="560" src="https://cdn.mfitzp.com/media/images/Screenshot_2019-07-18_at_19.38.29.width-800.png" width="800"/&gt;&lt;/p&gt;
&lt;p&gt;We've also been experimenting with alternatives short-form tutorials &amp;amp; docs for core Qt widgets and features. The first of these by John covers &lt;a href="https://www.pythonguis.com/tutorials/qscrollarea/"&gt;adding scrollable regions with QScrollArea&lt;/a&gt; to your app. We'll have more of these, together with more complete documentation re-written for Python coming soon.&lt;/p&gt;
&lt;h4&gt;New Year&lt;/h4&gt;
&lt;p&gt;That's all for this year.&lt;/p&gt;
&lt;p&gt;Almost. We're currently running a &lt;a href="https://www.pythonguis.com/pyqt5-book/"&gt;50% discount on all courses and books&lt;/a&gt; with the code &lt;strong&gt;NEWYEAR20&lt;/strong&gt;. Every purchase gets unlimited access to all future updates and upgrades, so this is a great way to get in ahead of all the good stuff coming down the pipeline.&lt;/p&gt;
&lt;p&gt;The same code will give 20% off after New Year. Feel free to share it with the people you love, or wait a few days and share it with people you love slightly less.&lt;/p&gt;
&lt;p&gt;Thanks for all your support in 2019, and here's to another great year of building GUI apps with Python!&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt5 see my book, &lt;a href="https://www.mfitzp.com/pyqt5-book/"&gt;Create GUI Applications with Python &amp; Qt5.&lt;/a&gt;&lt;/p&gt;
            </content><category term="qt"/><category term="pyqt"/><category term="pyside"/></entry><entry><title>Etch-A-Snap — The Raspberry Pi powered Etch-A-Sketch camera</title><link href="https://www.martinfitzpatrick.com/etch-a-snap/" rel="alternate"/><published>2019-04-07T15:20:00+00:00</published><updated>2019-04-07T15:20:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2019-04-07:/etch-a-snap/</id><summary type="html">&lt;strong&gt;Etch-A-Snap&lt;/strong&gt; is (probably) the worlds first &lt;strong&gt;Etch-A-Sketch Camera&lt;/strong&gt;. Powered by a Raspberry Pi Zero (or Zero W) it snaps photos just like any other camera, but outputs them by drawing to an &lt;em&gt;Pocket Etch-A-Sketch&lt;/em&gt; screen. Quite slowly.</summary><content type="html">&lt;p&gt;&lt;strong&gt;Etch-A-Snap&lt;/strong&gt; is (probably) the worlds first &lt;strong&gt;Etch-A-Sketch Camera&lt;/strong&gt;. Powered by a Raspberry Pi Zero (or Zero W) it snaps photos just like any other camera, but outputs them by drawing to an &lt;em&gt;Pocket Etch-A-Sketch&lt;/em&gt; screen. Quite slowly.&lt;/p&gt;
&lt;p&gt;Photos are processed down to 240x144 pixel 1-bit (black &amp;amp; white) line drawings using &lt;code&gt;Pillow&lt;/code&gt; and &lt;code&gt;OpenCV&lt;/code&gt; and then translated into plotter commands by building a network graph representation with &lt;code&gt;networkx&lt;/code&gt;. The &lt;em&gt;Etch-A-Sketch&lt;/em&gt; wheels are driven by two 5V stepper motors mounted into a custom 3D printed frame. The &lt;em&gt;Etch-A-Snap&lt;/em&gt; is entirely portable and powered by 4xAA batteries &amp;amp; 3x18650 LiPo cells.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;developing time&lt;/em&gt; for a photo is approximately 15 minutes to 1 hour depending on complexity&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1" rel="footnote"&gt;1&lt;/a&gt;&lt;/sup&gt;.
The video below shows the Etch-A-Snap in action taking a photo.&lt;/p&gt;
&lt;iframe allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/wFrpxqqJUbc" width="560"&gt;&lt;/iframe&gt;
&lt;p&gt;Keep scrolling for some more examples.&lt;/p&gt;
&lt;h2 id="camera-examples"&gt;Camera Examples&lt;/h2&gt;
&lt;p&gt;These shots were taken live using the Etch-A-Snap &amp;mdash;&amp;nbsp;a single shot was taken and immediately drawn to the screen. The drawing process was captured using a timelapse camera.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://imgur.com/o1HbUvc.mp4"&gt;Selfie&lt;/a&gt;&lt;/strong&gt; taken indoors against a plain background.
&lt;video class="" controls="" style="max-width: 100%; min-height: 546px;"&gt;&lt;source src="//imgur.com/o1HbUvc.mp4" type="video/mp4"/&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://imgur.com/eeu8bcH.mp4"&gt;Outdoor street view&lt;/a&gt;&lt;/strong&gt;, showing the Etch-A-Snap struggling a bit on building details. &lt;video class="" controls="" style="max-width: 100%; min-height: 546px;"&gt;&lt;source src="//imgur.com/eeu8bcH.mp4" type="video/mp4"/&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.youtube.com/watch?v=zniip20q794"&gt;Doraemon and a Deer&lt;/a&gt;&lt;/strong&gt; shows a real-time recording of a snap. The image taken with the camera &lt;a href="https://imgur.com/05mpKQC"&gt;can be seen here for reference&lt;/a&gt;. In realtime you can see the initial print speed is slow as the graph is still being calculated, but then quickly increases. Print time is 6 minutes.
&lt;iframe allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/zniip20q794" width="560"&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  In early tests the &lt;em&gt;Etch-A-Snap&lt;/em&gt; ran at 2 pixels per second, but now achieves a lightning fast 20 pixels per second.&lt;/p&gt;
&lt;h2 id="image-examples"&gt;Image examples&lt;/h2&gt;
&lt;p&gt;The Etch-A-Snap can also draw a picture from any image type supported by &lt;code&gt;Pillow&lt;/code&gt; using the &lt;code&gt;draw.py&lt;/code&gt; utility. Using this script the Etch-A-Snap crops, resizes and processes the images the exact same way as from the camera, but you can be a bit more selective with the image and increase the chances of getting something half decent out.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://imgur.com/1oXtA1r.mp4"&gt;Andre Hazes&lt;/a&gt;&lt;/strong&gt; generated from a stock photo with plain white background. This is probably the best photographic result produced, producing a pretty recognisable result. &lt;video class="" controls="" style="max-width: 100%; min-height: 546px;"&gt;&lt;source src="//imgur.com/1oXtA1r.mp4" type="video/mp4"/&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://i.imgur.com/tKF7QmS.mp4"&gt;Python logo&lt;/a&gt;&lt;/strong&gt; generated from a colour PNG. The logo is on a plain white background, leaving it completely disconnected from the frame network. Linkers are added automatically. &lt;video class="" controls="" style="max-width: 100%; min-height: 546px;"&gt;&lt;source src="//i.imgur.com/tKF7QmS.mp4" type="video/mp4"/&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://imgur.com/i0tXHtN.mp4"&gt;Etch-A-Sketch logo&lt;/a&gt;&lt;/strong&gt; generated from a monochrome PNG at 120x122. &lt;video class="" controls="" style="max-width: 100%; min-height: 546px;"&gt;&lt;source src="//imgur.com/i0tXHtN.mp4" type="video/mp4"/&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://imgur.com/wmu5kTd.mp4"&gt;The Queen&lt;/a&gt;&lt;/strong&gt;. There is no route-optimisation (travelling salesman) at work when drawing, aside from adding weight to previously draw areas to encourage avoiding them in future. This picture of the Queen shows some serious back-tracking over the hair/top right corner. This picture was generated at 120x122. &lt;video class="" controls="" style="max-width: 100%; min-height: 546px;"&gt;&lt;source src="//imgur.com/wmu5kTd.mp4" type="video/mp4"/&gt;&lt;/video&gt;&lt;/p&gt;
&lt;h2 id="how-does-it-work"&gt;How does it work?&lt;/h2&gt;
&lt;p&gt;If you're interested in how the Etch-A-Snap works, or want to build one yourself, take a look at the separate parts of the write-up &amp;mdash;
&lt;a href="/etch-a-snap--build"&gt;Build&lt;/a&gt;, &lt;a href="/etch-a-snap--build"&gt;Processing&lt;/a&gt;, &lt;a href="/etch-a-snap--build"&gt;Drawing&lt;/a&gt; and &lt;a href="/etch-a-snap--build"&gt;Plotter&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The following files and resources are also available &amp;mdash;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The full code is &lt;a href="https://github.com/mfitzp/etchasnap"&gt;available on Github&lt;/a&gt; along with &lt;a href="https://github.com/mfitzp/etchasnap/resources/Etch-A-Snap.ipynb"&gt;a Jupyter notebook&lt;/a&gt; which details (and allows you to) experiment with the image processing and graph generation.&lt;/li&gt;
&lt;li&gt;You can &lt;a href="https://download.martinfitzpatrick.com/etch-a-snap-3d-prints.zip"&gt;download the STL&lt;/a&gt; files for 3D printing, or edit the model on &lt;a href="https://www.tinkercad.com/things/13uotDFY1AL-etch-a-snap"&gt;TinkerCad&lt;/a&gt; directly.&lt;/li&gt;
&lt;li&gt;The circuit &lt;a href="https://download.martinfitzpatrick.com/Etch-A-Snap.fzz"&gt;Fritzing file is also available&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="footnote"&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;It's quicker than film!&amp;nbsp;&lt;a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text"&gt;&amp;larrhk;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="etch-a-snap"/><category term="etch-a-sketch"/><category term="python"/><category term="raspberry-pi"/></entry><entry><title>Gyroscopic 3D wireframe cube — Using a 3-axis gyro for live 3D perspective</title><link href="https://www.martinfitzpatrick.com/gyroscopic-wireframe-cube/" rel="alternate"/><published>2019-01-11T08:00:00+00:00</published><updated>2019-01-11T08:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2019-01-11:/gyroscopic-wireframe-cube/</id><summary type="html">This little project combines the previous &lt;a href="/3-axis-gyro-micropython/"&gt;accelerometer-gyroscope&lt;/a&gt; code with the &lt;a href="/3d-rotating-cube-micropython-oled/"&gt;3D rotating OLED cube&lt;/a&gt; to produce a 3D cube which responds to gyro input, making it possible to "peek around" the cube with simulated perspective, or make it spin with a flick of the wrist.</summary><content type="html">&lt;p&gt;This little project combines the previous &lt;a href="/3-axis-gyro-micropython/"&gt;accelerometer-gyroscope&lt;/a&gt; code with the &lt;a href="/3d-rotating-cube-micropython-oled/"&gt;3D rotating OLED cube&lt;/a&gt; to produce a 3D cube which responds to gyro input, making it possible to "peek around" the cube with simulated perspective, or make it spin with a flick of the wrist.&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  Take a look at those earlier articles if you're interested in the background basics.&lt;/p&gt;
&lt;div class="requirements"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th colspan="2"&gt;Requirements&lt;/th&gt;
&lt;th colspan="2"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Wemos D1 &lt;span&gt;v2.2+ or good imitations.&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="http://geni.us/wemosd1" target="_blank" class="link-affiliate"&gt;amazon&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3-axis Gyroscope  &lt;span&gt;Based on MPU6050 chip&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="http://geni.us/3axisgyrosmpu6050" target="_blank" class="link-affiliate"&gt;amazon&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.96in OLED Screen  &lt;span&gt;128x64 pixels, I2c interface.&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="http://geni.us/oledi2c128x64" target="_blank" class="link-affiliate"&gt;amazon&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Breadboard &lt;span&gt;Any size will do.&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://amzn.to/2HB39F1" target="_blank" class="link-affiliate"&gt;amazon&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wires &lt;span&gt;Loose ends, or jumper leads.&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2 id="libraries"&gt;Libraries&lt;/h2&gt;
&lt;p&gt;We need two Python drivers for this project &amp;mdash;&amp;nbsp;one for the 128x64 OLED display, and one for the gyroscope.&lt;/p&gt;
&lt;p&gt;The display in this example uses the &lt;em&gt;ssd1306&lt;/em&gt; chip, so we can use the module available
&lt;a href="https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py"&gt;in the MicroPython repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The gyroscope is a MPU6050, a Python library for which is available from @adamjezek98 &lt;a href="https://github.com/adamjezek98/MPU6050-ESP8266-MicroPython/blob/master/mpu6050.py"&gt;here on Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Download both files and upload them to your controller using ampy or the web REPL.&lt;/p&gt;
&lt;p&gt;Once the libraries are in place, connect to your controller and try and import both packages. If the imports work, you should be good to go.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import ssd1306
import mpu6050
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="wiring"&gt;Wiring&lt;/h2&gt;
&lt;p&gt;Both the &lt;em&gt;ssd1306&lt;/em&gt; display and the &lt;em&gt;MPU6050&lt;/em&gt; gyroscope-accelerometer communicte via I2C. Helpfully they're also on different channels, so we don't need to do any funny stuff to talk to them both at the same time.&lt;/p&gt;
&lt;p&gt;The wiring is therefore quite simple, hooking them both up to &lt;code&gt;+5V&lt;/code&gt;/&lt;code&gt;GND&lt;/code&gt; and connecting their &lt;code&gt;SCL&lt;/code&gt; and &lt;code&gt;SDA&lt;/code&gt; pins to &lt;code&gt;D1&lt;/code&gt; and &lt;code&gt;D2&lt;/code&gt; respectively.&lt;/p&gt;
&lt;p&gt;&lt;img alt="I2C OLED display and Gyro wired to Wemos D1" src="https://www.martinfitzpatrick.com/static/invent/gyroscopic-wireframe-cube/gyro-display-wemos-d1-on.jpg"  loading="lazy" width="1251" height="707"/&gt;&lt;/p&gt;
&lt;p class="admonition admonition-warning"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-exclamation-circle"&gt;&lt;/i&gt;&lt;/span&gt;  On the boards I have the the &lt;code&gt;SDA&lt;/code&gt;, &lt;code&gt;SCL&lt;/code&gt;, &lt;code&gt;GND&lt;/code&gt; and &lt;code&gt;5V&lt;/code&gt; pins are in reverse order when the boards are placed pins-top. Double check what you're wiring where.&lt;/p&gt;
&lt;h2 id="code"&gt;Code&lt;/h2&gt;
&lt;p&gt;The project is made up of 3 parts &amp;mdash;&amp;nbsp;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the &lt;em&gt;gyroscope&lt;/em&gt; code to calibrate, retrieve and smooth the data&lt;/li&gt;
&lt;li&gt;the &lt;em&gt;3D point&lt;/em&gt; code to handle the positions of cube in space&lt;/li&gt;
&lt;li&gt;the &lt;em&gt;simulation&lt;/em&gt; code to handle the inputs, and apply them to the 3D scene, outputting the result&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;First, the basic imports for I2C and the two libraries used for the display and gyro.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from machine import I2C, Pin
import ssd1306
import mpu6050
import math

i2c = I2C(scl=Pin(5), sda=Pin(4))
display = ssd1306.SSD1306_I2C(128, 64, i2c)
accel = mpu6050.accel(i2c)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Gyroscope&lt;/h3&gt;
&lt;p&gt;The gyroscope values can be a little noisy, and because of manufacturing variation (and gravity) need calibrating at rest before use.&lt;/p&gt;
&lt;p&gt;Some standard &lt;em&gt;smoothing&lt;/em&gt; and &lt;em&gt;calibration&lt;/em&gt; code is shown below &amp;mdash; to see a more thorough explanation of this see the &lt;a href="/3-axis-gyro-micropython/"&gt;introduction to 3-axis gyro-accelerometers in MicroPython&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First the smoothed sampling code which takes a number of samples and returns the &lt;em&gt;mean&lt;/em&gt; average. It accepts a calibration input which provides a base value to remove from the resulting measurement.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def get_accel(n_samples=10, calibration=None):
    # Setup a dict of measure at 0
    result = {}
    for _ in range(n_samples):
        v = accel.get_values()

        for m in v.keys():
            # Add on value / n_samples (to generate an average)
            result[m] = result.get(m, 0) + v[m] / n_samples

    if calibration:
        # Remove calibration adjustment
        for m in calibration.keys():
            result[m] -= calibration[m]

    return result
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The calibration code takes a number of samples, waiting for the variation to drop below threshold. It then returns this &lt;em&gt;base&lt;/em&gt; offset for use in future calls to &lt;code&gt;get_accel&lt;/code&gt;.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def calibrate(threshold=50):
    print('Calibrating...', end='')
    while True:
        v1 = get_accel(100)
        v2 = get_accel(100)
        if all(abs(v1[m] - v2[m]) &amp;lt; threshold for m in v1.keys()):
            print('Done.')
            return v1
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Point3D objects&lt;/h3&gt;
&lt;p&gt;The simplest way to model objects in 3D space is to store and manipulate their &lt;em&gt;vertices&lt;/em&gt; only &amp;mdash;&amp;nbsp;for a cube, that means the 8 corners.&lt;/p&gt;
&lt;p&gt;To rotate the cube we manipulate these points in 3 dimensional space. To draw the cube, we project these points onto a 2-dimensional plane, to give a set of &lt;code&gt;x,y&lt;/code&gt; coordinates, and connect the vertices with our edge lines.&lt;/p&gt;
&lt;p&gt;The code here is &lt;a href="http://codentronix.com/2011/04/21/rotating-3d-wireframe-cube-with-python/"&gt;based on this example for Pygame&lt;/a&gt;. The initial conversion of that code to MicroPython with an OLED screen and some background on the theory &lt;a href="/3d-rotating-cube-micropython-oled/"&gt;can be found here&lt;/a&gt;.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;class Point3D:
    def __init__(self, x = 0, y = 0, z = 0):
        self.x, self.y, self.z = x, y, z

    def rotateX(self, deg):
        """ Rotates this point around the X axis the given number of degrees. """
        rad = deg * math.pi / 180
        cosa = math.cos(rad)
        sina = math.sin(rad)
        y = self.y * cosa - self.z * sina
        z = self.y * sina + self.z * cosa
        return Point3D(self.x, y, z)

    def rotateY(self, deg):
        """ Rotates this point around the Y axis the given number of degrees. """
        rad = deg * math.pi / 180
        cosa = math.cos(rad)
        sina = math.sin(rad)
        z = self.z * cosa - self.x * sina
        x = self.z * sina + self.x * cosa
        return Point3D(x, self.y, z)

    def rotateZ(self, deg):
        """ Rotates this point around the Z axis the given number of degrees. """
        rad = deg * math.pi / 180
        cosa = math.cos(rad)
        sina = math.sin(rad)
        x = self.x * cosa - self.y * sina
        y = self.x * sina + self.y * cosa
        return Point3D(x, y, self.z)

    def project(self, win_width, win_height, fov, viewer_distance):
        """ Transforms this 3D point to 2D using a perspective projection. """
        factor = fov / (viewer_distance + self.z)
        x = self.x * factor + win_width / 2
        y = -self.y * factor + win_height / 2
        return Point3D(x, y, self.z)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="gyro-locked-perspective-simulation"&gt;Gyro-locked Perspective Simulation&lt;/h2&gt;
&lt;p&gt;The first demo uses the accelerometer to produce a simulated perspective view of the cube. Tilting the board allows us to see "around" the edges of the cube, as if we were looking into the scene through a window.&lt;/p&gt;
&lt;p&gt;To detect the angle of the device we're using the accelerometer. You might think to use the gyroscope first &amp;mdash;&amp;nbsp;I did &amp;mdash;&amp;nbsp;but remember the gyroscope detects angular &lt;em&gt;velocity&lt;/em&gt;, not angle. Measurements are zero at rest, in any orientation. You can track the velocity changes and calculate the angle from this yourself, but gradually the error will build up and the cube will end up pointing the wrong way.&lt;/p&gt;
&lt;p&gt;Using the accelerometer we have a defined rest point (flat on the surface) from which to calculate the current rotation. Placing the device flat will always return to the initial state.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;class Simulation:
    def __init__(self, width=128, height=64, fov=64, distance=4):

        self.vertices = [
            Point3D(-1,1,-1),
            Point3D(1,1,-1),
            Point3D(1,-1,-1),
            Point3D(-1,-1,-1),
            Point3D(-1,1,1),
            Point3D(1,1,1),
            Point3D(1,-1,1),
            Point3D(-1,-1,1)
        ]

        # Define the edges, the numbers are indices to the vertices above.
        self.edges  = [
            # Back
            (0, 1), (1, 2), (2, 3), (3, 0),
            # Front
            (5, 4), (4, 7), (7, 6), (6, 5),
            # Front-to-back
            (0, 4), (1, 5), (2, 6), (3, 7),
        ]

        # Dimensions
        self.projection = [width, height, fov, distance]

    def run(self):
        #&amp;nbsp;Starting angle (unrotated in any dimension)
        angleX, angleY, angleZ = 0, 0, 0

        calibration = calibrate()

        while 1:

            data = get_accel(10, calibration)

            angleX = data['AcX'] / 256
            angleY = data['AcY'] / 256

            t = []
            for v in self.vertices:
                # Rotate the point around X axis, then around Y axis, and finally around Z axis.
                r = v.rotateX(angleX).rotateY(angleY).rotateZ(angleZ)

                # Transform the point from 3D to 2D
                p = r.project(*self.projection)

                # Put the point in the list of transformed vertices
                t.append(p)

            display.fill(0)

            for e in self.edges:
                display.line(*to_int(t[e[0]].x, t[e[0]].y, t[e[1]].x, t[e[1]].y, 1))

            display.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We use a simple helper function to convert lists of &lt;code&gt;float&lt;/code&gt; into lists of &lt;code&gt;int&lt;/code&gt; to make updating the OLED display simpler.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def to_int(*args):
    return [int(v) for v in args]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We can create a &lt;code&gt;Simulation&lt;/code&gt; and run it with the following.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;s = Simulation()
s.run()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  Leave it on a flat surface as you start it up, so the calibration can complete quickly.&lt;/p&gt;
&lt;p&gt;Once running it should look something like the following. If you pick up the device and tilt it you should notice the perspective of the cube change, as if you were 'looking around' the side of a real 3D cube.&lt;/p&gt;
&lt;p&gt;{% youtube uesh3CcE0RA %}&lt;/p&gt;
&lt;h2 id="making-it-spin"&gt;Making it Spin&lt;/h2&gt;
&lt;p&gt;So far we've only used the accelerometer, and the cube has remained locked in a single position. This second demo uses the &lt;em&gt;gyroscope&lt;/em&gt; to detect &lt;em&gt;angular velocity&lt;/em&gt; allowing you to make the cube spin by flicking the device in one direction or another.&lt;/p&gt;
&lt;p&gt;We do this by reading the velocity and &lt;em&gt;adding&lt;/em&gt; it along a given axis. By reducing the velocity gradually over time, we can add a sense of &lt;em&gt;friction&lt;/em&gt; to the rotation. The result is a cube that you can flick to rotate, that will gradually come to a rest.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  The idea is to mimick the effect of a cube (e.g. a dice) floating inside a ball of liquid. Rotating it quickly adds momentum, which is gradually reduced by friction.&lt;/p&gt;
&lt;p&gt;The simulation code is given below.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;class Simulation:
    def __init__(
        self,
        width=128,
        height=64,
        fov=64,
        distance=4,
        inertia=10,
        acceleration=25,
        friction=1
        ):

        self.vertices = [
            Point3D(-1,1,-1),
            Point3D(1,1,-1),
            Point3D(1,-1,-1),
            Point3D(-1,-1,-1),
            Point3D(-1,1,1),
            Point3D(1,1,1),
            Point3D(1,-1,1),
            Point3D(-1,-1,1)
        ]

        # Define the edges, the numbers are indices to the vertices above.
        self.edges  = [
            # Back
            (0, 1), (1, 2), (2, 3), (3, 0),
            # Front
            (5, 4), (4, 7), (7, 6), (6, 5),
            # Front-to-back
            (0, 4), (1, 5), (2, 6), (3, 7),
        ]

        # Dimensions
        self.projection = [width, height, fov, distance]

        # Configuration
        self.friction = friction
        self.acceleration = acceleration
        self.inertia = inertia

    def run(self):
        velocityX, velocityY, velocityZ = 0, 0, 0
        calibration = calibrate()

        while 1:
            t = []

            # Get current rotational velocity from sensor.
            data = get_accel(10, calibration)
            gyroX = -data['GyY'] / 1024
            gyroY = data['GyX'] / 1024
            gyroZ = -data['GyZ'] / 1024

            # Apply velocity, with slide for friction.
            if abs(gyroX) &amp;gt; self.inertia:
                velocityX = slide_to_value(velocityX, gyroX, self.acceleration)

            if abs(gyroY) &amp;gt; self.inertia:
                velocityY = slide_to_value(velocityY, gyroY, self.acceleration)

            if abs(gyroZ) &amp;gt; self.inertia:
                velocityZ = slide_to_value(velocityZ, gyroZ, self.acceleration)

            rotated = []
            for v in self.vertices:
                r = v.rotateX(velocityX).rotateY(velocityY).rotateZ(velocityZ)
                p = r.project(*self.projection)
                t.append(p)
                rotated.append(r)

            self.vertices = rotated
            display.fill(0)

            for e in self.edges:
                display.line(*to_int(t[e[0]].x, t[e[0]].y, t[e[1]].x, t[e[1]].y, 1))

            display.show()

            velocityX = slide_to_value(velocityX, 0, self.friction)
            velocityY = slide_to_value(velocityY, 0, self.friction)
            velocityZ = slide_to_value(velocityZ, 0, self.friction)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We need another helper function which handles the gradual "slide" of a given &lt;code&gt;value&lt;/code&gt; towards it's &lt;code&gt;target&lt;/code&gt;. This is used to both smooth acceleration and to gradually bleed off velocity via friction. The maximum value of change is specified by &lt;code&gt;slide&lt;/code&gt;.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def slide_to_value(value, target, slide):
    """
    Move value towards target, with a maximum increase of slide.
    """
    difference = target-value
    if not difference:
        return value
    sign = abs(difference) / difference  #&amp;nbsp;-1 if negative, 1 if positive
    return target if abs(difference) &amp;lt; slide else value + slide * sign
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The simulation works as follows &amp;mdash;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Read the &lt;em&gt;rotational velocity&lt;/em&gt; from the gyroscope for each axis (X and Z axes are reversed because of the orientation of the sensor).&lt;/li&gt;
&lt;li&gt;If the measured velocity in a given axis is higher than &lt;code&gt;inertia&lt;/code&gt; we add move the current &lt;code&gt;velocity&lt;/code&gt; towards the measured value, in steps of &lt;code&gt;acceleration&lt;/code&gt; max.&lt;/li&gt;
&lt;li&gt;The current velocities are used to update the &lt;em&gt;vertices&lt;/em&gt; rotating them in 3D space, and storing the resulting updated positions. This is neccessary to ensure that the orientation of the axes for the view remain aligned with the frame of the gyroscope.&lt;/li&gt;
&lt;li&gt;The display is drawn as before.&lt;/li&gt;
&lt;li&gt;Finally we move all values towards zero by sliding towards zero, in steps of &lt;code&gt;friction&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The end result is a 3D cube which responds to user input through the gyroscope, rotating along the appropriate axis. The &lt;code&gt;inertia&lt;/code&gt; means small movements are ignored, so you can flick it in a given direction and then return it slowly to the original place and it will continue to spin.&lt;/p&gt;
&lt;p&gt;{% youtube JJJxuqNs_f8 %}&lt;/p&gt;
&lt;p&gt;You can experiment with the &lt;code&gt;inertia&lt;/code&gt;, &lt;code&gt;acceleration&lt;/code&gt; and &lt;code&gt;friction&lt;/code&gt; values to see what effect they have. There is no real physics at work here, so you can create some quite weird behaviours.&lt;/p&gt;</content><category term="python"/><category term="micropython"/></entry><entry><title>3-axis Accelerometer-Gyro — Measuring acceleration and orientation with an MPU6050</title><link href="https://www.martinfitzpatrick.com/3-axis-gyro-micropython/" rel="alternate"/><published>2019-01-01T08:00:00+00:00</published><updated>2019-01-01T08:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2019-01-01:/3-axis-gyro-micropython/</id><summary type="html">Measuring acceleration and rotation has a lot of useful applications, from drone or rocket stablisation to making physically interactive handheld games.</summary><content type="html">&lt;p&gt;Measuring acceleration and rotation has a lot of useful applications, from drone or rocket stablisation to making physically interactive handheld games.&lt;/p&gt;
&lt;p&gt;An accelerometer measures &lt;em&gt;proper&lt;/em&gt; acceleration, meaning the rate of change of velocity relative to it's own &lt;em&gt;rest frame&lt;/em&gt;. This is in contrast to &lt;em&gt;coordinate&lt;/em&gt; acceleration, which is relative to a fixed coordinate system. The practical upshot of this is that at rest on Earth an accelerometer will measure acceleration due to the Earth's gravity, of &lt;em&gt;g&lt;/em&gt; &amp;asymp; 9.81 m/s. An accelerometer in freefall will measure zero. This can be adjusted for with calibration.&lt;/p&gt;
&lt;p&gt;A gyroscope (from Ancient Greek &amp;gamma;ῦ&amp;rho;&amp;omicron;&amp;sigmaf; "circle" and &amp;sigma;&amp;kappa;&amp;omicron;&amp;pi;έ&amp;omega; "to look") in contrast measures orientation and &lt;em&gt;angular&lt;/em&gt; velocity, or rotation around an an axis. Angular velocity will always be zero at rest.&lt;/p&gt;
&lt;p&gt;The availability of cheap single-chip accelerometer-gyroscope packages makes them practical for any project.&lt;/p&gt;
&lt;h2 id="mpu6050"&gt;MPU6050&lt;/h2&gt;
&lt;p&gt;The MPU6050 is a nifty little 3-axis accelerometer and gyro package, providing measurements for acceleration along and rotation around 3 axes.
It also contains an inbuilt temperature sensor. There are 4 configurable ranges for the gyro and accelerometer, meaning it can be used for both micro and macro measurements. Communication is via a simple I2C interface.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align="right"&gt;Gyro Full Scale Range (&amp;deg;/sec)&lt;/th&gt;
&lt;th align="right"&gt;Gyro Sensitivity (LSB/&amp;deg;/sec)&lt;/th&gt;
&lt;th align="right"&gt;Gyro Rate Noise (dps/&amp;radic;Hz)&lt;/th&gt;
&lt;th align="right"&gt;Accel Full Scale Range (g)&lt;/th&gt;
&lt;th align="right"&gt;Accel Sensitivity (LSB/g)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align="right"&gt;&amp;plusmn;250&lt;/td&gt;
&lt;td align="right"&gt;131&lt;/td&gt;
&lt;td align="right"&gt;0.005&lt;/td&gt;
&lt;td align="right"&gt;&amp;plusmn;2&lt;/td&gt;
&lt;td align="right"&gt;16384&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="right"&gt;&amp;plusmn;500&lt;/td&gt;
&lt;td align="right"&gt;65.5&lt;/td&gt;
&lt;td align="right"&gt;0.005&lt;/td&gt;
&lt;td align="right"&gt;&amp;plusmn;4&lt;/td&gt;
&lt;td align="right"&gt;8192&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="right"&gt;&amp;plusmn;1000&lt;/td&gt;
&lt;td align="right"&gt;32.8&lt;/td&gt;
&lt;td align="right"&gt;0.005&lt;/td&gt;
&lt;td align="right"&gt;&amp;plusmn;8&lt;/td&gt;
&lt;td align="right"&gt;4096&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="right"&gt;&amp;plusmn;2000&lt;/td&gt;
&lt;td align="right"&gt;16.4&lt;/td&gt;
&lt;td align="right"&gt;0.005&lt;/td&gt;
&lt;td align="right"&gt;&amp;plusmn;16&lt;/td&gt;
&lt;td align="right"&gt;2048&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;See the &lt;a href="https://www.invensense.com/products/motion-tracking/6-axis/mpu-6050/"&gt;full MPU-6050 Product Specificiation&lt;/a&gt;.&lt;/p&gt;
&lt;div class="requirements"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th colspan="2"&gt;Requirements&lt;/th&gt;
&lt;th colspan="2"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Wemos D1 &lt;span&gt;v2.2+ or good imitations.&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="http://geni.us/wemosd1" target="_blank" class="link-affiliate"&gt;amazon&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3-axis Gyroscope  &lt;span&gt;Based on MPU6050 chip&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="http://geni.us/3axisgyrosmpu6050" target="_blank" class="link-affiliate"&gt;amazon&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Breadboard &lt;span&gt;Any size will do.&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="http://geni.us/oledi2c128x32" target="_blank" class="link-affiliate"&gt;amazon&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wires &lt;span&gt;Loose ends, or jumper leads.&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2 id="setting-up"&gt;Setting up&lt;/h2&gt;
&lt;p&gt;The MPU-6050 provides an I2C interface for communication. There are Python libraries available which simplify the communication further and return the measurements in a simple format. The examples here are using &lt;a href="https://github.com/adamjezek98/MPU6050-ESP8266-MicroPython"&gt;this MPU6050 library&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can download the &lt;a href="https://github.com/adamjezek98/MPU6050-ESP8266-MicroPython/blob/master/mpu6050.py"&gt;mpu6050.py&lt;/a&gt; file directly.
Click &lt;em&gt;Raw&lt;/em&gt; format and save the file with a &lt;code&gt;.py&lt;/code&gt; extension.
Upload the file to your device using &lt;a href="https://github.com/adafruit/ampy"&gt;ampy&lt;/a&gt; tool (or the WebREPL):&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;ampy --port /dev/tty.wchusbserial141120 put mpu6050.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;With the &lt;code&gt;mpu6050.py&lt;/code&gt; file on your Wemos D1, you can import it as any other Python module. Connect to your device,
and then in the REPL enter:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from machine import I2C, Pin
import mpu6050
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If the &lt;code&gt;import mpu6050&lt;/code&gt; succeeds, the package is correctly uploaded and you're good to go.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Wiring for 3-axis Gyro with Wemos D1" src="https://www.martinfitzpatrick.com/static/tutorials/3-axis-gyro-micropython/gyro-wemos-d1.jpg"  loading="lazy" width="1064" height="752"/&gt;&lt;/p&gt;
&lt;p&gt;Wire up the MPU6050, connecting pins &lt;code&gt;D1&lt;/code&gt; to &lt;code&gt;SCL&lt;/code&gt; and &lt;code&gt;D2&lt;/code&gt; to &lt;code&gt;SDA&lt;/code&gt;. Provide power from &lt;code&gt;G&lt;/code&gt; and &lt;code&gt;5V&lt;/code&gt;. The light on the MPU6050 should light up once it's active.&lt;/p&gt;
&lt;h2 id="reading-values"&gt;Reading values&lt;/h2&gt;
&lt;p&gt;With the &lt;code&gt;mpu6050&lt;/code&gt; Python library on your device, and the MPU6050 module wired up, you can connect to the shell and start talking to it.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from machine import I2C, Pin
import mpu6050

i2c = I2C(scl=Pin(5), sda=Pin(4))
accel = mpu6050.accel(i2c)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;With the &lt;code&gt;accel&lt;/code&gt; object, we can read values from the sensor with &lt;code&gt;.get_values()&lt;/code&gt;. This will return a dictionary of measurements from the sensor.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; accel.get_values()
{'GyZ': -46, 'GyY': -135, 'GyX': -1942, 'Tmp': 26.7888, 'AcZ': 24144, 'AcY': 68, 'AcX': -1004}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;There are 3 sets of measurements returned from the sensor &amp;mdash;&amp;nbsp;&lt;em&gt;acceleration&lt;/em&gt;, &lt;em&gt;rotation&lt;/em&gt; (gyration) and &lt;em&gt;temperature&lt;/em&gt;. The &lt;em&gt;acceleration&lt;/em&gt; and &lt;em&gt;rotation&lt;/em&gt; measurements provide 3 values each, one for each of the axes (X, Y, Z).&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align="left"&gt;Measurement&lt;/th&gt;
&lt;th align="left"&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align="left"&gt;AcX&lt;/td&gt;
&lt;td align="left"&gt;Acceleration &lt;em&gt;along&lt;/em&gt; X axis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;AcY&lt;/td&gt;
&lt;td align="left"&gt;Acceleration &lt;em&gt;along&lt;/em&gt; Y axis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;AcZ&lt;/td&gt;
&lt;td align="left"&gt;Acceleration &lt;em&gt;along&lt;/em&gt; Z axis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;GyX&lt;/td&gt;
&lt;td align="left"&gt;Rotation &lt;em&gt;around&lt;/em&gt; X axis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;GyY&lt;/td&gt;
&lt;td align="left"&gt;Rotation &lt;em&gt;around&lt;/em&gt; Y axis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;GyZ&lt;/td&gt;
&lt;td align="left"&gt;Rotation &lt;em&gt;around&lt;/em&gt; Z axis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Tmp&lt;/td&gt;
&lt;td align="left"&gt;Temperature &amp;deg;C&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  The direction of the X and Y axes relative to the sensor are shown on the module itself. But you can always just adjust your code by trial and error.&lt;/p&gt;
&lt;h2 id="smoothing"&gt;Smoothing&lt;/h2&gt;
&lt;p&gt;If you repeatedly read measurements from the sensor in this way you'll notice that they're bouncing all over the place. This is normal for analog sensors. Before we can use the measurements from the sensor, we need to &lt;em&gt;smooth out&lt;/em&gt; these random fluctuations to leave us with real representative data.&lt;/p&gt;
&lt;p&gt;A simple way to do this is to read multiple values and take the mean (or median) of all the values. The sensor returns multiple values, so we need to average all of these individually.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def get_smoothed_values(n_samples=10):
    """
    Get smoothed values from the sensor by sampling
    the sensor `n_samples` times and returning the mean.
    """
    result = {}
    for _ in range(n_samples):
        data = accel.get_values()

        for k in data.keys():
            # Add on value / n_samples (to generate an average)
            # with default of 0 for first loop.
            result[k] = result.get(k, 0) + (data[k] / n_samples)

    return result
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Running the above function will sample the sensor ten times, and return the averaged values.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; accel.get_values()
{'GyZ': -46, 'GyY': -135, 'GyX': -1942, 'Tmp': 26.7888, 'AcZ': 24144, 'AcY': 68, 'AcX': -1004}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you repeatedly run this you should find the samples have stabilised quite a bit. As the values are in the range &amp;plusmn;32768 the above measurements are actually &lt;em&gt;pretty close&lt;/em&gt; to zero,
though &lt;em&gt;Z acceleration&lt;/em&gt; is notably higher. &lt;code&gt;AcZ&lt;/code&gt; is the acceleration measurement in the Z (straight-up) axis and this value
is the acceleration due to gravity of &lt;em&gt;g&lt;/em&gt; &amp;asymp; 9.81 m/s.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  The measured value 24144/16384 (at lowest measurement sensitivity) gives 1.47&lt;em&gt;g&lt;/em&gt; so it's still off a bit.&lt;/p&gt;
&lt;p&gt;The other offsets at rest are just inherent errors in the chip (individual, not the model), they're not interesting. To get our measurements centred around zero we can must identify this bias and adjust for it through &lt;em&gt;calibration&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="calibration"&gt;Calibration&lt;/h2&gt;
&lt;p&gt;If we take a number of repeated sensor measurements over time we can determine the standard, or average, deviation
from zero over time. This offset can then be subtracted from future measurements to correct them. The device must
be at rest and not changing for this to work reliably.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def calibrate(threshold=50, n_samples=100):
    """
    Get calibration date for the sensor, by repeatedly measuring
    while the sensor is stable. The resulting calibration
    dictionary contains offsets for this sensor in its
    current position.
    """
    while True:
        v1 = get_accel(n_samples)
        v2 = get_accel(n_samples)
        # Check all consecutive measurements are within
        # the threshold. We use abs() so all calculated
        # differences are positive.
        if all(abs(v1[k] - v2[k]) &amp;lt; threshold for k in v1.keys()):
            return v1  # Calibrated.
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  The &lt;code&gt;all(abs(v1[k] - v2[k]) &amp;lt; threshold for key in v1.keys())&lt;/code&gt; line is a bit of a beast. It iterates all the keys in our &lt;code&gt;v1&lt;/code&gt; dictionary, testing &lt;code&gt;abs(v1[k] - v2[k])&lt;/code&gt; for each. Here &lt;code&gt;abs()&lt;/code&gt; gives us the &lt;em&gt;absolute&lt;/em&gt; or positive difference, so we don't need to compare against negative &lt;code&gt;threshold&lt;/code&gt;. Finally, &lt;code&gt;all()&lt;/code&gt; tests that this is true for every key we've iterated.&lt;/p&gt;
&lt;p&gt;Run this &lt;code&gt;calibrate()&lt;/code&gt; function and wiggle the sensor around. You will see the device remain in the calibrating state, with the light flashing, while you wiggle it.&lt;/p&gt;
&lt;p&gt;This is because while the device is moving, the difference between consecutive measurements will be &lt;em&gt;greater&lt;/em&gt; than the defined threshold.&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  In the above calibration method we're testing all measurements from the sensor. You could of course only test some of them &amp;mdash;&amp;nbsp;e.g. only gyro or acceleration &amp;mdash; depending on what you're using.&lt;/p&gt;
&lt;p&gt;If you place your sensor onto the table, the calibration test will pass and the function will return values in the same format as for &lt;code&gt;.get_values()&lt;/code&gt;.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; calibrate()
{'GyZ': -46, 'GyY': -115, 'GyX': -1937, 'Tmp': 26.8359, 'AcZ': 23960, 'AcY': 44, 'AcX': -872}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The output dictionary of base measurements can be used to adjust subsequent measurements to remove this offset and recalibrate to zero at rest.&lt;/p&gt;
&lt;p&gt;Below is an updated &lt;code&gt;get_smoothed_values&lt;/code&gt; function which removes the calibrated offset before returning the smoothed data.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def get_smoothed_values(n_samples=10, calibration=None):
    """
    Get smoothed values from the sensor by sampling
    the sensor `n_samples` times and returning the mean.

    If passed a `calibration` dictionary, subtract these
    values from the final sensor value before returning.
    """
    result = {}
    for _ in range(n_samples):
        data = accel.get_values()

        for k in data.keys():
            # Add on value / n_samples to produce an average
            # over n_samples, with default of 0 for first loop.
            result[k] = result.get(k, 0) + (data[k] / n_samples)

    if calibration:
        # Remove calibration adjustment.
        for k in calibration.keys():
            result[k] -= calibration[k]

    return result
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The following short snippet will allow you to see a table of the gyro and acceleration measurements in (very smoothed) real-time. The numbers are padded to stop them bouncing around as they change, and it uses control-characters to clear the terminal.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;calibration = calibrate()
while True:
    data = get_smoothed_values(n_samples=100, calibration=calibration)
    print(
        '\t'.join('{0}:{1:&amp;gt;10.1f}'.format(k, data[k])
        for k in sorted(data.keys())),
    end='\r')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Running this you should see something like the following at rest:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;AcX:     -17.7  AcY:      -3.2  AcZ:      -4.2  GyX:      -1.3  GyY:       1.9  GyZ:       1.8  Tmp:       0.0
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you pick up the sensor, you should see the &lt;code&gt;Z&lt;/code&gt; acceleration increase, or decrease as you drop it. The &lt;code&gt;X&lt;/code&gt; and &lt;code&gt;Y&lt;/code&gt; acceleration should increase/decrease if you tilt the device in any direction. The acceleration measured here is acceleration due to gravity &amp;mdash; if you tilt the device so the X axis is pointing straight down, all of &lt;em&gt;g&lt;/em&gt; (&amp;asymp; 9.81 m/s) will be acting through X, and none through &lt;code&gt;Z&lt;/code&gt;.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;AcX:   17679.9  AcY:     233.8  AcZ:  -16332.6  GyX:       3.9  GyY:      32.5  GyZ:       1.0  Tmp:      -0.2
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Gyroscopic measurements show rotation around the relevant axis, so will always be zero at rest, but increase with rotational speed in each axis. For example if you rotate the device away from you, you should see a spike in the &lt;code&gt;Y&lt;/code&gt; gyroscopic value, which returns to zero as the unit comes to a rest.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;AcX:   -6540.4  AcY:      22.4  AcZ:   -1930.1  GyX:    -116.7  GyY:     748.9  GyZ:     211.3  Tmp:       0.0
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you pop your finger on the chip, you should also see the temperature raise very slightly.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;AcX:    -547.8  AcY:      29.8  AcZ:     -36.0  GyX:      -6.9  GyY:       8.9  GyZ:      -3.8  Tmp:       0.3
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This covers the basic work of interfacing with an MPU6050 from MicroPython. I'll be adding some projects using this
chip shortly.&lt;/p&gt;</content><category term="python"/><category term="micropython"/></entry><entry><title>Dictionary Views &amp; Set Operations — Working with dictionary view objects</title><link href="https://www.martinfitzpatrick.com/python-dictionary-sets/" rel="alternate"/><published>2018-09-30T06:00:00+00:00</published><updated>2018-09-30T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2018-09-30:/python-dictionary-sets/</id><summary type="html">The &lt;em&gt;keys&lt;/em&gt;, &lt;em&gt;values&lt;/em&gt; and &lt;em&gt;items&lt;/em&gt; from a dictionary can be accessed using the &lt;code&gt;.keys()&lt;/code&gt;, &lt;code&gt;.values()&lt;/code&gt; and &lt;code&gt;.items()&lt;/code&gt; methods. These methods return &lt;em&gt;view objects&lt;/em&gt; which provide a view on the source dictionary.</summary><content type="html">&lt;p&gt;The &lt;em&gt;keys&lt;/em&gt;, &lt;em&gt;values&lt;/em&gt; and &lt;em&gt;items&lt;/em&gt; from a dictionary can be accessed using the &lt;code&gt;.keys()&lt;/code&gt;, &lt;code&gt;.values()&lt;/code&gt; and &lt;code&gt;.items()&lt;/code&gt; methods. These methods return &lt;em&gt;view objects&lt;/em&gt; which provide a view on the source dictionary.&lt;/p&gt;
&lt;p&gt;The view objects &lt;code&gt;dict_keys&lt;/code&gt; and &lt;code&gt;dict_items&lt;/code&gt; support &lt;code&gt;set&lt;/code&gt;-like operations (the latter only when all values are hashable) which can be used to combine and filter dictionary elements.&lt;/p&gt;
&lt;h2 id="keys"&gt;Keys&lt;/h2&gt;
&lt;p&gt;Dictionary keys are &lt;em&gt;always&lt;/em&gt; hashable, so &lt;code&gt;set&lt;/code&gt; operations are always available on the &lt;code&gt;dict_keys&lt;/code&gt; view object.&lt;/p&gt;
&lt;h3&gt;All keys (&lt;code&gt;set&lt;/code&gt; union)&lt;/h3&gt;
&lt;p&gt;To get all &lt;em&gt;keys&lt;/em&gt; from multiple dictionaries, you can use the &lt;code&gt;set&lt;/code&gt; union.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; d1 = {'key1':'value1', 'key2':'value2', 'key3':3}
&amp;gt;&amp;gt;&amp;gt; d2 = {'key3':'value3-new', 'key5':'value5'}

&amp;gt;&amp;gt;&amp;gt; d1.keys() | d2.keys()
{'key5', 'key3', 'key2', 'key1'}  #&amp;nbsp;this is a set, not a dict
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  You can use the same approach to combine &lt;code&gt;dict_items&lt;/code&gt; and merge dictionaries.&lt;/p&gt;
&lt;h3&gt;Keys in common (&lt;code&gt;set&lt;/code&gt; intersection)&lt;/h3&gt;
&lt;p&gt;The &lt;em&gt;keys&lt;/em&gt; common to two dictionaries can be determined using &lt;code&gt;set&lt;/code&gt; intersection (&lt;code&gt;&amp;amp;&lt;/code&gt;).&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; d1 = {'key1':'value1', 'key2':'value2', 'key3':3}
&amp;gt;&amp;gt;&amp;gt; d2 = {'key3':'value3-new', 'key5':'value5'}

&amp;gt;&amp;gt;&amp;gt; d1.keys() &amp;amp; d2.keys()
{'key3'}    #&amp;nbsp;this is a set, not a dictionary
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You could use the resulting &lt;code&gt;set&lt;/code&gt; to filter your dictionary using a dictionary comprehension.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; {k:d1[k] for k in keys}
{'key2':'value2', 'key1':'value1'}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Unique keys (&lt;code&gt;set&lt;/code&gt; difference)&lt;/h3&gt;
&lt;p&gt;To retrieve keys unique to a given dictionary, you can use &lt;code&gt;set&lt;/code&gt; difference (&lt;code&gt;-&lt;/code&gt;). Keys from the right hand &lt;code&gt;dict_keys&lt;/code&gt; are removed from the left, resulting in a &lt;code&gt;set&lt;/code&gt; of the remaining keys.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; d1 = {'key1':'value1', 'key2':'value2', 'key3':3}
&amp;gt;&amp;gt;&amp;gt; d2 = {'key3':'value3-new', 'key5':'value5'}

&amp;gt;&amp;gt;&amp;gt; d1.keys() - d2.keys()
{'key1', 'key2'}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Unique keys from both (&lt;code&gt;set&lt;/code&gt; symmetric difference)&lt;/h3&gt;
&lt;p&gt;If you want items unique to &lt;em&gt;both&lt;/em&gt; dictionaries, the &lt;code&gt;set&lt;/code&gt; symmetric difference (&lt;code&gt;^&lt;/code&gt;) returns this. The result is items unique to both the left and right hand of the comparison.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; d1 = {'key1':'value1', 'key2':'value2', 'key3':3}
&amp;gt;&amp;gt;&amp;gt; d2 = {'key3':'value3-new', 'key5':'value5'}

&amp;gt;&amp;gt;&amp;gt; d1.keys() ^ d2.keys()
{'key5', 'key2', 'key1'}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="items"&gt;Items&lt;/h2&gt;
&lt;p&gt;If both the keys &lt;em&gt;and&lt;/em&gt; values of a dictionary are hashable, the &lt;code&gt;dict_items&lt;/code&gt; view will support &lt;code&gt;set&lt;/code&gt;-like operations.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  If the values are &lt;em&gt;not&lt;/em&gt; hashable all of these2 operations will all raise a &lt;code&gt;TypeError&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Merge (&lt;code&gt;set&lt;/code&gt; union)&lt;/h3&gt;
&lt;p&gt;You can use &lt;code&gt;set&lt;/code&gt; &lt;em&gt;union&lt;/em&gt; operations to merge two dictionaries.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; d1 = {'key1':'value1', 'key2':'value2', 'key3':3}
&amp;gt;&amp;gt;&amp;gt; d2 = {'key3':'value3-new', 'key5':'value5'}
&amp;gt;&amp;gt;&amp;gt; d3 = {'key4':'value4', 'key6':'value6'}

&amp;gt;&amp;gt;&amp;gt; d = dict(d1.items() | d2.items() | d3.items())
&amp;gt;&amp;gt;&amp;gt; d
{'key1':'value1', 'key2':'value2', 'key3':'value3-new', 'key5':'value5', 'key4':'value4', 'key6':'value6'}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Since it is quite common for dictionary values to &lt;em&gt;not&lt;/em&gt; be hashable, you will probably want to use &lt;a href="/article/python-dictionaries"&gt;one of the other approaches for merging dictionaries&lt;/a&gt; instead.&lt;/p&gt;
&lt;h3&gt;Common entries (&lt;code&gt;set&lt;/code&gt; intersection)&lt;/h3&gt;
&lt;p&gt;The &lt;em&gt;items&lt;/em&gt; common to two dictionaries can be determined using &lt;code&gt;set&lt;/code&gt; intersection (&lt;code&gt;&amp;amp;&lt;/code&gt;). Both the key &lt;em&gt;and&lt;/em&gt; value must match &amp;mdash; items are compared as &lt;code&gt;(key, value)&lt;/code&gt; tuples.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; d1 = {'key1':'value1', 'key2':'value2', 'key3':3}
&amp;gt;&amp;gt;&amp;gt; d2 = {'key1':'value1', 'key5':'value5'}

&amp;gt;&amp;gt;&amp;gt; d1.items() &amp;amp; d2.items()
{('key1', 'value1')}    #&amp;nbsp;this is a set, not a dict
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Unique entries (&lt;code&gt;set&lt;/code&gt; difference)&lt;/h3&gt;
&lt;p&gt;To retrieve items unique to a given dictionary, you can use &lt;code&gt;set&lt;/code&gt; difference (&lt;code&gt;-&lt;/code&gt;). Items from the right hand &lt;code&gt;dict_keys&lt;/code&gt; are removed from the left, resulting in a &lt;code&gt;set&lt;/code&gt; of the remaining item &lt;code&gt;(key, value)&lt;/code&gt; tuples.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; d1 = {'key1':'value1', 'key2':'value2', 'key3':3}
&amp;gt;&amp;gt;&amp;gt; d2 = {'key3':'value3-new', 'key5':'value5'}

&amp;gt;&amp;gt;&amp;gt; d1.items() - d2.items()
{('key3', 3), ('key2', 'value2'), ('key1', 'value1')}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Unique entries from both (&lt;code&gt;set&lt;/code&gt; symmetric difference)&lt;/h3&gt;
&lt;p&gt;If you want items unique to &lt;em&gt;both&lt;/em&gt; dictionaries, the &lt;code&gt;set&lt;/code&gt; symmetric difference (&lt;code&gt;^&lt;/code&gt;) returns this. The result is item &lt;code&gt;(key, value)&lt;/code&gt; tuples unique to both the left and right hand of the comparison.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; d1 = {'key1':'value1', 'key2':'value2', 'key3':3}
&amp;gt;&amp;gt;&amp;gt; d2 = {'key3':'value3-new', 'key5':'value5'}

&amp;gt;&amp;gt;&amp;gt; d1.items() ^ d2.items()
{('key2', 'value2'), ('key5', 'value5'), ('key1', 'value1'), ('key3', 3), ('key3', 'value3-new')}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
</content><category term="python"/></entry><entry><title>Creating a 3D wireframe cube with MicroPython on an OLED display — Basic 3D model rotation and projection</title><link href="https://www.martinfitzpatrick.com/creating-a-3d-rotating-cube-with-micropython-and-oled-display/" rel="alternate"/><published>2018-09-23T07:00:00+00:00</published><updated>2018-09-23T07:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.martinfitzpatrick.com,2018-09-23:/creating-a-3d-rotating-cube-with-micropython-and-oled-display/</id><summary type="html">An ESP2866 is never going to compete with an actual graphics card. But it has more than enough oomph to explore the fundamentals of 3D graphics. In this short tutorial we'll go through the basics of creating a 3D scene and displaying it on an OLED screen using MicroPython.</summary><content type="html">&lt;p&gt;An ESP2866 is never going to compete with an actual graphics card. But it has more than enough oomph to explore the fundamentals of 3D graphics. In this short tutorial we'll go through the basics of creating a 3D scene and displaying it on an OLED screen using MicroPython.&lt;/p&gt;
&lt;p&gt;This kind of mono wireframe 3D reminds me of early ZX Spectrum 3D games which mostly involved shooting one wobbly line at another, and looking at the resulting wobbly lines. It was awesome.&lt;/p&gt;
&lt;p&gt;The 3D code here is &lt;a href="http://codentronix.com/2011/04/21/rotating-3d-wireframe-cube-with-python/"&gt;based on this example for Pygame&lt;/a&gt; with some simplifications and the display code modified for working with &lt;code&gt;framebuf&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="requirements"&gt;Requirements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Wemos D1 v2.2+ or good imitations.&lt;/li&gt;
&lt;li&gt;0.96in OLED Screen 128x64 pixels, I2c interface.&lt;/li&gt;
&lt;li&gt;Breadboard Any size will do.&lt;/li&gt;
&lt;li&gt;Wires (Loose ends, or jumper leads.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="setting-up"&gt;Setting up&lt;/h2&gt;
&lt;p&gt;The display used here is a 128x64 OLED which communicates over I2C. We're using the &lt;em&gt;ssd1306&lt;/em&gt; module for OLED displays available &lt;a href="https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py"&gt;in the MicroPython repository&lt;/a&gt; to handle this communication for us, and provide a &lt;code&gt;framebuf&lt;/code&gt; drawing interface.&lt;/p&gt;
&lt;p&gt;Upload the &lt;code&gt;ssd1306.py&lt;/code&gt; file to your device's filesystem using the &lt;a href="https://github.com/adafruit/ampy"&gt;ampy&lt;/a&gt; tool (or the WebREPL).&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-bash"&gt;bash&lt;/span&gt;
&lt;pre&gt;&lt;code class="bash"&gt;ampy --port /dev/tty.wchusbserial141120 put ssd1306.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;With the &lt;code&gt;ssd1306.py&lt;/code&gt; file on your Wemos D1, you should be able to import it as any other Python module. Connect to your device,
and then in the REPL enter:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from machine import I2C, Pin
import ssd1306
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If the &lt;code&gt;import ssd1306&lt;/code&gt; succeeds, the package is correctly uploaded and you're good to go.&lt;/p&gt;
&lt;p&gt;Wire up the OLED display, connecting pins &lt;code&gt;D1&lt;/code&gt; to &lt;code&gt;SCL&lt;/code&gt; and &lt;code&gt;D2&lt;/code&gt; to &lt;code&gt;SDA&lt;/code&gt;.Provide power from &lt;code&gt;G&lt;/code&gt; and &lt;code&gt;5V&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="I2C OLED display wired to Wemos D1" src="3d-rotating-cube.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;To work with the display, we need to create an &lt;code&gt;I2C&lt;/code&gt; object, connecting via pins &lt;code&gt;D1&lt;/code&gt; and &lt;code&gt;D2&lt;/code&gt; &amp;mdash;&amp;nbsp;hardware pin 4 &amp;amp; 5 respectively. Passing the resulting &lt;code&gt;i2c&lt;/code&gt; object into our &lt;code&gt;SSD1306_I2C&lt;/code&gt; class, along with screen dimensions, gets us our interface to draw with.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from machine import I2C, Pin
import ssd1306
import math


i2c = I2C(scl=Pin(5), sda=Pin(4))
display = ssd1306.SSD1306_I2C(128, 64, i2c)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="modelling-3d-objects"&gt;Modelling 3D objects&lt;/h2&gt;
&lt;p&gt;The simplest way to model objects in 3D space is to store and manipulate their &lt;em&gt;vertices&lt;/em&gt; only &amp;mdash;&amp;nbsp;for a cube, that means the 8 corners.&lt;/p&gt;
&lt;p&gt;To rotate the cube we manipulate these points in 3 dimensional space. To draw the cube, we project these points onto a 2-dimensional plane, to give a set of x,y coordinates, and connect the vertices with our edge lines.&lt;/p&gt;
&lt;p&gt;Rotation along each axis and the projection onto a 2D plane is described below.&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  The full code is &lt;a href="https://www.mfitzp.com/files/3d-cube-micropython.py"&gt;available for download here&lt;/a&gt; if you want to skip ahead and start experimenting.&lt;/p&gt;
&lt;h3&gt;3D Rotation&lt;/h3&gt;
&lt;p&gt;Rotating an object in 3 dimensions is no different than rotating a object on a 2D surface, it's just a matter of perspective.&lt;/p&gt;
&lt;p&gt;Take a square drawn on a flat piece of paper, and rotate it 90&amp;deg;.
If you look before and after rotation the X and Y coordinates of any given corner change, but the square is still flat on the paper. This is analogous to rotating any 3D object along it's Z axis &amp;mdash;&amp;nbsp;the axis that is coming out of the middle of the object and straight &lt;em&gt;up&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The same applies to rotation &lt;em&gt;along&lt;/em&gt; any axis &amp;mdash;&amp;nbsp;the coordinates in the axis of rotation remain unchanged, while coordinates along other axes are modified.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;# Rotation along X
y' = y*cos(a) - z*sin(a)
z' = y*sin(a) + z*cos(a)
x' = x


# Rotation along Y
z' = z*cos(a) - x*sin(a)
x' = z*sin(a) + x*cos(a)
y' = y

# Rotation along Z
x' = x*cos(a) - y*sin(a)
y' = x*sin(a) + y*cos(a)
z' = z
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The equivalent Python code for the rotation along the X axis is shown below. It maps directly to the math already described. Note that when rotating in the X dimension, the x coordinates are returned unchanged and we also need to convert from degrees to radians (we could of course write this function to accept radians instead).&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def rotateX(self, x, y, z, deg):
    """ Rotates this point around the X axis the given number of degrees. Return the x, y, z coordinates of the result"""
    rad = deg * math.pi / 180
    cosa = math.cos(rad)
    sina = math.sin(rad)
    y = y * cosa - z * sina
    z = y * sina + z * cosa
    return x, y, z
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Projection&lt;/h3&gt;
&lt;p&gt;Since we're displaying our 3D objects on a 2D surface we need to be able to convert, or &lt;em&gt;project&lt;/em&gt;, the 3D coordinates onto 2D. The approach we are using here is perspective projection.&lt;/p&gt;
&lt;p&gt;If you imagine an object moving away from you, it gradually shrinks in size until it disappears into the distance. If it is directly in front of you, the edges of the object will gradually move towards the middle as it recedes. Similarly, a large square transparent object will have the rear edges appear 'within' the bounds of the front edges. This is perspective.&lt;/p&gt;
&lt;p&gt;To recreate this in our 2D projection, we need to move points towards the middle of our screen the further away from our 'viewer' they are. Our x &amp;amp; y coordinates are zero'd around the center of the screen (an x &amp;lt; 0 means to the left of the center point), so dividing x &amp;amp; y coordinates by &lt;em&gt;some amount of Z&lt;/em&gt; will move them towards the middle, appearing 'further away'.&lt;/p&gt;
&lt;p&gt;The specific formula we're using is shown below. We take into account the &lt;em&gt;field of view&lt;/em&gt; &amp;mdash;&amp;nbsp;how much of an area the viewer can see &amp;mdash; the &lt;em&gt;viewer distance&lt;/em&gt; and the screen height and width to project onto our &lt;code&gt;framebuf&lt;/code&gt;.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;x' = x * fov / (z + viewer_distance) + screen_width / 2
y' = -y * fov / (z + viewer_distance) + screen_height / 2
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Point3D code&lt;/h3&gt;
&lt;p&gt;The complete code for a single &lt;code&gt;Point3D&lt;/code&gt; is shown below, containing the methods for rotation in all 3 axes, and for projection onto a 2D plane. Each of these methods return a new &lt;code&gt;Point3D&lt;/code&gt; object, allow us to chain multiple transformations and avoid altering the original points we define.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;class Point3D:
    def __init__(self, x = 0, y = 0, z = 0):
        self.x, self.y, self.z = x, y, z

    def rotateX(self, angle):
        """ Rotates this point around the X axis the given number of degrees. """
        rad = angle * math.pi / 180
        cosa = math.cos(rad)
        sina = math.sin(rad)
        y = self.y * cosa - self.z * sina
        z = self.y * sina + self.z * cosa
        return Point3D(self.x, y, z)

    def rotateY(self, angle):
        """ Rotates this point around the Y axis the given number of degrees. """
        rad = angle * math.pi / 180
        cosa = math.cos(rad)
        sina = math.sin(rad)
        z = self.z * cosa - self.x * sina
        x = self.z * sina + self.x * cosa
        return Point3D(x, self.y, z)

    def rotateZ(self, angle):
        """ Rotates this point around the Z axis the given number of degrees. """
        rad = angle * math.pi / 180
        cosa = math.cos(rad)
        sina = math.sin(rad)
        x = self.x * cosa - self.y * sina
        y = self.x * sina + self.y * cosa
        return Point3D(x, y, self.z)

    def project(self, win_width, win_height, fov, viewer_distance):
        """ Transforms this 3D point to 2D using a perspective projection. """
        factor = fov / (viewer_distance + self.z)
        x = self.x * factor + win_width / 2
        y = -self.y * factor + win_height / 2
        return Point3D(x, y, self.z)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="3d-simulation"&gt;3D Simulation&lt;/h2&gt;
&lt;p&gt;We can now create a &lt;em&gt;scene&lt;/em&gt; by arranging Point3D objects in 3-dimensional space. To create a cube, rather than 8 discrete points, we will connect our vertices to their adjacent vertices &lt;em&gt;after&lt;/em&gt; projecting them onto our 2D surface.&lt;/p&gt;
&lt;h3&gt;Vertices&lt;/h3&gt;
&lt;p&gt;The vertices for a cube are shown below. Our cube is centered around 0 in all 3 axes, and rotates around this centre.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;self.vertices = [
    Point3D(-1,1,-1),
    Point3D(1,1,-1),
    Point3D(1,-1,-1),
    Point3D(-1,-1,-1),
    Point3D(-1,1,1),
    Point3D(1,1,1),
    Point3D(1,-1,1),
    Point3D(-1,-1,1)
]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Polygons or Lines&lt;/h3&gt;
&lt;p&gt;As we're drawing a wireframe cube, we actually have a couple of options &amp;mdash;&amp;nbsp;&lt;em&gt;polygons&lt;/em&gt; or &lt;em&gt;lines&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The cube has 6 faces, which means &lt;strong&gt;6 polygons&lt;/strong&gt;. To draw a single polygon requires 4 lines, making a total draw for the wireframe cube with polygons of &lt;strong&gt;24 lines&lt;/strong&gt;. We draw more lines than needed, because each polygon shares sides with 4 others.&lt;/p&gt;
&lt;p&gt;In contrast drawing only the lines that are required, a wireframe of the cube can be drawn using only &lt;strong&gt;12 lines&lt;/strong&gt; &amp;mdash;&amp;nbsp;half as many.&lt;/p&gt;
&lt;p&gt;For a filled cube, polygons would make sense, but here we're going to use the lines only, which we call &lt;em&gt;edges&lt;/em&gt;. This is an array of indices into our &lt;em&gt;vertices&lt;/em&gt; list.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;self.edges  = [
    # Back
    (0, 1),
    (1, 2),
    (2, 3),
    (3, 0),
    # Front
    (5, 4),
    (4, 7),
    (7, 6),
    (6, 5),
    # Front-to-back
    (0, 5),
    (1, 4),
    (2, 7),
    (3, 6),
]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;On each iteration we apply the rotational transformations to each point, then project it onto our 2D surface.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;r = v.rotateX(angleX).rotateY(angleY).rotateZ(angleZ)

# Transform the point from 3D to 2D
p = r.project(*self.projection)

# Put the point in the list of transformed vertices
t.append(p)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then we iterate our list of edges, and retrieve the relevant transformed vertices from our list &lt;code&gt;t&lt;/code&gt;. A line is then drawn between the x, y coordinates of two points making up the edge.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;for e in self.edges:
    display.line(*to_int(t[e[0]].x, t[e[0]].y, t[e[1]].x, t[e[1]].y, 1))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;to_int&lt;/code&gt; is just a simple helper function to convert lists of &lt;code&gt;float&lt;/code&gt; into lists of &lt;code&gt;int&lt;/code&gt; to make updating the OLED display simpler (you can't draw half a pixel).&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def to_int(*args):
    return [int(v) for v in args]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The complete simulation code is given below.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;class Simulation:
    def __init__(self, width=128, height=64, fov=64, distance=4, rotateX=5, rotateY=5, rotateZ=5):

        self.vertices = [
            Point3D(-1, 1,-1),
            Point3D( 1, 1,-1),
            Point3D( 1,-1,-1),
            Point3D(-1,-1,-1),
            Point3D(-1, 1, 1),
            Point3D( 1, 1, 1),
            Point3D( 1,-1, 1),
            Point3D(-1,-1, 1)
        ]

        # Define the edges, the numbers are indices to the vertices above.
        self.edges  = [
            # Back
            (0, 1), (1, 2), (2, 3), (3, 0),
            # Front
            (5, 4), (4, 7), (7, 6), (6, 5),
            # Front-to-back
            (0, 4), (1, 5), (2, 6), (3, 7),
        ]

        # Dimensions
        self.projection = [width, height, fov, distance]

        # Rotational speeds
        self.rotateX = rotateX
        self.rotateY = rotateY
        self.rotateZ = rotateZ

    def run(self):
        #&amp;nbsp;Starting angle (unrotated in any dimension).
        angleX, angleY, angleZ = 0, 0, 0

        while 1:
            t = []
            for v in self.vertices:
                # Rotate the point around X axis, then around Y axis, and finally around Z axis.
                r = v.rotateX(angleX).rotateY(angleY).rotateZ(angleZ)

                # Transform the point from 3D to 2D
                p = r.project(*self.projection)

                # Put the point in the list of transformed vertices.
                t.append(p)

            display.fill(0)

            for e in self.edges:
                display.line(*to_int(t[e[0]].x, t[e[0]].y, t[e[1]].x, t[e[1]].y, 1))

            display.show()

            #&amp;nbsp;Continue the rotation.
            angleX += self.rotateX
            angleY += self.rotateY
            angleZ += self.rotateZ
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="running-a-simulation"&gt;Running a simulation&lt;/h2&gt;
&lt;p&gt;To display our cube we need to create a &lt;code&gt;Simulation&lt;/code&gt; object, and then call &lt;code&gt;.run()&lt;/code&gt; to start it running.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;s = Simulation()
s.run()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Simulation with default parameters" src="https://i.imgur.com/KxAebOa.gif"/&gt;&lt;/p&gt;
&lt;p&gt;You can pass in different values for &lt;code&gt;rotateX&lt;/code&gt;, &lt;code&gt;rotateY&lt;/code&gt;, &lt;code&gt;rotateZ&lt;/code&gt; to alter the speed of rotation. Set a negative value to rotate in reverse.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;s = Simulation()
s.run()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;fov&lt;/code&gt; and &lt;code&gt;distance&lt;/code&gt; parameters are set at sensible values for the 128x64 OLED by default (based on testing). So you don't &lt;em&gt;need&lt;/em&gt; to change these, but you can.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;s = Simulation(fov=32, distance=8)
s.run()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Simulation with default parameters" src="https://i.imgur.com/x4HWO9F.gif"/&gt;&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  The &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; are defined by the display, so you won't want to change these unless you're using a different display output.&lt;/p&gt;</content><category term="python"/><category term="micropython"/><category term="electronics"/></entry></feed>