Paolo Amoroso's Journal

Tech projects, hobby programming, and geeky thoughts of Paolo Amoroso

As an amateur astronomer, Astrophotography mode is one reason I got my old Pixel 4 XL and my current Pixel 7 Pro.

But there’s another essential piece of gear for taking long-exposure photos of star fields or astronomical phenomena, a tripod. The one I bought for the Pixel 4 XL and now use with the Pixel 7 Pro is a Phinistec photo tripod, here with some of the included accessories:

Phinistec photo tripod with accessories: carrying pouch, smartphone adapter, Bluetooth shutter.

I do all my astrophotography from an apartment building in Milan, Italy, where I live. It’s a light-polluted urban area but these days I can’t wander around much.

I observe the sky from the apartment’s small balconies, which have the area of a medium-sized carpet. This constrains the camera holding gear I can use. I wanted a full-height tripod that can extend to at least waist level, not a tabletop tripod, as I can’t use tables or other elevated surfaces to set the photo equipment on.

The Phinistec tripod reaches a maximum height of 125 cm. It’s cheap, compact, and very light. It comes with a smartphone adapter, a Bluetooth remote shutter, a carrying pouch, and a Gopro adapter I don’t need.

Although the product specs mention compatibility only with iOS, the Bluetooth shutter works fine with Android. To pair it with your phone turn on Bluetooth discovery on the device, power up the shutter, and follow the prompts on Android.

The tripod is perfect for Astrophotography mode with the my Pixel. I can quickly set up the tripod and bring it to a balcony.

There’s a minor inconvenience, though. Even at full height, when pointing areas of the sky at high angular altitudes, viewing the phone’s screen is not much practical. I have to uncomfortably crouch or bend behind the screen.

#astronomy #Android

Discuss... Email | Reply

I changed my RSS reader from Feedly to Inoreader.

The affordable Supporter plan I subscribed to is the lowest Inoreader tier but I chose it not because I want something cheap, it's just the plan happens to provide all the features I need. I left behind a lifetime Feedly Pro plan that allows me to use the product indefinitely at no cost.

Importing my feeds and folders from Feedly via the API was seamless and smooth.


I had been using Feedly for ten years since March 13, 2013 when Google announced the shutdown of Google Reader. Over the years Feedly kept adding AI and enterprise features I didn't care about as an individual user. Although the Android app remained inadequate for a long time, Feedly is an okay product despite its frequent outages and performance issues.

I eventually grew dissatisfied with the reliability of Feedly and the company's drift away from the core RSS experience, which made me want to look for something new. A shameful new Feedly feature set the right timing to research a different newsreader.

I want a cloud RSS reader that synchronizes across platforms, particularly the web and Android. I had long heard great things about Inoreader, checked it out, loved it, and purchased a subscription.

First impressions

Inoreader has a clean and pleasant design with the right similarities to Feedly.

The general layout and the choice of keystrokes, mostly the same as Feedly, make the application immediately familiar and usable. Article presentation and formatting are better than Feedly's, which often doesn't render correctly certain page elements. For example, code blocks stand out nicely in Inoreader.

The web client is consistently fast and responsive. So far I haven't experienced the typical slowdowns and outages of Feedly.

What surprised me of Inoreader is text search actually works and instantaneously delivers accurate results. I hadn't seen these levels of search accuracy and performance in RSS readers since Google Reader. Compare this with Feedly's search, which takes several seconds and misses results I'm sure are there. Feedly search is an oxymoron.

A great Inoreader feature missing from Feedly is the ability to load the full text of the articles in partial feeds, it takes just a keypress. This alone is worth Inoreader.

The Chrome extension makes Inoreader double as a read later tool. My primary read later tool is Google Keep but I'm liking Inoreader's smooth workflow for saving and reading web pages.

The Android app has all the key features of the desktop version. For example, swiping down an article in a partial feed downloads the full text. Also, sharing to Inoreader a link to a website with an RSS feed prompts to subscribe to the feed or save the page for later. It's a no brainer, but the Feedly app doesn't support it.

Inoreader is making me rediscover RSS. Reading sessions felt like routine with Feedly, but Inoreader makes me eagerly anticipate sitting down and catching up with my favorite sources.


Discuss... Email | Reply

I'm enhancing Stringscope with a permanent command menu and a prompt area. The menu, an item of which holds a submenu, attaches to the right side of the main window, the prompt area to the top of the main window above the title bar. This is what the main window looks like now:

Window of the Interlisp program Stringscope with an early version of the command menu.

