Posted on

Laptop Battery Test using QB64

Tags: BASIC, programming, QB64, scientific method

What Is It?

My laptop battery seemed to fail after about 30 minutes, with what I thought was a full charge.  These things happen, as batteries are very much a consumable thing. Another full charge back at the lab and the behavior occurred again.  Yet another charge later and it was back to what appeared to be its nominal runtime, which was traditionally 3-4 hours. How was I to measure the specific behavior of the battery without sitting in front of it for hours and watching it?  A simple solution was to write a small program that updated a file every minute and then look at that file after the battery discharged enough to shut down the laptop. This was my first attempt to characterise this phenomenon based on observable data.

How Did I Get To This Point?

Time out of the office was once time unproductive for me.  If I wasn’t sitting in front of my desktop computer, how was I supposed to get any work done?  Then it occurred to me that, since I actually own a “portable computer”, i.e., a laptop, I really didn’t have an excuse.  So as an experiment, I took my trusty laptop with me on a tedious errand and proposed to write a couple of short articles.

As described above, I enjoyed about half an hour of reasonable performance with my laptop, without the benefit of being plugged into the wall.  I should mention that this is an older model, an ASUS R500V with an Intel Core i7 processor and a solid state drive. The “MFD” (manufactured?) date on a sticker on the bottom (after some Chinese characters which I still can’t read) said “2012-11”, so I would assume that meant November of 2012, which seems about right.  As of this writing, my laptop would be just over 7 years old.

Blame the Battery

Nothing lasts forever, especially consumables like rechargeable batteries.  The prudent thing to do would be to order a replacement and thus solve the problem, which I did.  In a few days, I expect delivery of a replacement battery, and that should be that.

Another reason to take this blunt and direct approach to problem-solving (lacking style or subtlety) is that laptop batteries are now cheap.  They used to hover around the US$100 level and can now be had for under $20, which often includes shipping. Problem solved, move along, nothing to see here.

Questioning My Motives

Yet I wondered.  I made this decision based on irritation and the fear of losing faith in (and possibly use of) my essential tools.  How could I make a more informed decision in the absence of cold, hard facts? Having some solid run-time data would be a good start.

What I didn’t want to do was have this turn into “a whole thing”, as I often say.  I had already performed a handful of charge-discharge cycles on the laptop-charger-battery system, but was not meticulously noting any actual times.  How could I do this without becoming a virtual slave to my ageing tools?

One More Datapoint Wouldn’t Hurt

I have my best technological ideas when I am furthest from the lab where I could take action on them.  Weird it is, but here we are. One experiment would be to run the laptop to exhaustion, and note the elapsed time.  This is problematic, as the laptop doesn’t do any useful work after the power fails.

A more indirect approach was needed.  Very quickly, a plan erupted into my mind.  I could write a program that updated a status file with the current time and date.  The file would need to be opened, written and closed as quickly as possible. Doing so will minimize the risk of the eventual and inevitable power failure would occur during the update itself, possibly corrupting the file or, worse, the file system itself.

Empowering Technologies

“If all you have is a hammer, everything begins to look like a nail”.

–paraphrase of The Law of the Instrument, see https://en.wikipedia.org/wiki/Law_of_the_instrument

This truism has another face:  The more art and skill you possess, the more options you have.  One “empowering technology” that I have is the ability to code in BASIC.  Luckily for me, I had early opportunities in life to learn about programming, long before “home computers” became available.

As a teeneager, I worked over the summer at a minicomputer (yes, that was a thing) manufacturer in Richardson, Texas called Computer Automation.  A bonus of this low-paying job was that I could borrow an Alpha 16 computer, complete with teletype machine, for the duration of the summer.  It boasted a very complete version of the BASIC programming language. I would noodle on this contraption until the wee hours of the morning. Surely this had to be distracting for the rest of my family, as a Teletype makes quite the clatter.

To this day, BASIC has its fans and detractors.  Moving from whatever version of BASIC the Alpha 16 was running, to the Atari BASIC cartridge (floating point! graphics!), to IBM’s BASICA, GW-BASIC, QBASIC, then QuickBASIC (compiled!), I always had a quick and (sometimes) dirty method to whip up some code to accomplish small tasks.  Very empowering, indeed!

