Skip to content

Instantly share code, notes, and snippets.

@tschak909
Created December 30, 2022 18:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tschak909/fec281dd3459777422e4eec88076a4be to your computer and use it in GitHub Desktop.
Save tschak909/fec281dd3459777422e4eec88076a4be to your computer and use it in GitHub Desktop.

FujiNet Programming Series: Using the HTTP Protocol Adapter and JSON Parser

Abstract

The N: device exposes a wealth of protocol adapters that can be used, such as HTTP and HTTPS. Using these protocol adapters alone give you access to a wealth of information stored on the Web, but you can also attach them to a JSON parser (an XML parser is also available) to have the FujiNet quickly parse, and query to find any needed information, to return to the Atari. This article will show how to use the HTTP protocol adapter, and how to pair it with the JSON parser, to read the latest Mastodon post.

A Note About Assumptions

This article assumes that you have read the previous article in this series, showing how the N: device handler is set up and used to modify an existing BASIC game, as concepts learned there will apply here, as well.

Requirements

You'll need the following:

  • An Atari 8-bit computer
  • A configured FujiNet. (LATEST FIRMWARE!)
  • A disk containing your favorite DOS and the N: handler.
  • A working Wi-Fi connection
  • BASIC

Using the HTTP device to read a URL from the Web

The N: device, at its simplest, can be used to read the data from a URL on the web. Once opened, any reads that occur will fetch the data from the URL.

For example, to read the text of the GPL license version 3.0 from gnu.org:

 0 DIM A$(128)
10 OPEN #1,4,2,"N:HTTPS://www.gnu.org/licenses/gpl-3.0.txt":REM 2 = TRANSLATE LF TO EOL
20 TRAP 60
30 INPUT #1,A$
40 ? A$
50 GOTO 30
60 CLOSE #1
70 END 

This program will read each line of the GPL into the variable A$ and display it. The TRAP handles the ERROR- 136, which will happen at the end of the document, to close the channel, and end the program.

Line 0 gives the size of the A$ string variable.

Line 10 opens the connection to the gnu.org web server. Since we are reading the document, we use an AUX1 value of 4. Also since we know that the document's lines end in a single line feed (LF) character, we use an AUX2 value of 2 to convert any of those characters found to ATASCII end-of-line (EOL) characters.

Line 20 sets an error trap for line 60, which closes the file. This will inevitably happen when the end of the document is reached.

Line 30 Gets a single line of text from the document into the A$ variable.

Line 40 prints the previously gathered line of text in A$ to the screen.

Line 50 loops back to line 30 to get the next line.

Line 60 is reached when the document ends. The channel is closed.

Line 70 ends the program.

If you prefer to get each character one at a time, you can also do this by replacing line 30 with:

30 GET #1,A

And replacing line 40 with:

40 ? CHR$(A);

This functionality alone allows one to easily get data from any Web endpoint, but the next section will show how to use the built-in JSON parser to show the latest post from the Mastodon social network, which exposes its data via JSON.

A Quick Introduction to JSON

To quickly summarize, JSON is short for JavaScript Object Notation, and was devised as a very simple method of encoding complex structures of information in a way that could be easily parsed. Coincidentally, the format for JSON is the exact format used by JavaScript to statically define an object, an array, a string, a number, a boolean (true or false), or a null (empty) value.

To illustrate, here is an example fragment of JSON that comes from the following URL: https://api.zippopotam.us/us/90210