This way of arranging menus and secondary windows by attaching them to a main window is typical of Interlisp programs with a GUI. The system supports this design with functions like the ones I used, CREATEMENUEDWINDOW to create and attach a menu and GETPROMPTWINDOW for doing the same with a prompt window.

The menu comprises these initial items and subitems:

  • Get: reads the strings of a new file
  • Find: searches for strings matching a specific text
  • Sort: sorts the strings in the following order
    • Ascending
    • Descending
  • Set threshold: changes the minimum length of strings
  • Reset: redisplays the strings read from the current file

The prompt window is an area for displaying status messages and receiving user input such as the name of a new file to read.

So far the Stringscope code sets up the menu and the prompt window. The menu handling function, however, is just a stub that prints to the main prompt window the selected menu item.

The next step will be to implement the commands the menu calls.

#stringscope #lisp

Discuss... Email | Reply

Although I despise cutouts and holes in smartphone screens I bought a Google Pixel 7 Pro Obsidian Black to replace my old Pixel 4 XL.

Google Pixel 7 Pro product box

I hoped the Google Store would send me a discount but it never happened, I should have taken advantage of the Black Friday promotion just after the device's release. Anyway, I've been using the Pixel 7 Pro for the past week or so and these are my initial impressions.


For my daily driver smartphone I've always wanted a high-specced, supported, Google-made flagship featuring the Google experience, so the Pixel 7 Pro was an obvious replacement for my end of life Pixel 4 XL.

I could have waited a few months for the upcoming Pixel 8 Pro but the early rumors hinted at a smaller screen. My ageing eyesight strongly prefers large screens, which made a difference in favor of the Pixel 7 Pro.

Another reason not to delay the purchase is that, much as I despise screen cutouts and holes, this design fad is likely here to stay for at least one product generation or two. Getting the Pixel 7 Pro minimizes screen defacement while letting me weather the storm and wait for more tasteful design trends.

Finally, I wanted the Pixel 7 Pro because I was eager to try astrophotography with better optical zoom, 5X versus 2X of the Pixel 4 XL.


After several days the screen hole isn't bothering me as much as I expected. A related feature the reviewers of the Pixel 7 Pro frowned upon but I don't mind is the curved screen. It's not much noticeable to me and the thin bezel is enough to prevent most of the inadvertent screen touches.

The cheap, plastic touch of the screen is unusual but I guess this is the kind of tactile experience the material of curved panels is supposed to give. Still, it contrasts with the thicker glass feel of the Pixel 4 XL screen.

Speaking of the screen, the integrated fingerprint reader is okay but not as accurate and fast as I hoped. I'll miss the lightning fast and accurate screen unlock of the Pixel 4 XL. I think I won't turn on face unlock on the Pixel 7 Pro as it's not supported for biometric authentication, so it may not help much.

Another love or hate design feature is the sensor pod. So far I'm in the don't mind camp.

While optical zoom is important to me, I'm liking the 0.5 X wide angle lens too which, until last year, I didn't have a use for. Then I did a dream trip to the Space Coast and a wide angle lens would have come in handy for photographing space technology subjects.

In ordinary use the Pixel 7 Pro doesn't seem much faster than the Pixel 4 XL, but the former makes a difference for resource-intensive apps and runs them more smoothly, with less lag and jankiness.


On the Pixel 4 XL I was already using Android 13, the same version currently on the Pixel 7 Pro, so there are no significant differences.

The experience of setting up the Pixel 7 Pro, configuring the apps, and performing system updates was similar too. It took 6-8 hours most of which spent migrating banking and credential management apps, each with its own complicated, idiosyncratic, and poorly documented migration procedure.

The Android system updates were excruciatingly slow when setting up the Pixel 7 Pro, most likely because they were large and highly I/O bound.

Aside from these issues, I like Google's Android skin.


Discuss... Email | Reply

I moved the Stringscope project infrastructure from a GitHub gist to a full repo. The gist is now unmaintained and links to the repo.

Announcing my new Interlisp project Braincons I motivated setting up its infrastructure as a repo with a number of reasons boiling down to convenience. Code is still readble despite the formatting Interlisp encodes, source files no longer need conversion, and a full repo is more flexible. I moved Stringscope to a repo for the same reasons.

#stringscope #lisp

Discuss... Email | Reply

After Stringscope I've been working on Braincons, an Interlisp implementation of the esoteric programming language Brainfuck. The project goals are to further learn the Medley Interlisp environment I'm developing Braincons with, as well as experiment with the compilation and implementation of a very simple language.

Braincons data structures inspected in Medley Interlisp.


Braincons is in an early stage of development and largely incomplete, so I don't have much to show yet.

There's no program, output window, or Executive command to run. But preliminary versions of some of the main functions and data structures are in place, which can be called and inspected from Lisp.

So far the system can only parse Brainfuck program sources, check for errors, and produce an intermediate representation a virtual machine will eventually execute.


To try out Braincons download the file BRAINCONS from the project repo, copy it to a file system your Medley Interlisp installation has access to, and evaluate the following expression from the Lisp Executive:



At this point you can call the parser function BRC.PARSE and pass it a Brainfuck source string:


BRC.PARSE returns the intermidiate representation of the source, then assigned to the variable PARSED.PROGRAM which you can inspect as a BRC.PROGRAM record:


The inspected record and its fields should look similar to the above screenshot. This is all I have for now.


I initially focused on designing the main data structures and functions upon which to build the rest of Braincons.

First, I assigned descriptive mnemonics to the Brainfuck commands as in this table:

Command Mnemonic
, IN
[ JZ

Braincons represents the source of a Brainfuck program as a string. Two data structures implemented as Interlisp records hold the intermediate representation the parser produces.

The record BRC.PROGRAM consists of an array of instructions, the index of the last instruction in the array, a list of any branch instructions in the source, and a list of any errors encountered while parsing the program. Each array entry holds a Lisp symbol of the mnemonic of the corresponding instruction, except for the branch instructions JZ (jump if zero) and JNZ (jump if not zero).

Branches are records with fields for the mnemonic, the source address in the instruction array, the destination address, and the index of the branch command in the source string.

So far Braincons has only one entry point, function BRC.PARSE, which takes a Brainfuck source string as the only argument and returns the parsed intermediate representation record BRC.PROGRAM. BRC.PARSE calls directly or indirectly some helper functions to parse individual instructions, create branch records, and check for errors.


Along with ASCII characters, Interlisp source files contain special characters such as and control codes that encode font changes. Visualizing these files in editors and tools that expect ASCII thus shows spurious characters.

To work around this I didn't directly share the raw code of Stringscope. Instead I stripped the control and formatting codes from the source exported from Medley Interlisp, replaced the special characters with more readable versions, and published it to a GitHub gist. The code in the gist is nicely formatted for reading but Medley Interlisp can't load it as is, so I have to provide the raw source separately.

Since Interlisp programmers are used to the formatting, and cleaning it up involves additional steps, I decided to store the raw source of Braincons in a GitHub repo. This also provides the additional flexibility of a full repo.

Further development

I'm actively changing and enhancing the code of the only working feature, the parser. Although the basic design of the main data structures is stabilizing, everything else will likely need a lot of refactoring and cleanup.

Next, I'll implement the virtual machine that processes and executes the parsed program.

In the long term I'd like to build a minimal Brainfuck development environment on top of the parsing and program execution functionality. The environment will comprise a source editor based on TEdit and an interactive visualization with controls for loading and starting programs, showing the state of the virtual machine and the executing program, reading user input, and displaying the output.

#braincons #lisp

Discuss... Email | Reply

Reporting on the implementation of new commands for Stringscope I noted my confusion on why File Manager building commands such as (CLEANUP 'STRINGSCOPE) sometimes don't write to a file the compiled Lisp code.

It turns out this occurred when loading Stringscope with FILESLOAD, which loads a compiled file if available. So, even after modifying the Lisp source, the File Manager somehow skipped the compilation step as it assumed the file was up to date. Loading Stringscope with LOAD instead, which pulls the source, is usually enough to make CLEANUP write the compiled file.

It's a step forward. But I still don't understand why (CLEANUP 'STRINGSCOPE) or (MAKEFILE 'STRINGSCOPE 'C) don't always compile to a file when the source is edited in memory.

#stringscope #lisp

Discuss... Email | Reply

One year ago today I published the first post of this blog hosted at, ending a long quest for a better publishing platform.

I had grown frustrated with the clunkyness and limitations of Blogger, where I settled for the prior threee years. So I set out to research a lightweight blogging platform with good support for technical writing that could replace Blogger, and finally went with One year and 225 posts later, I'd say was the right choice despite a major missing feature, post previews. I've never been so productive, neither with Blogger nor any other publishing platform.

Another fortunate decision, intentionally against the conventional wisdom of SEO, was to publish a personal blog that focuses on me rather than remaining confined in a specific niche. This let me write about new interests or experiences, adapting to the wandering and widening of my interests and activities.

The first year on was eventful. Two of my posts went viral on Hacker News, one on why I use a Chromebox and the other on my encounter with Medley Interlisp, and brought tens of thousands of views.

That was exciting. But over the past year I also joined the Fediverse, and sharing some of my blog posts there generated more interesting, meaninfgul, and lasting interactions than the drive-by traffic of viral posts.

Aside from the rewards and validation of such success metrics, an unexpected benefit of the first year of blogging at has been writing for an audience of one: me.

I'm using the blog more and more as a sort of lab journal for my hobby programming and tech projects. Since I work on a number of projects, and put them aside for some time before resuming, the blog has proven invaluable in getting back up to speed with projects or track useful resources by reviewing and referencing past posts.

So, even if nobody read my blog, I would still gain the most benefit from this personal space and online archive.


Discuss... Email | Reply

In addition to blogging about Medley Interlisp I also post about it on my Mastodon profile with the hashtag #interlisp. I share links, screenshots, project updates, videos, and other short content.


Discuss... Email | Reply

I continued working on Stringscope, the string listing tool I'm developing with Medley Interlisp. I added the function STRINGS to print the strings to the primary output stream rather than in a new window, as well as an Executive command that does the same.

To reflect this work I updated the Stringscope code and documentation hosted on a GitHub gist, describing also the function EXTRACT.STRINGS as part of the interface. The code in the gist is intended for publication and can't be loaded as is into Interlisp.


In addition to the same arguments as STRINGSCOPE, i.e. a file name FILENAME and the minimum length MIN.LEN strings must have, STRINGS accepts a third optional boolean argument NEWWIN. By default NEWWIN is NIL and makes STRINGS print to the primary output, otherwise the function opens a new window and works exactly like STRINGSCOPE. In fact, STRINGSCOPE is now just a wrapper that calls STRINGS with NEWWIN set to T.

STRINGSCOPE and STRINGS return a window if the functions open it otherwise the primary output stream. In case the input file can not be opened or contains no strings, the functions return NIL and print a warning to the prompt window.

Interlisp functions that create or manipulate a window usually return the window itself as their value, so I adopted a similar convention.


An early version of STRINGS duplicated a lot of the code of STRINGSCOPE for opening the input stream, checking for errors, evaluating some forms with the stream bound to a variable, and closing the stream. I factored that boilerplate into the macro WITH.INPUT.FILE:

                           &BODY BODY &AUX (RESULT (GENSYM))
                           (VALUE (GENSYM)))

         (* Opens an input stream to FILE and evaluates the forms in BODY with the stream 
         bound to STREAM. Returns the value of the last form in BODY, or NIL if FILE can 
         not be opened.)

          (if ,RESULT
              then (SETQ ,STREAM (CAR ,RESULT))
                   (SETQ ,VALUE (PROGN ,@BODY))
                   (CLOSEF ,STREAM)
                   (RETURN ,VALUE)
            else (RETURN NIL))))

The Executive command STRINGS

The reason I implemented a function like STRINGS that prints the output to the console is I wanted to try out creating an Executive command.

Also known as a listener in other Lisp environments, an Executive is an Interlisp window that provides a read-eval-print loop. In addition to Lisp expressions, an Executive accepts commands with a non-parenthesized syntax such as DIR (the equivalent of DIR on MS-DOS and ls on Unix) or CONN directory (cd directory on MS-DOS and Unix).

So I defined the Stringscope Executive command STRINGS that works exactly like the function STRINGS but takes only the file name and the minimum length arguments:


The macro DEFCOMMAND defines an Executive command and is really easy to use, here's the definition of STRINGS:


The STRINGS command is just a wrapper that calls the function STRINGS with appropriate arguments.

Compiling Stringscope

I initially run Stringscope compiled but it was time to start using the File Manager commands for compiling the code, (MAKEFILE 'STRINGSCOPE 'C) and (CLEANUP 'STRINGSCOPE). However, these commands apparently compile only in memory and don't write a compiled file like (TCOMPL 'STRINGSCOPE), which I have to call manually.

The File Manager is an Interlisp subsystem conceptually similar to the Unix tool Make. It's a collection of tools to notice, keep track of, and write to files the code changes to a Lisp system under development.

Another confusing aspect of compilation is when I load Stringscope with (FILESLOAD STRINGSCOPE), I get a warning I don't understand concerning a comment in the macro WITH.INPUT.FILE: Warning: Possible comment not stripped. I actually see the compiler couldn't strip a comment from the compiled code to save space, but not why.

Further development

Some of these new features were on my initial roadmap, others emerged while coding and using Stringscope.

Since this is an exploration and learning project, going forward I'll go with the flow and implement what I need or find interesting, and consider the initial roadmap more like a source of ideas than an action plan. Which is the kind of exploratory development Lisp supports and makes enjoyable.

#stringscope #lisp

Discuss... Email | Reply

Enter your email to subscribe to updates.