But This is 2020 Frowny Face Question Face

While I have also learned to program in C and a little bit in Python, sometimes you only want to perform a simple function. Setting up those environments and keeping up with their ever-changing capabilities can be exhausting, especially to old folk like me.

Enter QB64

QB64 calls itself “QB64 is a modern extended BASIC programming language that retains QBasic/QuickBASIC 4.5 compatibility and compiles native binaries for Windows, Linux, and macOS”.  You can find out more about it on their web site QB64.org.  QB64 is distributed under the MIT license, though it took an email to the support team to clarify this.

This is what QB64 looks like when first opened on a Windows 10 system.  Look familiar?

Figure 1.  The QB64 home screen.  Look familiar?

For those of you that remember QBASIC and similar programming environments from the long, long ago, the QB64 home screen should look rather familiar.  Just like most integrated development environments (IDEs) of today all start to look alike (I’m looking at you, Eclipse), there’s only so many ways to make a text-based application work that supports both a menu system and a multi-document interface (MDI).

QB64 version 1.4, the current stable version of the program as of this writing, is what I will be using for this project.

Program Outline

A simple program should be defined (or more precisely, be able to be defined) with a short outline.  Here’s what I want this program to do, in a nutshell:

  1. Document program function, author, date, license and possibly revision history
  2. Open a unique filename in a known location
  3. Write a short statement (plain text) as a heading describing the function of the file.
  4. Close the file
  5. Write a status message once a minute:
    1. Open the file
    2. Write the update record at the end of the file
    3. Close the file
  6. Repeat from Step 5, ad infinitum (because I don’t know the fancy Latin phrase for “until exhaustion”).

Bonus Points:  Display status messages on the console so we can “see” what’s going on with the program, as well as a command-line option to disable said notices.

Program Documentation

“Self-documenting code” is an oxymoron.  If it’s code, it’s not readable;  that’s what the word code means.  If it’s readable, it’s not code.  If it isn’t readable, it isn’t self-documenting.  Just sayin’.

However, let’s leave a few breadcrumbs for later generations in the form a few program comments.  As learned in your very first computer programming class (assuming that happened), the first few lines of your source code (i.e., the original, human-readable document that will subsequently be ground up into ones and zeros later) should contain the file name, the program or project to which it belongs, the purpose of the file (if not obvious) and the author’s name and perhaps contact information.  Bonus points will be awarded for identifying the licensing terms under which the work is to be published and keeping some sort of version-tracking information in the source code as well.

Here’s what I thought would be a good start to the program:

The program header (using comments).  File name, project name, author, contact information, no revision history.

Figure 2.  The program header (using comments).  File name, project name, author, contact information, no license or revision history.

Omitting the licensing terms for this soon-to-be-published code is my sneaky way of getting people to contact me if they are curious about using the code for their own purposes.  I wasn’t going to hear from those nogoodniks that were going to outright steal it, in any case.

Don’t fret, Dear Reader, that you are not able to scrape the code itself from these (graphic) screen shots.  I will post the complete code as in a block near the end of the article as well as offer a direct download link to the most recent version.

Satisfying outline item number one (so-called self-documentation), to a certain extent, we move on to number two, creating a unique file name for this experimental run.

Asking For Help

If your memory, like mine, does not retain the seemingly infinite number of BASIC keywords and their usage, fear not.  You can access detailed help in two ways.

The QB64 web site contains a wiki that describes the available functions by keyword or usage:

http://www.qb64.org/wiki/Keyword_Reference_-_By_usage

The same information is contained within the application itself.  Select menu item “Help/Keywords by usage” and see the following screen.

Built-in help from within the QB64 application

Figure 3.  Built-in help from within the QB64 application.

You can then mouse around, clicking on the links, and explore the available help topics.  This is a very handy feature of the original QuickBASIC as well as QB64.

The Program Begins

At the beginning of program execution, we should emit a line or two on the console describing the program’s name and intended function.  This gives the user immediate feedback that the program is running correctly.

For our purposes, a simple statement should suffice:

“Battery Test vX.X starting…”

If we are to use program version numbers, we should define them near the top of the primary source file as CONST data types, so that they cannot be subsequently modified as the program executes.

CONST PROGRAM_NAME = "Battery Test"
CONST VER_MAJOR = "0"
CONST VER_MINOR = "0"

' Announce program name/function

PRINT PROGRAM_NAME + " v" + VER_MAJOR + "." + VER_MINOR

Using this technique, you can change the program version number once at the top of the code and it will automatically be updated throughout, without having to hunt for every occurrence.

If you run the application now, you will be greeted with a simple console screen with the words “Battery Test v0.0” at the top and “Press any key to continue” and the bottom.

Unique File Names

Once upon a time, I used to write programs for the IBM PC that called native functions of the operating system.  One of the interesting functions provided by MS-DOS 3.0 and later, for example, was the “Create Unique Temporary File” command, or “INT 21h function 5Ah” to those in the know.  You would give this function a specific sub-directory and it would return a filename that was not currently being used within that folder. This was useful to avoid over-writing any other file. The resulting filename was usually gibberish, but since they were supposedly temporary in nature, it didn’t really matter much.

Today QB64 does not support this function, so we have to try a different approach.  What we can do today is create a filename that ought to be as-yet-unused, then check to see if it truly is not yet used, then use that.  If it is already in use, for whatever reason, we try again with a slightly different file name.

Making Up a Fake As-Yet-Unused Name

Unlike the Bad Old Days, we can use long file names with our programs.  Now by “long”, I mean more than the “8.3” format of our ancestors.  Ask an Old Person; they will tell you all about it.

Ideally, each run of this program will create its own status file.  We can, of course, delete all these files once the novelty wears off, but only after carefully examining the data and drawing what conclusions we may.

To lessen the chances of stepping on anyone else’s files, we’ll prefix our file name with the name of our little test program, i.e., “Battery Test”.  We then append the current time and date, which ought to make it unique.  However, let’s not assume.

“Let’s Not Guess;  Let’s Measure!”

These steps are all straight-forward when using QB64.  First, we prepare a candidate status file name using our predetermined suffix (i.e., program name) and the current date and time, with the file extension “.TXT” to let future visitors know it’s a “text” file.

filename$ = PROGRAM_NAME + " - " + DATE$ + " - " + TIME$ + ".TXT"

Unfortunately, our naive conglomeration will not work as a filename, because the BASIC function TIME$ returns a STRING variable that contains a human-readable value, e.g., “01:02:03”.  While colons help the human eye to separate the constituent values, they do not play nice with legacy file systems such as MS-DOS, upon which Windows 10 depends.

We can change the offending colons to (almost) anything else, so let’s change them to dashes:

alt_time$ = TIME$
MID$(alt_time$, 3) = "-"
MID$(alt_time$, 6) = "-"
filename$ = PROGRAM_NAME + " - " + DATE$ + " - " + alt_time$ + ".TXT"

Now we have a file name that Windows 10 will tolerate.

But Is It Unique?

While QB64 won’t generate a unique file name out of thin air for us, it will tell us if a given file name already exists or not, using the _FILEEXISTS(filename$) function.  This function returns a “0” value (for false) if the file does not exist in the current folder, or a “-1” (for true) if it does.

We test this assumption of uniqueness with a simple IF statement.  Note that here we are only interested in the case where the file name is not unique.  If it is already unique, we should continue with the rest of the program.

Supposing that, for some reason, the file name is not unique, a DO … LOOP UNTIL control structure increments a variable, n, appending it (within parentheses) to the candidate file name, until uniqueness occurs.

IF _FILEEXISTS(filename$) = -1 THEN
    n = 0
    DO
        n = n + 1
        filename$ = PROGRAM_NAME + " - " + DATE$ + " - " + alt_time$ + " (" + STR$(n) + ").TXT"
    LOOP WHILE _FILEEXISTS(filename$)
END IF

Now It Is Unique

Either the file name was originally unique, or a unique combination of the candidate file name with appended iterator n eventually was.  In either case, we announce the name of the file on the console.

PRINT "Using status file " + filename$

Emit Column Headings

A good way to analyze the resulting data file would be to import it into a spreadsheet program and generate some pretty graphs.  To make this process easier for the spreadsheet, we’ll name the forthcoming data columns in the first line.

Now we can output the file headers, being sure to close the file afterwards.  Remember, the battery is going to fail at any time now!  Let’s leave things tidy, shall we?