{
   "post code":"90210",
   "country":"United States",
   "country abbreviation":"US",
   "places":[
      {
         "place name":"Beverly Hills",
         "longitude":"-118.4065",
         "state":"California",
         "state abbreviation":"CA",
         "latitude":"34.0901"
      }
   ]

The {} braces indicate the delimiter of an object. An object is a collection of keys and their values, a key being in this case, "country", and its value being "United States." There can be as many keys in an object, and the values can be any of the above mentioned data types, even an object. Quotes are used in both the keys and values so that white space can be included in either the key or value.

[] brackets indicate the delimiter of an array. An array can have any number of objects, and allows you to group objects together. In the above example, it would be used to indicate multiple places. Arrays, like objects, can contain any of the above data types.

A nice benefit of JSON is that it can be very human readable, while still remaining very concise, contrasted with XML.

How do we get the Atari and the Fujinet to process JSON?

Processing JSON with the Fujinet and Atari

For a real world example, we'll use the FujiNet to parse JSON data coming in from the Mastodon federated social network (federated means that anyone can run a Mastodon server, even ABBUC!, all the posts are passed to other servers and a large federated network is formed), and display the latest public post. You can see the latest post by using the following URL: HTTPS://oldbytes.space/api/v1/timelines/public?limit=1. We add limit=1 to ensure that we only get the latest post, and less data for the FujiNet to consume, although it has no problem with the default 20 posts.

The result looks like this:

[
   {
      "id":"108375000803232990",
      "created_at":"2022-05-27T17:06:06.000Z",
      "in_reply_to_id":null,
      "in_reply_to_account_id":null,
      "sensitive":false,
      "spoiler_text":"",
      "visibility":"public",
      "language":"en",
      "uri":"https://botsin.space/users/hackaday/statuses/108374993548508083",
      "url":"https://botsin.space/@hackaday/108374993548508083",
      "replies_count":0,
      "reblogs_count":0,
      "favourites_count":0,
      "edited_at":null,
      "favourited":false,
      "reblogged":false,
      "muted":false,
      "bookmarked":false,
      "local_only":null,
      "content":"\u003cp\u003eThis week's Hack Chat was a journey into sound with Frank Olson. \u003ca href=\"https://hackaday.com/2022/05/27/vintage-pro-audio-hack-chat-gets-in-the-groove/\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"ellipsis\"\u003ehackaday.com/2022/05/27/vintag\u003c/span\u003e\u003cspan class=\"invisible\"\u003ee-pro-audio-hack-chat-gets-in-the-groove/\u003c/span\u003e\u003c/a\u003e\u003c/p\u003e\u003cp\u003eOriginal tweet : \u003ca href=\"https://twitter.com/hackaday/status/1530232463074725888\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"ellipsis\"\u003etwitter.com/hackaday/status/15\u003c/span\u003e\u003cspan class=\"invisible\"\u003e30232463074725888\u003c/span\u003e\u003c/a\u003e\u003c/p\u003e",
      "reblog":null,
      "account":{
         "id":"31819",
         "username":"hackaday",
         "acct":"hackaday@botsin.space",
         "display_name":"hackaday",
         "locked":false,
         "bot":true,
         "discoverable":false,
         "group":false,
         "created_at":"2018-04-11T00:00:00.000Z",
         "note":"\u003cp\u003e\u003cspan class=\"h-card\"\u003e\u003ca href=\"https://botsin.space/@hackaday\" class=\"u-url mention\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"\u003e@\u003cspan\u003ehackaday\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e twitter feed\u003cbr\u003e[Will be handed over to twitter account owner upon request]\u003c/p\u003e",
         "url":"https://botsin.space/@hackaday",
         "avatar":"https://assets.oldbytes.space/assets.oldbytes.space/accounts/avatars/000/031/819/original/6cfd655da843a3af.png",
         "avatar_static":"https://assets.oldbytes.space/assets.oldbytes.space/accounts/avatars/000/031/819/original/6cfd655da843a3af.png",
         "header":"https://assets.oldbytes.space/assets.oldbytes.space/cache/accounts/headers/000/031/819/original/e33b8cd33adefd0f.jpg",
         "header_static":"https://assets.oldbytes.space/assets.oldbytes.space/cache/accounts/headers/000/031/819/original/e33b8cd33adefd0f.jpg",
         "followers_count":2784,
         "following_count":1,
         "statuses_count":20445,
         "last_status_at":"2022-05-27",
         "emojis":[
            
         ],
         "fields":[
            {
               "name":"Owner",
               "value":"\u003cspan class=\"h-card\"\u003e\u003ca href=\"https://mastodon.social/@jeancf\" class=\"u-url mention\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"\u003e@\u003cspan\u003ejeancf@mastodon.social\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e",
               "verified_at":null
            },
            {
               "name":"Powered by",
               "value":"\u003ca href=\"https://gitlab.com/jeancf/twoot\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003egitlab.com/jeancf/twoot\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e",
               "verified_at":null
            }
         ]
      },
      "media_attachments":[
         {
            "id":"108375000778536938",
            "type":"image",
            "url":"https://assets.oldbytes.space/assets.oldbytes.space/cache/media_attachments/files/108/375/000/778/536/938/original/71d87d5ecddef5f0.jpg",
            "preview_url":"https://assets.oldbytes.space/assets.oldbytes.space/cache/media_attachments/files/108/375/000/778/536/938/small/71d87d5ecddef5f0.jpg",
            "remote_url":"https://files.botsin.space/media_attachments/files/108/374/993/508/532/269/original/bb4c6ed0f8286d7e.jpg",
            "preview_remote_url":null,
            "text_url":null,
            "meta":{
               "original":{
                  "width":569,
                  "height":320,
                  "size":"569x320",
                  "aspect":1.778125
               },
               "small":{
                  "width":533,
                  "height":300,
                  "size":"533x300",
                  "aspect":1.7766666666666666
               }
            },
            "description":null,
            "blurhash":"UBB:g70L01~W?b9ZM{?bE4jYIUx]-:WBM{az"
         }
      ],
      "mentions":[
         
      ],
      "tags":[
         
      ],
      "emojis":[
         
      ],
      "card":{
         "url":"https://hackaday.com/2022/05/27/vintage-pro-audio-hack-chat-gets-in-the-groove/",
         "title":"Vintage Pro Audio Hack Chat Gets In The Groove",
         "description":"Despite the fact that we’ve been doing them for years now, it’s still hard to predict how a Hack Chat will go. There’s no question it will be an hour of interesting discussion of …",
         "type":"link",
         "author_name":"",
         "author_url":"",
         "provider_name":"Hackaday",
         "provider_url":"",
         "html":"",
         "width":400,
         "height":225,
         "image":"https://assets.oldbytes.space/assets.oldbytes.space/cache/preview_cards/images/001/633/519/original/e1ecdcc89cbeadc1.jpeg",
         "embed_url":"",
         "blurhash":"UBB|1v0L01~W?b9ZM{?bE4jYIUx]-:WBM{az"
      },
      "poll":null
   }
]

As you can see, it's a lot of information, and would easily overwhelm the memory space of the Atari computer, before it even has a chance to process any of it! By letting the FujiNet process the data, we can use its larger memory space and processing power to quickly pick out the pieces of information we want to use, and send them over the channel to the Atari.

With all this in mind, we start by defining a variable to hold the received data:

0 DIM A$(128)
1 TRAP 100

Once the variable has been defined, we can open the channel to a Mastodon server. Since we are parsing the data on the FujiNet, we don't have to care about the format of the line endings, and can simply ask for no translation.

10 OPEN #1,12,0,"N:HTTPS://oldbytes.space/api/v1/timelines/public?limit=1"

Now that we're connected, we have to switch the mode for the network channel to JSON mode, so that we can have access to the JSON parser. This is done with command # 252 ($FC), and is accomplished with the following XIO:

20 XIO 252,#1,12,1

The AUX1 must match what was used for open, and the AUX2 of 1 means we want to use the JSON parser.

The data is parsed with the following XIO command 'P':

30 XIO ASC("P"),#1,12,0,"N:"

At this point, the data has been read by the FujiNet, and is sitting in its memory. We can then query all of that data for the pieces of information we want. In this case, we want to show:

  • The poster's name
  • The timestamp of the post
  • The content of the post.

So we need to use a JSON pointer for each of these pieces of information.

Getting to the point, with JSON Pointers

To allow for quick traversal of JSON documents, the JSON Pointer RFC #6901 was created. It can be read in its entirety here: https://datatracker.ietf.org/doc/html/rfc6901, but basically, to get the pieces of information we want, we can use the following pointers:

  • /0/account/display_name
  • /0/created_at
  • /0/content

And to get and display each of these pieces of information, we just need to use the 'Q' XIO command on N:, with each of these query strings. Displaying the first two query results are easy enough:

40 XIO ASC("Q"),#1,12,0,"N:/0/account/display_name"
50 INPUT #1,A$:? A$
60 XIO ASC("Q"),#1,12,0,"N:/0/created_at"
70 INPUT #1,A$:? A$

Handling large values

Mastodon posts can be up to 1000 characters in length, and thus almost eight times larger than what the Atari CIO reasonably expects. This means that we will have to handle the /0/content value one byte at a time. Using GET, this is easy enough:

80 XIO ASC("Q"),#1,12,0,"N:/0/content"
90 GET #1,A:? CHR$(A);:GOTO 90

We can finally close the I/O connection, and end the program.

100 CLOSE #1:END

Running this program produces a result like Figure 1.

APIs Are Everywhere

Since the #FujiNet can handle both HTTPS connections and has a built-in JSON parser, this opens up a wide world of Web APIs that are available to gather from. You can see a large list of example ones at: https://mixedanalytics.com/blog/list-actually-free-open-no-auth-needed-apis/ so feel free to experimnent with any bit of JSON you find on the Web!

Other Experiments

Try negotiating an OAuth connection. See if you can see the contents of your Google Drive or GMail box. There are lots of possibilities, and as we all try to talk to different end-points, we'll find things that need to be added to FujiNet's code, let's all try to make it possible to use our Atari's on the modern web!

Until next time, Have fun!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment