I didn’t publicize the C version of my Atari 8 bit library when I released it, except in a small-ish community. The current version is 1.21. The C version of the library includes some things the Action! version does not. The C version has the following features:
Text windowing
Window gadgets
Menu controls
FujiNet bindings
FujiNet Base64 and Hash bindings
APE Time support
The library can be downloaded from my GitHub site where the Action! version of the library is also hosted. Another Atari enthusiast also converted the library to Pascal, and added a few functions. The Pascal code is hosted on the GitHub page as well.
Rather than post any of it here, I’ll point you at the GitHub page for all versions of the library.
I’ve been working on a project where I need to store bytes and integers in a file and read them back in later. The bytes were easy enough, the integers were easy too, once I found the best way. I’m only talking about words consisting of two bytes.
The first method I tried was using fprintf() and fscanf(). While this works, fscanf() needs to know how many bytes to read or a terminator. This is also wasteful, as the number is saved in ASCII form. So, a 5 digit number which takes 2 bytes of memory, ends up as 5 bytes plus a terminator once saved with fprintf().
The next method I tried was calculating the high (most significant) and low (least significant) bytes, then storing them as individual bytes using fputc(). This worked well using fgetc() to retrieve the values. The method was efficient storage wise, but came at the cost of mathmatical operations on the save and read.
The last method I tried, which turns out to be the best, is using fwrite() and fread(). In the past I primarily related these to writing and reading arrays of bytes, such as character strings, or numeric arrays. It was perhaps some knowledge I lost over the years, and just re-learned. I discovered using this method its easy to store and retrieve an integer in the storage efficient 2 byte form.
I wrote a small program to set a byte and integer, display them in various forms, write them to file, then read them back into new variables, and finally display them back in the same forms. This is done on the Atari 8 bit using CC65. Compilation instructions are in the header notes.
FujiNet is a fantastic device for the Atari 8 bit line of computers which provides WiFi, and SD card storage among other things, all over SIO. One of the functions provided is APETIME support, a protocol developed by AtariMax as part of their Atari Peripheral Emulator (APE).
In the last post I demonstrated my BASIC and Action! versions of a program to get the time from the FujiNet device using APETIME. The procedure is documented here (https://github.com/FujiNetWIFI/fujinet-platformio/wiki/Accessing-the-Real-Time-Clock). At the time of this writing there was no complete C example, only an example of the call.
After completing the conversion of my Action! library to C, and starting a new project where I needed to get the time from FujiNet from C, I thought it would be a good time to create a version of the FujiNet time program I had written in BASIC and Action!. This post details my solution. I wrote the routine into a function which can be called from the main program at any point. It only requires passing a pointer to a 6 byte array.
To call the SIO routine, I used inline assembly to execute the jump.
As with my Action! source, the routine that gets the time from FujiNet is the FNGTime() function (FujiNet Get Time). It first sets up the SIO call by assigning appropriate values into the DCB (Device Control Block) memory location. Using CC65 v2.19+ libraries allows setting the DCB via equates using an OS structure as defined in “atari.h” include file, with no need for the Poke function. It sets the buffer location (DBUF), to the address of the byte array passed in.
Last it calls the SIO vector using the SIOV function.
The main routine sets aside 6 bytes of storage, calls the FNGTime() function with the address of the storage array, then prints the result in a friendly fashion.
// ------------------------------------------------------------
// Program: fujitime.c
// Desc...: Gets date/time from FujiNet
// Author.: Ripdubski
// Date...: 2023.02.09
// Notes..: cl65 -v [-O] -t atarixl fujitime.c -o fujitime.xex
// ------------------------------------------------------------
// Pull in include files
#include <stdio.h>
#include <atari.h>
#include <conio.h>
#include <unistd.h>
// -----------------------------------
// Proc..: void SIOV(void)
// Desc..: OS SIO Vector
// -----------------------------------
void SIOV(void)
{
__asm__ ("JSR $E459");
}
// -----------------------------------
// Proc..: void FNGTime(char *bA)
// Desc..: Get date/time from FujiNet
// via APETIME protocol
// Return: 6 bytes DMYHMS into bA
// -----------------------------------
void FNGTime(char *bA)
{
// Setup SIO call using byte array address
// by putting values into the DCB.
// APETime=Device 69 ($45), Unit 1
// Time command=147 ($93)
// Get 6 bytes, timeout just over 15s
OS.dcb.ddevic = 0x45;
OS.dcb.dunit = 0x01;
OS.dcb.dcomnd = 0x93;
OS.dcb.dstats = 0x40;
OS.dcb.dbuf = (void *) bA;
OS.dcb.dtimlo = 15;
OS.dcb.dunuse = 0;
OS.dcb.dbyt = (unsigned int) 6;
// Call SIO
SIOV();
}
// -----------------------------------
// Main Routine
// -----------------------------------
void main(void)
{
// Storage for 6 bytes preset to 0
char bDT[7]={ 0, 0, 0, 0, 0, 0 };
// Call time routine with the storage array
FNGTime(bDT);
// What time is it?
printf("\nDate: 20%d.%d.%d\n", bDT[2], bDT[1], bDT[0]);
printf("Time: %d:%d:%d\n", bDT[3], bDT[4], bDT[5]);
// Wait for a keystroke
printf("Press any key to continue...\n");
while (! kbhit()) {};
}
I’m going to include the FNGTime() function in my C library which was converted from my Action! library in 2022. This will happen in a future release.
All the gadgets up until now have been mildly user interactive. In this post I finally tackle the biggest, and likely most useful, gadget – GInput(). This is the string input routine that allows type restricted (numeric, alphabetic, alpha-numeric, or any text) string input. The string editing size can be smaller than the string itself and the string will scroll left or right as the cursor is moved. There are some special keys tossed in to get to start of the string, end of the string, clearing the string, inserting and deleting characters. At the time of this writing, the C version hasn’t been fully tested for the last three cases mentioned, but will be by the time the library is published.
The code for GInput() is quite large in relation to the other gadgets, but it does quite a bit.
Here is a demo program showing how the gadget is called:
// ------------------------------------------------------------
// Program: ginput.c
// Desc...: A8 Library Input Test
// Author.: Ripdubski
// Date...: 202208
// Notes..: cl65 -v [-O] -t atarixl ginput.c -o ginput.xex
// ------------------------------------------------------------
// Pull in include files
#include <stdio.h>
#include <conio.h>
#include <unistd.h>
#include <string.h>
#include <atari.h>
#include "a8defines.h"
#include "a8defwin.h"
#include "a8libmisc.c"
#include "a8libstr.c"
#include "a8libwin.c"
#include "a8libgadg.c"
#include "a8libmenu.c"
// ------------------------------------------------------------
// Func...: void DoInput(void)
// Desc...: Displays input demo
// ------------------------------------------------------------
void DoInput(void)
{
byte bW;
unsigned char cA[41], cB[41], cC[41], cD[41];
// Assign default string values
strcpy(cA, "-100.00 ");
strcpy(cB, "This string has something to edit in it!");
strcpy(cC, " ");
sprintf(cD, "%cAny character string!%c ", CHBALL, CHBALL);
// Open window & draw form
bW = WOpen(2, 6, 36, 10, WOFF);
WOrn(bW, WPTOP, WPLFT, "Input Test");
WPrint(bW, 1, 1, WOFF, "Data Fields");
WPrint(bW, 2, 3, WOFF, "Numer:");
WPrint(bW, 2, 4, WOFF, "Alpha:");
WPrint(bW, 2, 5, WOFF, "AlNum:");
WPrint(bW, 2, 6, WOFF, "Any..:");
// Display fields as is
// WPrint will truncate what doesn't fit (27 chars for this window).
WPrint(bW, 8, 3, WOFF, cA);
WPrint(bW, 8, 4, WOFF, cB);
WPrint(bW, 8, 5, WOFF, cC);
WPrint(bW, 8, 6, WOFF, cD);
// Input each in succession
// 27 is the maximum width to display of the 40 lengths
GInput(bW, 8, 3, GNUMER, 27, cA);
GInput(bW, 8, 4, GALPHA, 27, cB);
GInput(bW, 8, 5, GALNUM, 27, cC);
GInput(bW, 8, 6, GANY, 27, cD);
// Wait for a key
WPrint(bW, WPCNT, 8, WOFF, "press any key");
WaitKCX(WOFF);
// Close window
WClose(bW);
}
// ------------------------------------------------------------
// Func...: void main(void)
// Desc...: Main routine
// ------------------------------------------------------------
void main(void)
{
// Variables
byte bW1, bW2, bC, bD;
unsigned char *pcM[9] =
{ " Alert ", " Progress ", " Buttons ", " Checkboxes ", " Radio Button ", " Spinner ", " Input ", " Quit " };
// Setup screen
WInit();
WBack(14);
// Open windows
bW1 = WOpen(0, 0, 40, 3, WON);
WPrint(bW1, WPCNT, 1, WOFF, "CC65 A8 Library Test");
bW2 = WOpen(12, 6, 16, 12, WOFF);
WOrn(bW2, WPTOP, WPCNT, "Menu");
// Set done to false
bD = FALSE;
// Loop until done (Quit selected)
while (! bD) {
// Call menu
bC = MenuV(bW2, 1, 2, WON, 1, 8, pcM);
// Process choice
switch (bC)
{
case 1: GAlert("Alert! menu option 1 selected!");
break;
case 2: GAlert("Alert! menu option 2 selected!");
break;
case 3: GAlert("Alert! menu option 3 selected!");
break;
case 4: GAlert("Alert! menu option 4 selected!");
break;
case 5: GAlert("Alert! menu option 5 selected!");
break;
case 6: GAlert("Alert! menu option 6 selected!");
break;
case 7: DoInput();
break;
case 8: bD = TRUE;
break;
}
}
// Wait for a keypress
while (! kbhit()) { };
// Close windows
WClose(bW2);
WClose(bW1);
// Exit
return;
}
Here is a screenshot of it in use (the cursor is active on the s in the word has of the Alpha field):
This time I cover the conversion of the numeric spinner input. It allows for a number to be chosen where the user does not enter it directly, but rather changes the value by pressing keys representing increment and decrement. It only allows for value between 0 and 250. Values above 250 are reserved for form control, such as ESC and TAB. I feel like this is fine since no one will want to cycle up/down into thousands or more, and most likely this will be used from 0 to 100. I contemplated a top limit of 100, but decided to allow up to 250. This was also the last gadget I wrote for my Action! library.
In the demo program, the user is allowed to change the Value field from 5 to 100. Once accepted, the new value is displayed below in the New field.
Here is the demo program showing how its called:
// ------------------------------------------------------------
// Program: gspin.c
// Desc...: A8 Library Window Menu Test
// Author.: Ripdubski
// Date...: 202208
// Notes..: cl65 -v [-O] -t atarixl gspin.c -o gspin.xex
// ------------------------------------------------------------
// Pull in include files
#include <stdio.h>
#include <conio.h>
#include <unistd.h>
#include <string.h>
#include <atari.h>
#include "a8defines.h"
#include "a8defwin.h"
#include "a8libmisc.c"
#include "a8libstr.c"
#include "a8libwin.c"
#include "a8libgadg.c"
#include "a8libmenu.c"
// ------------------------------------------------------------
// Func...: void DoSpin(void)
// Desc...: Displays spinner demo
// ------------------------------------------------------------
void DoSpin(void)
{
byte bW, bC, bV;
unsigned char cL[4];
// Set default value
bV = 10;
// Open window
bW = WOpen(8, 7, 24, 9, WOFF);
WOrn(bW, WPTOP, WPLFT, "Spinner");
WPrint(bW, 2, 2, WOFF, "Value:");
WPrint(bW, 4, 4, WOFF, "New:");
// Display spinner and get returned value
// Min value=5, max value=100
bC = GSpin(bW, 8, 2, 5, 100, bV);
// What was returned?
if (bC == XESC)
{
GAlert(" Escaped Out! ");
}
else if (bC == XTAB)
{
GAlert(" Tabbed Out! ");
}
else
{
// Show the new value
sprintf(cL, "%3d", bC);
WPrint(bW, 8, 4, WOFF, cL);
}
// Wait for a key
WPrint(bW, WPCNT, 6, WOFF, "Press any key.");
WaitKCX(WOFF);
// Close window
WClose(bW);
}
// ------------------------------------------------------------
// Func...: void main(void)
// Desc...: Main routine
// ------------------------------------------------------------
void main(void)
{
// Variables
byte bW1, bW2, bC, bD;
unsigned char *pcM[8] =
{ " Alert ", " Progress ", " Buttons ", " Checkboxes ", " Radio Button ", " Spinner ", " Quit " };
// Setup screen
WInit();
WBack(14);
// Open windows
bW1 = WOpen(0, 0, 40, 3, WON);
WPrint(bW1, WPCNT, 1, WOFF, "CC65 A8 Library Test");
bW2 = WOpen(12, 6, 16, 11, WOFF);
WOrn(bW2, WPTOP, WPCNT, "Menu");
// Set done to false
bD = FALSE;
// Loop until done (Quit selected)
while (! bD) {
// Call menu
bC = MenuV(bW2, 1, 2, WON, 1, 7, pcM);
// Process choice
switch (bC)
{
case 1: GAlert("Alert! menu option 1 selected!");
break;
case 2: GAlert("Alert! menu option 2 selected!");
break;
case 3: GAlert("Alert! menu option 3 selected!");
break;
case 4: GAlert("Alert! menu option 4 selected!");
break;
case 5: GAlert("Alert! menu option 5 selected!");
break;
case 6: DoSpin();
break;
case 7: bD = TRUE;
break;
}
}
// Wait for a keypress
while (! kbhit()) { };
// Close windows
WClose(bW2);
WClose(bW1);
// Exit
return;
}
With a basic button set now working, I set my sights on converting my Action! library radio button routine. This is the GRadio() function. It displays a set of radio buttons either horizontally or vertically. I made a minor alteration in how navigation/selection is handled in the C version. In the Action! version, space would establish a button as selected but not exit until Enter was pressed. After using it a bit and never having been completely satisfied, I chose to have Space select and exit. Navigation feels smoother with this minor change.
To enable form drawing before activating any controls, it is necessary to call the function with a flag telling it to only display the choices and exit. You call it again with an edit flag when it is ready to be activated. After a radio button is edited, it should be called again with the display only flag to clean up – meaning to revert the selection if ESC was pressed, or show the new selection. There is no difference here in how it worked in my Action! library.
It’s noteworthy to mention that each of the interactive gadgets will return special values if ESC or TAB are pressed. This enables you to write a looping routing to process different input gadget elements as a form and allow the user to TAB through each control – much like modern GUI’s. In the demo program here you can see the handling of these keys which prevent the exit of the loop/form until both sets of radio buttons have the 3rd option selected.
Here is the demo program showing how the function is called for both orientations.
// ------------------------------------------------------------
// Program: gradio.c
// Desc...: A8 Library Gadget Radio Button Test
// Author.: Ripdubski
// Date...: 202208
// Notes..: cl65 -v [-O] -t atarixl gradio.c -o gradio.xex
// ------------------------------------------------------------
// Pull in include files
#include <stdio.h>
#include <conio.h>
#include <unistd.h>
#include <string.h>
#include <atari.h>
#include "a8defines.h"
#include "a8defwin.h"
#include "a8libmisc.c"
#include "a8libstr.c"
#include "a8libwin.c"
#include "a8libgadg.c"
#include "a8libmenu.c"
// ------------------------------------------------------------
// Func...: void DoRadio(void)
// Desc...: Displays radio button demo
// ------------------------------------------------------------
void DoRadio(void)
{
byte bW, bra, brb, brap, brbp;
unsigned char *rA[4] = { "One", "Two", "Three" };
unsigned char *rB[4] = { "Choice A", "Choice B", "Choice C" };
// Set defaults for selection and previous selection
bra = 1;
brb = 1;
brap = bra;
brbp = brb;
// Open window
bW = WOpen(2, 5, 36, 12, WOFF);
WOrn(bW, WPTOP, WPLFT, "Radio Button Test");
// Show horizontal buttons
WPrint(bW, 1, 2, WOFF, "Radio Buttons (horiz)");
GRadio(bW, 2, 3, GHORZ, GDISP, brap, 3, rA);
// Show veritcal buttons
WPrint(bW, 1, 5, WOFF, "Radio Buttons (vert)");
GRadio(bW, 2, 6, GVERT, GDISP, brbp, 3, rB);
WPrint(bW, 1, 10, WOFF, "Pick 3rd choices to exit.");
// Loop until accepted or cancelled
while ((bra != 3) || (brb != 3))
{
// Display horiz buttons and get choice
bra = GRadio(bW, 2, 3, GHORZ, GEDIT, brap, 3, rA);
// If not bypass, set previous selected value
if ((bra != XESC) && (bra != XTAB))
{
brap = bra;
}
// Redisplay buttons
GRadio(bW, 2, 3, GHORZ, GDISP, brap, 3, rA);
// Display vert buttons and get choice
brb = GRadio(bW, 2, 6, GVERT, GEDIT, brbp, 3, rB);
// If not bypass, set previous selected value
if ((brb != XESC) && (brb != XTAB))
{
brbp = brb;
}
// Redisplay buttons
GRadio(bW, 2, 6, GVERT, GDISP, brbp, 3, rB);
}
// Close window
WClose(bW);
}
// ------------------------------------------------------------
// Func...: void main(void)
// Desc...: Main routine
// ------------------------------------------------------------
void main(void)
{
// Variables
byte bW1, bW2, bC, bD;
unsigned char *pcM[7] = { " Alert ", " Progress ", " Buttons ", " Checkboxes ", " Radio Button ", " Quit " };
// Setup screen
WInit();
WBack(14);
// Open windows
bW1 = WOpen(0, 0, 40, 3, WON);
WPrint(bW1, WPCNT, 1, WOFF, "CC65 A8 Library Test");
bW2 = WOpen(12, 6, 16, 10, WOFF);
WOrn(bW2, WPTOP, WPCNT, "Menu");
// Set done to false
bD = FALSE;
// Loop until done (Quit selected)
while (! bD) {
// Call menu
bC = MenuV(bW2, 1, 2, WON, 1, 6, pcM);
// Process choice
switch (bC)
{
case 1: GAlert("Alert! menu option 1 selected!");
break;
case 2: GAlert("Alert! menu option 2 selected!");
break;
case 3: GAlert("Alert! menu option 3 selected!");
break;
case 4: GAlert("Alert! menu option 4 selected!");
break;
case 5: DoRadio();
break;
case 6: bD = TRUE;
break;
}
}
// Wait for a keypress
while (! kbhit()) { };
// Close windows
WClose(bW2);
WClose(bW1);
// Exit
return;
}
In the previous post I converted the non-interactive gadget GProc(). In this post I move to the most basic of user interaction: buttons (like OK, Cancel) via function GButton(). The button labels and quantity are up to you, as well as the default button selection.
The programmer is required to specify any ornaments for the buttons, if so desired. As the user moves between buttons, the active one is displayed in inverse video. The implementation in C is actually quite simpler than an Action! due to C’s advanced handling of arrays of strings. The source code will be released once the entire library has been converted – at that point you can compare if you are so inclined.
Here is a program demonstrating its use:
// ------------------------------------------------------------
// Program: gbutton.c
// Desc...: A8 Library Gadget Button Test
// Author.: Ripdubski
// Date...: 202208
// Notes..: cl65 -v [-O] -t atarixl gbutton.c -o gbutton.xex
// ------------------------------------------------------------
// Pull in include files
#include <stdio.h>
#include <conio.h>
#include <unistd.h>
#include <string.h>
#include <atari.h>
#include "a8defines.h"
#include "a8defwin.h"
#include "a8libmisc.c"
#include "a8libstr.c"
#include "a8libwin.c"
#include "a8libgadg.c"
#include "a8libmenu.c"
// ------------------------------------------------------------
// Func...: void DoButt(void)
// Desc...: Displays button demo
// ------------------------------------------------------------
void DoButt(void)
{
byte bC, bW;
unsigned char *cS[3] = { "[ Ok ]", "[ Cancel ]" };
// Open window
bW = WOpen(9, 10, 22, 6, WOFF);
WOrn(bW, WPTOP, WPLFT, "Buttons");
WPrint(bW, 2, 2, WOFF, "Select a button:");
// Call the button handler with all buttons defined
bC = GButton(bW, 5, 4, 2, 2, cS);
// Close window
WClose(bW);
return;
}
// ------------------------------------------------------------
// Func...: void main(void)
// Desc...: Main routine
// ------------------------------------------------------------
void main(void)
{
// Variables
byte bW1, bW2, bC, bD;
unsigned char *pcM[7] = { " Alert ", " Progress ", " Buttons ", " Checkboxes ", " Radio Button ", " Quit " };
// Setup screen
WInit();
WBack(14);
// Open windows
bW1 = WOpen(0, 0, 40, 3, WON);
WPrint(bW1, WPCNT, 1, WOFF, "CC65 A8 Library Test");
bW2 = WOpen(12, 6, 16, 10, WOFF);
WOrn(bW2, WPTOP, WPCNT, "Menu");
// Set done to false
bD = FALSE;
// Loop until done (Quit selected)
while (! bD) {
// Call menu
bC = MenuV(bW2, 1, 2, WON, 1, 6, pcM);
// Process choice
switch (bC)
{
case 1: GAlert("Alert! menu option 1 selected!");
break;
case 2: GAlert("Alert! menu option 2 selected!");
break;
case 3: DoButt();
break;
case 4: GAlert("Alert! menu option 4 selected!");
break;
case 5: GAlert("Alert! menu option 5 selected!");
break;
case 6: bD = TRUE;
break;
}
}
// Wait for a keypress
while (! kbhit()) { };
// Close windows
WClose(bW2);
WClose(bW1);
// Exit
return;
}
In the next set of posts I’ll be converting my Action! libraries Gadget routines. Gadgets are what I refer to as the widgets used in conjunction with the windowing library to create form based input. Think of a gadget as an input mechanism like a button (OK, Cancel, etc), a radio button (where only 1 over several choices can be selected), a checkbox, etc. The first one I converted is not so much as an input control, but a way to show progress to a user during a slow operation. This is the GProg() function.
This function is called with the placement location within an already opened window handle, and the value (0 to 100) to display.
Here is the code that demonstrates:
// ------------------------------------------------------------
// Program: gprog.c
// Desc...: A8 Library Progress Gadget Test
// Author.: Ripdubski
// Date...: 202208
// Notes..: cl65 -v [-O] -t atarixl gprog.c -o gprog.xex
// ------------------------------------------------------------
// Pull in include files
#include <stdio.h>
#include <conio.h>
#include <unistd.h>
#include <string.h>
#include <atari.h>
#include "a8defines.h"
#include "a8defwin.h"
#include "a8libmisc.c"
#include "a8libstr.c"
#include "a8libwin.c"
#include "a8libgadg.c"
#include "a8libmenu.c"
// ------------------------------------------------------------
// Func...: void DoProg(void)
// Desc...: Displays progress bar demo
// ------------------------------------------------------------
void DoProg(void)
{
byte bL, bW;
// Open window
bW = WOpen(9, 10, 22, 4, WOFF);
WOrn(bW, WPTOP, WPLFT, "Progress");
// Loop through progress
for (bL=1; bL <= 100; bL+=5)
{
// Display the progress bar and wait 1 second
GProg(bW, 1, 2, bL);
sleep(1);
}
// Close window
WClose(bW);
return;
}
// ------------------------------------------------------------
// Func...: void main(void)
// Desc...: Main routine
// ------------------------------------------------------------
void main(void)
{
// Variables
byte bW1, bW2, bC, bD;
unsigned char *pcM[7] = { " Alert ", " Progress ", " Buttons ", " Checkboxes ", " Radio Button ", " Quit " };
// Setup screen
WInit();
WBack(14);
// Open windows
bW1 = WOpen(0, 0, 40, 3, WON);
WPrint(bW1, WPCNT, 1, WOFF, "CC65 A8 Library Test");
bW2 = WOpen(12, 6, 16, 10, WOFF);
WOrn(bW2, WPTOP, WPCNT, "Menu");
// Set done to false
bD = FALSE;
// Loop until done (Quit selected)
while (! bD) {
// Call menu
bC = MenuV(bW2, 1, 2, WON, 1, 6, pcM);
// Process choice
switch (bC)
{
case 1: GAlert("Alert! menu option 1 selected!");
break;
case 2: DoProg();
break;
case 3: GAlert("Alert! menu option 3 selected!");
break;
case 4: GAlert("Alert! menu option 4 selected!");
break;
case 5: GAlert("Alert! menu option 5 selected!");
break;
case 6: bD = TRUE;
break;
}
}
// Wait for a keypress
while (! kbhit()) { };
// Close windows
WClose(bW2);
WClose(bW1);
// Exit
return;
}
As I worked on converting my Action! library to C for use with CC65, I stumbled on a few typos which resulted in minor bugs. This release addresses those and provides a minor update to the manual as well. This version supercedes all previous versions.
The attached file has a “.odt” extension due to upload restrictions. Simply download it, rename it so “.ATR” is the only extension. The API documentation is in PDF format.
Version 1.6 will include the FujiNet and SQL bindings. I do not have an ETA on that release yet.
Downloads
A GitHub page has been established for code distribution. You can download the release there, or using the links below.