' Open file for output; write caption; close file

file_handle = FREEFILE
OPEN filename$ FOR OUTPUT AS file_handle
PRINT #file_handle, PROGRAM_NAME + " Date, Time"
CLOSE file_handle

The Main Loop

Here is where the program should spend the majority of its time.  Once a minute (or until we decide on a more reasonable time period), the status file will be opened and a new line appended to the end, containing the date and time.

One way to achieve this timing between updates would be to wait for the minute number on the system-reported time to change.  The reason this would be a Bad Thing is that it would run continuously, eating up all available processor cycles while doing so.  Another, better, option would be to use the _DELAY function, which takes a parameter that can be expressed in fractional seconds. It also yields any remaining CPU cycles back to the operating system so other tasks can execute. We want just shy of one minute, or sixty seconds, so we pass an argument of 59.99 seconds to the function.  This will eventually result in either a roll-over or roll-under event to occur in the log, specifically in the seconds column. For our purposes, this is acceptable.

' The main loop

DO
    file_handle = FREEFILE
    OPEN filename$ FOR APPEND AS file_handle
    PRINT DATE$ + ", " + TIME$ ' to console
    PRINT #file_handle, DATE$ + ", " + TIME$ ' to file
    CLOSE file_handle

    _DELAY 59.99 ' 59.99 seconds is most of a minute
LOOP

Program Testing

Now that we have a potentially working program, let’s set it to work and see what it does.  With the battery reporting to be about 75% charged, I unplugged it from the charger and started the logging program.  Then I just walked away.

Later, I found the laptop powered down, as expected.  Looking at the status log, I saw that the first entry was made at “02-24-2020, 14:59:59”, and the last one was recorded at “02-24-2020, 16:51:59”.  So right away I decided that the program should be calculating the elapsed time and recording it for me. Do I have time to do clock math? No, I do not.

Well, for right now, I guess I can do some clock math.  First, the starting and ending dates are the same, so that makes it a bit easier.  16:51:59 minus 14:59:59 is 1:52:00, so there we have a nearly two hour run time with what was reported to be a 75% charge.  All I have to do now is wait the estimated (so it tells me) 1:32:00 for the battery to fully charge and then I can repeat the experiment with a 100% charge.  In the meantime, I can update the program to calculate the elapsed time for me.

A Few Final Tweaks to the Program

I added a minute counter to the main loop, so we don’t have to tax our big brains with clock math.  The resulting number is added to the status report each minute. I spent some time trying to get the number of minutes to print out using “H:MM” format, but got frustrated and gave up. This would be trivial using C’s printf() function, but that’s not what I’m using here. The number of minutes is close enough. Fractional hours would have worked, as well.

After several runs, I discovered that the laptop was set to automatically power down after two hours when running on the battery.  I created a new battery profile for testing to exhaustion and ran some more charge+discharge cycles.

I really wanted to add the system’s estimated battery power remaining percentage and run time, but this is where I decided to stop.  Sadly, importing these status files into a spreadsheet program and creating a graph only produces a straight, diagonal line, indicating the steady march of time during each test.  Having some fun statistics like estimated run time would make the graphs more entertaining, at least.

Program Listing

As promised, here’s the program listing for this little test application.

' battery_test.bas
' Battery Test program
' 22 February 2020
' Dale Wheat
' https://www.dalewheat.com
' dale@dalewheat.com

CONST PROGRAM_NAME = "Battery Test"
CONST VER_MAJOR = "0"
'CONST VER_MINOR = "0" ' original version
CONST VER_MINOR = "1" '  added elapsed minutes

' Announce program name/function

PRINT PROGRAM_NAME + " v" + VER_MAJOR + "." + VER_MINOR

' Generate unique file name for status log

alt_time$ = TIME$
MID$(alt_time$, 3) = "-"
MID$(alt_time$, 6) = "-"
filename$ = PROGRAM_NAME + " - " + DATE$ + " - " + alt_time$ + ".TXT"

IF _FILEEXISTS(filename$) = -1 THEN
    n = 0
    DO
        n = n + 1
        filename$ = PROGRAM_NAME + " - " + DATE$ + " - " + alt_time$ + " (" + STR$(n) + ").TXT"
    LOOP WHILE _FILEEXISTS(filename$)
END IF

