
SQLite Broken
- Fangbeast
- PureBasic Protozoa
- Posts: 4789
- Joined: Fri Apr 25, 2003 3:08 pm
- Location: Not Sydney!!! (Bad water, no goats)
I ripped out the essentials of what I was doing from my address book program. Hope this gives you some idea??
Let me know if not.
Let me know if not.
Code: Select all
;
============================================================================================================================
; Fang prototypes
;============================================================================================================================
PrototypeC sqlite3_open( dbName.p-ascii, dbHandle.l)
PrototypeC sqlite3_exec( dbHandle.l, dbName.p-ascii, Zero1.l, Zero2.l, Error.l)
PrototypeC sqlite3_close( dbHandle.l)
PrototypeC sqlite3_errmsg( Error.l)
PrototypeC sqlite3_get_table( dbHandle.l, SQLquery.p-ascii, lPointer.l, lRows.l, lCols.l, Error.l)
PrototypeC sqlite3_free_table( lPointer.l)
PrototypeC sqlite3_changes( dbHandle.l)
PrototypeC sqlite3_last_insert_rowid( dbHandle.l)
PrototypeC sqlite3_free( lPointer.l)
;============================================================================================================================
; Fang declarations for procedures, if any
;============================================================================================================================
Declare.l SQL3GetTable(sSQLQuery.s, *Rows, *Cols, lDataBaseHandle.l)
;============================================================================================================================
; Include any form designer code at this point
;============================================================================================================================
;============================================================================================================================
; SQLite 3 related found by El_Choni
;============================================================================================================================
#SQLITE3_OK = 0 ; Successful Result
#SQLITE3_ERROR = 1 ; SQL error Or missing database
#SQLITE3_INTERNAL = 2 ; An internal logic error in SQLite
#SQLITE3_PERM = 3 ; Access permission denied
#SQLITE3_ABORT = 4 ; Callback routine requested An abort
#SQLITE3_BUSY = 5 ; The database file is locked
#SQLITE3_LOCKED = 6 ; A table in The database is locked
#SQLITE3_NOMEM = 7 ; A malloc() failed
#SQLITE3_READONLY = 8 ; Attempt To write A readonly database
#SQLITE3_INTERRUPT = 9 ; Operation terminated by SQLite_Interrupt()
#SQLITE3_IOERR = 10 ; Some kind of disk I/O error occurred
#SQLITE3_CORRUPT = 11 ; The database disk image is malformed
#SQLITE3_NOTFOUND = 12 ; (internal Only) table Or record not found
#SQLITE3_FULL = 13 ; Insertion failed because database is full
#SQLITE3_CANTOPEN = 14 ; Unable To open The database file
#SQLITE3_PROTOCOL = 15 ; database lock protocol error
#SQLITE3_EMPTY = 16 ; (internal Only) database table is empty
#SQLITE3_SCHEMA = 17 ; The database schema changed
#SQLITE3_TOOBIG = 18 ; Too much Data For one Row of A table
#SQLITE3_CONStraint = 19 ; abort due To contraint violation
#SQLITE3_MISMATCH = 20 ; Data type mismatch
#SQLITE3_MISUSE = 21 ; Library used incorrectly
#SQLITE3_NOLFS = 22 ; Uses OS features not supported on host
#SQLITE3_AUTH = 23 ; Authorization denied
#SQLITE3_ROW = 100 ; sqlite_step() has another Row ready
#SQLITE3_DONE = 101 ; sqlite_step() has finished executing
;==============================================================================================================================
; Program data structure
;==============================================================================================================================
Structure programdata ; Program data structure (Variables you use to do things)
EndStructure
;============================================================================================================================
; Make all the protype names global
;============================================================================================================================
Global program.programdata
Global NewList SqlData.s()
Global s3open.sqlite3_open, s3exec.sqlite3_exec, s3close.sqlite3_close, s3error.sqlite3_errmsg ; All sqlite3 prototypes
Global s3gettable.sqlite3_get_table, s3freetable.sqlite3_free_table, s3changes.sqlite3_changes
Global s3lastinsertid.sqlite3_last_insert_rowid, s3freememory.sqlite3_free
;============================================================================================================================
; Include the sqlite dll in the final exe
;============================================================================================================================
DataSection
sqlite3_dll : IncludeBinary "sqlite3upx.dll" ; Use the UPX packed dll
;sqlite3_dll : IncludeBinary "sqlite3.dll" ; Or use the unpacked version
EndDataSection
;============================================================================================================================
; Write all your procedures at this point or include them here
;============================================================================================================================
;============================================================================================================================
; Get data back from an SQLite database table and stuff it into a concatenated linked list (My choice)
;============================================================================================================================
Procedure.l SQL3GetTable(sSQLQuery.s, *Rows, *Cols, lDataBaseHandle.l)
ClearList(SqlData.s())
If s3gettable(lDataBaseHandle, sSQLQuery, @LResultsPtr, @LRows, @LCols, @ReturnValue) = #SQLITE3_OK
PokeL(*Rows, LRows) ; Return number of rows/columns
PokeL(*Cols, LCols)
If LRows > -1 And LCols > 0
Address.l = LResultsPtr ; Copy data into array
For Row.l = 1 To LRows
For Col.l = 0 To LCols - 1
tempdata.s + PeekS(PeekL(Address + (((Row * LCols) + Col) * 4))) + "|"
Next
AddElement(SqlData.s()) ; Stuff the data into a linked list for later
SqlData.s() = tempdata ; Or whatever else youw ant to do with it
tempdata.s = ""
Next
EndIf
s3freetable(LResultsPtr) ; free table memory
ProcedureReturn #True
Else
s3error(@ReturnValue)
ProcedureReturn #False
EndIf
EndProcedure
;============================================================================================================================
; Start the main program initialisation here
;============================================================================================================================
;============================================================================================================================
; Initialise the SQLite dll, create the datebase and the table if they don't exist
;============================================================================================================================
sqlite3_lib = LoadLibraryM(?sqlite3_dll) ; Load the library from memory, don't write to disk
If sqlite3_lib
s3open.sqlite3_open = GetProcAddressM(sqlite3_lib, "sqlite3_open")
s3exec.sqlite3_exec = GetProcAddressM(sqlite3_lib, "sqlite3_exec")
s3close.sqlite3_close = GetProcAddressM(sqlite3_lib, "sqlite3_close")
s3error.sqlite3_errmsg = GetProcAddressM(sqlite3_lib, "sqlite3_errmsg")
s3gettable.sqlite3_get_table = GetProcAddressM(sqlite3_lib, "sqlite3_get_table")
s3freetable.sqlite3_free_table = GetProcAddressM(sqlite3_lib, "sqlite3_free_table")
s3changes.sqlite3_changes = GetProcAddressM(sqlite3_lib, "sqlite3_sqlite3_changes")
s3lastinsertid.sqlite3_last_insert_rowid = GetProcAddressM(sqlite3_lib, "sqlite3_last_insert_rowid")
s3freememory.sqlite3_free = GetProcAddressM(sqlite3_lib, "sqlite3_free")
If s3open(program\database, @program\dbhandle) = #SQLITE3_OK
SqlQuery.s = "CREATE TABLE addresses("
SqlQuery.s + "firstname TEXT,lastname TEXT,street TEXT,"
SqlQuery.s + "record INTEGER PRIMARY KEY AUTOINCREMENT)"
If s3exec(program\dbhandle, program\query, #Null, #Null, @ReturnValue) = #SQLITE3_OK
;MessageRequester("Information", "New table created in the new database")
EndIf
Else
MessageRequester("SQLite3 Error", "Could not open " + program\database)
End
EndIf
Else
MessageRequester("SQLite3 Error", "Could not initialise SQL3 Dll library")
End
EndIf
;============================================================================================================================
; To add a record
;============================================================================================================================
AddRecord:
SqlQuery.s = "INSERT INTO addresses("
SqlQuery.s + "firstname,lastname,street)"
SqlQuery.s + "VALUES('" + user\firstname + "','" + user\lastname + "','" + user\street + "')"
If s3exec(program\dbhandle, program\query, #Null, #Null, @ReturnValue) = #SQLITE3_OK ; Add the record
user\record = Str(s3lastinsertid(program\dbhandle)) ; Last inserteded id of record
Else
; Put error emssage here
EndIf
Return
;============================================================================================================================
; To return data from a table
;============================================================================================================================
GetData:
SqlQuery.s = "Select * From addresses" ; Return ALL data from the named table
If SQL3GetTable(SqlQuery.s, @myRows, @myCols, program\dbhandle) ; Get the data from the table into the linked list
ForEach SqlData.s() ; Iterate through the list
; Do what you need to here with the data. I return all data with "|" as the field separator
Next
EndIf
Return
;============================================================================================================================
; Permanently delete a record or records from a database
;============================================================================================================================
DeleteRecord:
SqlQuery.s = "DELETE FROM addresses WHERE record = '" + program\record + "'" ; Delete using record number
If s3exec(program\dbhandle, program\query, #Null, #Null, @ReturnValue) = #SQLITE3_OK ; Try to delete it now
Else
CallCFunctionFast(sqlite3_free, @ReturnValue) ; Free the sqlite call
EndIf
Return
;============================================================================================================================
; Permanently delete a record or records from a database
;============================================================================================================================
EditRecord:
If SQL3GetTable("Select * FROM addresses WHERE record='" + program\record + "'", @myRows, @myCols, program\dbhandle)
If CountList(SqlData.s()) <> 0 ; Check if any data was returned even if query worked
; Do what you need to witht he returned data here
EndIf
EndIf
Return
Amateur Radio/VK3HAF, (D-STAR/DMR and more), Arduino, ESP32, Coding, Crochet
- Fangbeast
- PureBasic Protozoa
- Posts: 4789
- Joined: Fri Apr 25, 2003 3:08 pm
- Location: Not Sydney!!! (Bad water, no goats)
Thanks jb. Wasn't sure if it was comprehensive enough. I only ever use 2 commands (more or less) and they are enough to parse queries.
For more advanced people, I am trying to learn how to porototype all 90 functions available in the sqlite3 dll but I need help from C experts.
Later, I'll put out a small FlashGet log analyser (working) with these functions properly laid out in it.
For more advanced people, I am trying to learn how to porototype all 90 functions available in the sqlite3 dll but I need help from C experts.
Later, I'll put out a small FlashGet log analyser (working) with these functions properly laid out in it.
Amateur Radio/VK3HAF, (D-STAR/DMR and more), Arduino, ESP32, Coding, Crochet
Thanks Fangbeast, sounds like what you're working on there may be exactly what I need ... please keeps us all posted on your progress. Unfortunately, my exposure to C is limited to a class 20yrs ago!Fangbeast wrote:...For more advanced people, I am trying to learn how to porototype all 90 functions available in the sqlite3 dll but I need help from C experts...

- Fangbeast
- PureBasic Protozoa
- Posts: 4789
- Joined: Fri Apr 25, 2003 3:08 pm
- Location: Not Sydney!!! (Bad water, no goats)
I don't know C, period. So unless somebody knows C and PB very well, this will not get done. I am usinc the basic worked out by some very smart people on the forum but they have no time to do any more so this will stay as it is sadly. I am not saying that most jobs cannot be done with the commands alread there, especially since most work is done by SQL queries themseles fed to the 'exec' command but it would be nice to have a FULL implementation of sqlite in pb.
Just to give you an idea what I am up against.
Just to give you an idea what I am up against.
Code: Select all
#define SQLITE_STATIC ((void(*)(void *))0)
#define SQLITE_TRANSIENT ((void(*)(void *))-1)
#define SQLITE_INTEGER 1
#define SQLITE_FLOAT 2
#define SQLITE_TEXT 3
#define SQLITE_BLOB 4
#define SQLITE_NULL 5
#define SQLITE_UTF8 1
#define SQLITE_UTF16 2
#define SQLITE_UTF16BE 3
#define SQLITE_UTF16LE 4
#define SQLITE_ANY 5
#define SQLITE_UTF8 1
#define SQLITE_UTF16BE 2
#define SQLITE_UTF16LE 3
#define SQLITE_UTF16 4
#define SQLITE_CREATE_INDEX 1 /* Index Name Table Name */
#define SQLITE_CREATE_TABLE 2 /* Table Name NULL */
#define SQLITE_CREATE_TEMP_INDEX 3 /* Index Name Table Name */
#define SQLITE_CREATE_TEMP_TABLE 4 /* Table Name NULL */
#define SQLITE_CREATE_TEMP_TRIGGER 5 /* Trigger Name Table Name */
#define SQLITE_CREATE_TEMP_VIEW 6 /* View Name NULL */
#define SQLITE_CREATE_TRIGGER 7 /* Trigger Name Table Name */
#define SQLITE_CREATE_VIEW 8 /* View Name NULL */
#define SQLITE_DELETE 9 /* Table Name NULL */
#define SQLITE_DROP_INDEX 10 /* Index Name Table Name */
#define SQLITE_DROP_TABLE 11 /* Table Name NULL */
#define SQLITE_DROP_TEMP_INDEX 12 /* Index Name Table Name */
#define SQLITE_DROP_TEMP_TABLE 13 /* Table Name NULL */
#define SQLITE_DROP_TEMP_TRIGGER 14 /* Trigger Name Table Name */
#define SQLITE_DROP_TEMP_VIEW 15 /* View Name NULL */
#define SQLITE_DROP_TRIGGER 16 /* Trigger Name Table Name */
#define SQLITE_DROP_VIEW 17 /* View Name NULL */
#define SQLITE_INSERT 18 /* Table Name NULL */
#define SQLITE_PRAGMA 19 /* Pragma Name 1st arg Or NULL */
#define SQLITE_READ 20 /* Table Name Column Name */
#define SQLITE_SELECT 21 /* NULL NULL */
#define SQLITE_TRANSACTION 22 /* NULL NULL */
#define SQLITE_UPDATE 23 /* Table Name Column Name */
#define SQLITE_ATTACH 24 /* Filename NULL */
#define SQLITE_DETACH 25 /* Database Name NULL */
#define SQLITE_DENY 1 /* Abort the SQL statement With an error */
#define SQLITE_IGNORE 2 /* Don't allow access, but don't generate an error */
void *sqlite3_aggregate_context(sqlite3_context*, int nBytes);
int sqlite3_aggregate_count(sqlite3_context*);
int SQLite3_Bind_Blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*));
int sqlite3_bind_double(sqlite3_stmt*, int, double);
int SQLite3_Bind_Int(sqlite3_stmt*, int, int);
int sqlite3_bind_int64(sqlite3_stmt*, int, long long int);
int sqlite3_bind_null(sqlite3_stmt*, int);
int SQLite3_Bind_Text(sqlite3_stmt*, int, const char*, int n, void(*)(void*));
int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int n, void(*)(void*));
int sqlite3_bind_parameter_count(sqlite3_stmt*);
int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName);
const char *sqlite3_bind_parameter_name(sqlite3_stmt*, int n);
int sqlite3_busy_handler(sqlite3*, Int(*)(void*,int), void*);
int sqlite3_busy_timeout(sqlite3*, int ms);
int sqlite3_changes(sqlite3*);
int sqlite3_clear_bindings(sqlite3_stmt*);
int sqlite3_close(sqlite3*);
int sqlite3_collation_needed(sqlite3*, void*, void(*)(void*,sqlite3*,int eTextRep,const char*));
int sqlite3_collation_needed16(sqlite3*, void*, void(*)(void*,sqlite3*,int eTextRep,const void*));
const void *SQLite3_Column_Blob(sqlite3_stmt*, int iCol);
int SQLite3_Column_Bytes(sqlite3_stmt*, int iCol);
int sqlite3_column_bytes16(sqlite3_stmt*, int iCol);
double sqlite3_column_double(sqlite3_stmt*, int iCol);
int SQLite3_Column_Int(sqlite3_stmt*, int iCol);
long long int sqlite3_column_int64(sqlite3_stmt*, int iCol);
const unsigned char *SQLite3_Column_Text(sqlite3_stmt*, int iCol);
const void *sqlite3_column_text16(sqlite3_stmt*, int iCol);
int SQLite3_Column_Type(sqlite3_stmt*, int iCol);
int SQLite3_Column_Count(sqlite3_stmt *pStmt);
const char *sqlite3_column_database_name(sqlite3_stmt *pStmt, int N);
const void *sqlite3_column_database_name16(sqlite3_stmt *pStmt, int N);
const char *sqlite3_column_decltype(sqlite3_stmt *, int i);
const void *sqlite3_column_decltype16(sqlite3_stmt*,int);
const char *SQLite3_Column_Name(sqlite3_stmt*,int);
const void *sqlite3_column_name16(sqlite3_stmt*,int);
const char *sqlite3_column_origin_name(sqlite3_stmt *pStmt, int N);
const void *sqlite3_column_origin_name16(sqlite3_stmt *pStmt, int N);
const char *sqlite3_column_table_name(sqlite3_stmt *pStmt, int N);
const void *sqlite3_column_table_name16(sqlite3_stmt *pStmt, int N);
void *sqlite3_commit_hook(sqlite3*, Int(*xCallback)(void*), void *pArg);
int sqlite3_complete(const char *sql);
int sqlite3_complete16(const void *sql);
int sqlite3_create_collation(sqlite3*, const char *zName, int pref16, void*, Int(*xCompare)(void*,int,const void*,int,const void*));
int sqlite3_create_collation16(sqlite3*, const char *zName, int pref16, void*, Int(*xCompare)(void*,int,const void*,int,const void*));
int sqlite3_create_function(sqlite3 *,const char *zFunctionName,int nArg,int eTextRep,void *pUserData,void (*xFunc)(sqlite3_context*,int,sqlite3_value**),void (*xStep)(sqlite3_context*,int,sqlite3_value**),void (*xFinal)(sqlite3_context*));
int sqlite3_create_function16(sqlite3*,const void *zFunctionName,int nArg,int eTextRep,void *pUserData,void (*xFunc)(sqlite3_context*,int,sqlite3_value**),void (*xStep)(sqlite3_context*,int,sqlite3_value**),void (*xFinal)(sqlite3_context*));
int sqlite3_data_count(sqlite3_stmt *pStmt);
int sqlite3_db_handle(sqlite3_stmt*);
int sqlite3_enable_shared_cache(int);
int sqlite3_errcode(sqlite3 *db);
const char *sqlite3_errmsg(sqlite3*);
const void *sqlite3_errmsg16(sqlite3*);
int sqlite3_exec(sqlite3*,const char *sql,sqlite_callback,void *,char **errmsg);
int sqlite3_expired(sqlite3_stmt*);
int SQLite3_Finalize(sqlite3_stmt *pStmt);
void sqlite3_free(char *z);
int sqlite3_get_table(sqlite3*,const char *sql,char ***resultp,int *nrow,int *ncolumn,char **errmsg);
void sqlite3_free_table(char **result);
int sqlite3_get_autocommit(sqlite3*);
int sqlite3_global_recover();
void sqlite3_interrupt(sqlite3*);
long long int sqlite3_last_insert_rowid(sqlite3*);
const char *sqlite3_libversion(void);
char *sqlite3_mprintf(const char*,...);
char *sqlite3_vmprintf(const char*, va_list);
int sqlite3_open(const char *filename, sqlite3 **ppDb );
int sqlite3_open16(const void *filename,sqlite3 **ppDb );
int SQLite3_Prepare(
sqlite3 *db, const char *zSql,int nBytes,sqlite3_stmt **ppStmt, const char **pzTail);
int sqlite3_prepare16(
sqlite3 *db, const void *zSql,int nBytes,sqlite3_stmt **ppStmt,const void **pzTail);
void sqlite3_progress_handler(sqlite3*, int, Int(*)(void*), void*);
int sqlite3_release_memory(int N);
int sqlite3_reset(sqlite3_stmt *pStmt);
void sqlite3_result_blob(sqlite3_context*, const void*, int n, void(*)(void*));
void sqlite3_result_double(sqlite3_context*, double);
void sqlite3_result_error(sqlite3_context*, const char*, int);
void sqlite3_result_error16(sqlite3_context*, const void*, int);
void sqlite3_result_int(sqlite3_context*, int);
void sqlite3_result_int64(sqlite3_context*, long long int);
void sqlite3_result_null(sqlite3_context*);
void sqlite3_result_text(sqlite3_context*, const char*, int n, void(*)(void*));
void sqlite3_result_text16(sqlite3_context*, const void*, int n, void(*)(void*));
void sqlite3_result_text16be(sqlite3_context*, const void*, int n, void(*)(void*));
void sqlite3_result_text16le(sqlite3_context*, const void*, int n, void(*)(void*));
void sqlite3_result_value(sqlite3_context*, sqlite3_value*);
void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
int sqlite3_set_authorizer(sqlite3*, Int (*xAuth)(void*,int,const char*,const char*,const char*,const char*), void *pUserData);
int sqlite3_sleep(int);
void sqlite3_soft_heap_limit(int N);
int SQLite3_Step(sqlite3_stmt*);
int sqlite3_table_column_metadata(sqlite3 *db,const char *zDbName, const char *zTableName,const char *zColumnName,char const **pzDataType,char const **pzCollSeq,int *pNotNull,int *pPrimaryKey,int *pAutoinc);
void sqlite3_thread_cleanup(void);
int sqlite3_total_changes(sqlite3*);
void *sqlite3_trace(sqlite3*, void(*xTrace)(void*,const char*), void*);
int sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*);
void *sqlite3_update_hook(
sqlite3*, void(*)(void *,int ,char const *,char const *,sqlite_int64),void*);
void *sqlite3_user_data(sqlite3_context*);
const void *sqlite3_value_blob(sqlite3_value*);
int sqlite3_value_bytes(sqlite3_value*);
int sqlite3_value_bytes16(sqlite3_value*);
double sqlite3_value_double(sqlite3_value*);
int sqlite3_value_int(sqlite3_value*);
long long int sqlite3_value_int64(sqlite3_value*);
const unsigned char *sqlite3_value_text(sqlite3_value*);
const void *sqlite3_value_text16(sqlite3_value*);
const void *sqlite3_value_text16be(sqlite3_value*);
const void *sqlite3_value_text16le(sqlite3_value*);
int sqlite3_value_type(sqlite3_value*);
Amateur Radio/VK3HAF, (D-STAR/DMR and more), Arduino, ESP32, Coding, Crochet
This was true up to PB 3.94. After that, forget it. PB 4 breaks everything. Now:chris319 wrote:El Choni's wrappers for SQLite2 are REALLY good. They make using SQLite2 with PB a breeze.
Code: Select all
symbol = SQLiteData(ct, 0)
The database I am working with used to be a MySQL database which I accessed in PERL. Then I had the bright idea to convert it to SQLite so I could put everything on a memory stick and send it to a collaborator. PB seemed to be the way to go. Well it was the way to go until 3.94 except that 3.94 did not support double-precision floats. My next bright idea was to convert my SQLite program to PB 4 to take advantage of doubles. Now it seems that every time Fred sneezes my code becomes mysteriously broken and something has to be rewritten, or there is no discernable solution or workaround, and I am getting really, REALLY fed up with it. I could convert to SQLite3 and unravelling the mysteries of that will be another mountain to climb before I get this %$#@! project off the ground. With PERL or SQLite2/PB < 4.0/El Choni's library I had everything going in a matter of hours. PB is now more trouble than it's worth.
Please see my updated SQLite3 code here:
Updated SQLite3 code
This version builds upon kiffi's previous fine work and is compatible with PureBasic 4.0.
Apologies to Fred for making him sneeze.
(My 100th post. Oooh, I'm jumping up and down for joy.)
Updated SQLite3 code
This version builds upon kiffi's previous fine work and is compatible with PureBasic 4.0.
Apologies to Fred for making him sneeze.
(My 100th post. Oooh, I'm jumping up and down for joy.)
- the.weavster
- Addict
- Posts: 1576
- Joined: Thu Jul 03, 2003 6:53 pm
- Location: England
Have any of you had a go with my SQLite Server (which is listed here: http://www.purebasic.fr/english/viewtopic.php?t=19889)? It works with SQLite 3 databases.
I'd appreciate a few opinions, even if you don't like it!
Weave
I'd appreciate a few opinions, even if you don't like it!
Weave
- Fangbeast
- PureBasic Protozoa
- Posts: 4789
- Joined: Fri Apr 25, 2003 3:08 pm
- Location: Not Sydney!!! (Bad water, no goats)
We like it, it's just that it's going to cost us money to like it more (huge grin). I don't have any work so I am not allowed to like it too closely (according to my wife)
And does anyone use my wrapperless solution to sqlite3 that doesn't break between compiler releases???
And does anyone use my wrapperless solution to sqlite3 that doesn't break between compiler releases???
Amateur Radio/VK3HAF, (D-STAR/DMR and more), Arduino, ESP32, Coding, Crochet