PRINT "Using status file " + filename$

' Open file for output; write caption; close file

file_handle = FREEFILE
OPEN filename$ FOR OUTPUT AS file_handle
PRINT #file_handle, PROGRAM_NAME + " Date, Time, Elapsed (minutes)"
CLOSE file_handle

' The main loop

minutes = 0 ' reset count of elapsed minutes

DO

    file_handle = FREEFILE
    OPEN filename$ FOR APPEND AS file_handle
    PRINT #file_handle, DATE$ + ", " + TIME$ + ", " + STR$(minutes) ' to file
    CLOSE file_handle

    PRINT DATE$ + ", " + TIME$ + ", " + STR$(minutes) ' to console

    minutes = minutes + 1 ' count the elapsed minutes
    _DELAY 59.99 ' 59.99 seconds is most of a minute
LOOP

Listing 1.  The Battery Test program in QB64. You can also download it from https://www.dalewheat.com/info-content/battery_test.bas.

Additional Features

More Accurate Timing

QB64 offers some more versatile timing options, such as the TIMER and ON TIMER functions.  These would make the periodic update event asynchronous to the setup code, and would more than likely eliminate the roll-over or roll-under anomalies of the current method.

Battery Runtime Guessing

When running Windows on a laptop, the user can hover the mouse over the battery icon in the notification tray to see the estimated run time remaining on the current charge.

Battery runtime estimate

Figure [4]  Windows 10 estimates remaining battery capacity

By accessing a Windows DLL via QB64, we could add this information to the status file that is created.  Other operating systems should offer similar guesses.

Command Line Parameters

To tailor each run of this little program, we could allow the use of “command line parameters”.  Doing so would allow us to modify the program’s behavior without having to alter the original source code each time.

Some options that come to mind:

  • -v for Verbosity
  • -p for Path of status file
  • -n for alternate name for status file
  • -m for maximum run time limit
  • -t for time period between status updates

Surely you’ve already thought of some useful additions, yourself.  These options would need to be handled at the beginning of the program.

Other Solutions

Some of you might, by this point, be thinking, “But you could have much easierly (now a word) done it using [x], where x is some other technology instead of BASIC, or a different flavor of BASIC.  A DOS batch file, some Windows application for the very purpose… the list goes on.  You are entirely correct to say so. But this was the story of how I did it this way.

Things I Learned While Telling You This Story

If you installed QB64 in a folder that requires “administrative privileges”, your resultant executables might not execute normally.  Installing in a folder where you already have complete freedom (i.e., write privilege) is advised.

Using Google Docs for quick articles such as this one has a small but measurable learning curve.  For example, keeping up with word count is important to a professional writer. While you can check your word count with a menu item (Tools/Word Count, or Ctrl+Shift+C), you can also check the box on the resulting dialog box marked “Display word count while typing”.  This wedges a word count box in the lower left corner of the screen. Bonus: It’s actually a drop-down selection box. Clicking on it gives you the totals for pages, words and characters (with and without counting spaces, if that matters to you). Unfortunately, this setting is not persistent.  Also, once you get past a certain length, the post-it widget no longer works.

Using keyboard shortcuts in Google Docs (or any application) is always good from someone who is communicating mostly via the keyboard.  Accepting spell-check and grammar-check suggestions is trivial using a mouse (just hover, then click) but the keyboard-only version is a bit cumbersome:  Cursor-key over to the offending word or phrase, Ctrl+Shift+\, then down arrow, then [Enter]. Yikes! But it keeps my hands on the keyboard, where I want them.  One day muscle memory will do this for me.

https://webapps.stackexchange.com/questions/127538/is-there-a-keyboard-shortcut-for-accept-suggestion-in-google-docs

Conclusion

I’m glad I took this little diversion.  I’ve got a better understanding of how well (or not) my laptop battery performs.  I also enjoyed working with QB64. Not all of the tools from my past can still hold water these days.  For example, once upon a time I could (correctly) adjust the alignment magnets on the back of a cathode ray tube. Ah, good times.

QB64 has proven to be a useful tool for me for writing simple programs that perform simple functions.  This leverages my misspent youth as a BASIC programmer. Having browsed through QB64’s list of other functions, I think it would be fun to write more ambitious applications.

Let me know what you think.