Home | pfodApps/pfodDevices | WebStringTemplates | Java/J2EE | Unix | Torches | Superannuation | CRPS Treatment | | About Us
 

Forward Logo (image)      

Using Claude AI to Code an Energy Monitor
to run on an ESP32 using Arduino

by Matthew Ford 2nd April 2025 (originally posted 15th March 2025)
© Forward Computing and Control Pty. Ltd. NSW Australia
All rights reserved.

Update: 2nd April 2025 – Claude Code's code returned a number of infinite loops. Which when pointed out to Claude, Claude could not fix. Claude just went round and round changing logic tests but not removing the loop. Only after being instructed to introduce another state variable was Claude able to break the loop.
Update: 24th March 2025 – Claude Code's binary search method went into an infinite loop when searching on a single record.

Introduction

This page describes setting and using Claude Code on a Windows 10 machine and then using it to write Arduino code for an Energy Monitoring System running on an ESP32.

Claude Code is an 'agentic' coding tool made by Anthropic. As of 15th March 2025, it is in beta as a research preview. You should think of it as an enthusiastic intern, eager to help, with basic knowledge, but lacking engineering judgement and finesse. In this project, Claude almost never produced 'correct' code. There were either compile errors or logic errors. However when they were pointed out to Claude, it would correct them.

Cost: AI costs ~US$35.00 (so far)
Time: ~40hrs over 15 days.

The completed code will be available in the Solar Hot Water V2 project (under construction) and a separate RTCwithTZ library.

Tips for Effective AI Coding

Don't trust Claude's code!! Check and test it carefully as you would with the code submitted by any intern.

Leave Claude's terminal session open (costs more??) until the request change is check as correct, so that when errors need to be corrected Claude already has the context of the changes requested and made.

Make the request as precise as possible with any extra information known to better guide Claude to the solution you want.

Ask Claude to tell you what it is planning to do before it does it, because is, sometimes, Claude over enthusiastic and can muck up existing code that already works.

Keep all the code related to an object / data / functionality in the same file so that when you ask Claude to change one part, Claude can see that other parts need to updated to match at the same time. Claude will do these other updates automagically.

Know when to stop making changes! Just because it is easy to add a feature does not mean you should add it. Keep it simple. Only add what is actually needed.

An introductory example

Claude will do more intelligent coding than what is requested, if it is in the same context.

You get better results if all the code associated with a set of data is in the one file. Here is an example request

>update printDataFile to output hw_kwr as the last field in the csv line.

Here is what Claude did in response

I've successfully updated the code to include hw_kwh in the CSV output. Here's a summary of the changes made:

  1. Updated the CSV header in printDataFile to include hw_kwh
  2. Modified printDataFile to include the hw_kwh value in the output (with 3 decimal places precision)
  3. Updated printBinaryRecord to also include hw_kwh in its output
  4. Updated the comment for parseCSVLineToBinaryRecord to include hw_kwh in the expected format
  5. Enhanced parseCSVLineToBinaryRecord to parse the hw_kwh field from CSV files, with graceful handling for older
   files that might not have this field

  These changes will ensure that hw_kwh is properly included in the CSV output when using the printDataFile
  function, which will help provide more comprehensive data about hot water consumption in the exported files.

Because the print and parse methods where in the same file, Claude correctly interpreted that if one was changed the other would also need to be changed. Claude also handle missing data when reading in older files. But it still got one part of code wrong. It tried to used a toFloat() method to parse a double. It should have used toDouble(). As shown below Claude's output is almost never completely correct, but it still does most of the grunt work for you.

Installing Claude Code on Windows 10

Claude Code is a UNIX based system so installation on Windows 10 is a bit more involved. You need to install WSL2 (Windows Subsystem for Linux 2) first.

Prerequisites

WSL 2 is only available on Windows 11 or Windows 10, Version 1903, Build 18362 or later.
Check your Windows version by selecting the Windows logo key + R, type winver, select OK. (Or enter the ver command in Windows Command Prompt).

Also Virtualization needs to be enabled
One the Task Manager → performance → CPU Virtualization and enable it.

The Microsoft article suggest setting up Windows Terminal. I just used the Command Prompt to open the Claude Code terminal.

WSL2 Installation

The following links will be useful
https://www.supportyourtech.com/articles/how-to-install-wsl2-on-windows-10-a-step-by-step-guide/
https://learn.microsoft.com/en-us/windows/wsl/install
https://learn.microsoft.com/en-us/windows/wsl/setup/environment#set-up-your-linux-user-info

Follow those to install WSL2 and setup your UNIX login.

After WSL2 is installed open the Command Prompt (run as Admin necessary ??) and type the command
wsl ~
which will take you to your home directory.

Note: you can also access your linux subsystem from the Window File explorer to copy files to and from.

Claude Installation

Claude needs Node.js installed. Installing from npm did not work. After the npm install of Node.js, the installation of claude via npm install -g @anthropic-ai/claude-code failed.

Option 3 from https://www.digitalocean.com/community/tutorials/how-to-install-node-js-on-ubuntu-20-04 worked to install Node.js.

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
source ~/.bashrc
nvm list-remote
nvm uninstall node   >>> says not installed ??
nvm install –lts

>>> then this cmd worked
npm install -g @anthropic-ai/claude-code 

Then create a directory in home for your AI work, e.g. aicode. cd aicode and run
sudo chmod -R 777 aicode
to let claude update you code files directly.

Note: Claude Code is a terminal program. You need an anthropic account on your console.anthropic.com and follow the one-time OAuth process with your Console account.
You’ll also need active billing at console.anthropic.com.to and add money to your account as needed. See under Settings → Billing to add money and check you credit.

Create a directory for you AI work, e.g. aicode. cd aicode and run
sudo chmod -R 777 aicode
to let claude update you code files directly. Otherwise Claude will write the generated code to a temporary directory.

Running Claude

From your UNIX directory, e.g. aicode, type the command claude to open the Claude Code terminal and go through the initial setup.
Also check out https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview

Coding With Claude Code

Claude Code provides terminal base system for working on files in the current directory and sub-directories. It can update the files directly if the permissions are sufficiently lax (0x777) otherwise it will write the generated code to a temporary directory. Assuming you are continuing to use your Windows OS to run the Arduino compile and load, after Claude has made changes, close Arduino and copy the changes the Arduino project in the Arduino sketch directory. Beyond Compare is very useful for this as it quickly highlights the changes for checking.

Giving Claude Instructions

You can just type instructions directly into the terminal window, but it can be more convenient to create a text file claudeInstructions.txt in the aicode directory and that details your requests, with as much extra information as available, to get the precise result you want. A separate text file readInst.txt containing just the line
Read the claudeInstructions.txt and tell me what you would change to implement the requested changes|
is also useful to copy and paste into Claude's terminal prompt.

Coding Tasks for Claude and the Results

The aim of this project was to save the basic data coming, via a telnet connection every 2 seconds, from the Hot Water Controller in the meter box and plot it on a web page.

Basic Web Page Plot

Started out with CanvasJS reading from a csv stream. Claude first wrote the webpage code js using the “fetch” web api, but when requested, rewrote it using the more basic XMLHttpRequest which works with older browsers. XMLHttpRequest is more complex to code but Claude handled that part successfully. CanvasJS is free for no-commercial use and canvasjs.min.js is about 500Kb so it can be hosted locally and served off-line.

Saving the Telnet Data to Day Files

The starting code base included a telnet client that connected to the Solar Hot Water Controller in the meter box and received a stream of csv data, one line each 2sec, containing the various kWhr and Watts measurements and the state of the system, Solar, Off, Off Peak and Hot Water triac on/off.

The first Claude request was to save total import/export Watts, Hot Water Watts, state, How Water triac on/off and Hot Water Boost on/off together with the record's utc_time to a LittleFS's file with today's date. That was successful with one minor logical error. Claude automagically included a circular buffer to hold the records and only save them to the file when the buffer was full and added debugging output using the existing debug structure, but when reporting the number of records saved, it cleared the number first and then wrote the debug message instead of the other way around.

Then Claude was requested to save the buffer at least once per hour and before rotating the file name at midnight. This result had a more serious failure. To find the local time, Claude used localtime_r which gives incorrect results on the ESP32. This showed up when the plots for the day did not start at midnight and resulted in about 5hrs of fault finding.

Plotting the Day Files Data

Next when Claude was asked to plot the saved data via CanvasJS, its first attempt sent all the data to the webpage for the javascript to filter to display the selected date/time range. There are about 43000 records a day (~1 Mega bytes) and the system will save at least 7 days on data (using an ESP32 with 16Meg of flash storage).

Asking Claude to do the filtering in the ESP32 and to only send 500 points to be plotted resulted in code that tried to load all the data in to memory in the ESP32!! Although the down sampling code was good.

Asking Claude to do a 'disk' base filtering and down sampling resulted in useful code block that pre-scanned the files to determine the ones in the date range and count the records in the date/time range (by reading them on at time), then calculating the sampling interval and re-reading the files (one record at a time) and building up the web response csvData from the 500 sampled points and then sending back to the webpage for plotting. Claude successfully handled the edge cases and give usefully debug output messages.

However this process took more than 5secs to complete and the AsyncWebServer library watch dog timer kept timing out and rebooting the ESP32.

Using Chunked Responses

Asking Claude to use a Chunked response got around this. Here Claude was particularly useful as I did not have to completely understand how to code the supporting methods to implement Chunked responses in the AsyncWebSever. I just had to know it was possible. Perhaps Claude would have suggested it if I had asked, but I did not try that.

The Chuked response code uses a stateStruct to keep the state of the response between callbacks for the next chunk. The code Claude provided did not compile due to coding error defining and instanting the stateStruct. Also Claude did not put the entire state in the stateStruct, but instead used two local statics that leaked the state between multiple concurrent requests.

The AsyncWebServer Chunked call backs passes in a maxLen available to be used. Claude initially ignored that value, but fixed the code when directed to test if the next csv line would fit. Even with Chunked responses some calls were taking a few seconds, which if the AsyncWebServer processing thread was running on the Arduino loop() thread, would interfere with processing incoming telnet data. So the code was edited by hand to only run for 500ms. However this resulted in some call backs returning 0 for the response data. So Claude was asked to send back “\r\n” in those cases and modify the webpage to ignore the empty lines. This it did successfully.

Claude's code first scans the file directory for date data files and then keeps a note of those in the date range for subsequent processing. However Claude's code did not sort the files into date order for processing, so it was asked to fix that which Claude did.

While the filtering and down sampling now ran in block of less then 500ms, the initial counting of the records in the date/time range still took a few seconds for just 3 days partial data because Claude was reading every record in sequence and counting them. So I asked Claude to
a) use a binary search to find the start and end time record on the start and end date and use the file position to calculate the number of records and
b) for internal days just use the file length to calculate the number of records.
This was the most successful code Claude produced. It worked first time and cut the record counting down to less then 100ms for 3 days data.
Update: 24th March 2025 – Claude Code's binary search method went into an infinite loop when searching on a single record.

There are some missing records in the data files (due to stopping and starting the ESP32 for testing). Claude was asked to insert a “null” record if there was a gap of more then 2.5 * the sampling interval. The resulting code broke the test for the csvData being kept to less than maxLen. Asking Claude to fix this gave useful code.

Miscellaneous changes were request to the plot display, such as the format of the date and time on the x-axis and enabling zoom and pane. Claude did the successfully and I did not have to refer to the CanvasJS documentation.

The next change request (for now) as to remove the method that handled plotting just on date file, that is web requests of the form /data/YYYYMMDD. Claude's first attempt was to use addParam( ) calls to add date range data and then call the date range processing method directly. However that method is not implemented by the AsyncWebServer library and trying to compile the code gave an error message saying AsyncWebServerRequest does not have a addParam() method. Claude had deduced that such a method was a reasonable inclusion is a webserver library but did not check that it actually existed in AsyncWebServer.

Pasting the error message into Claude prompted it to correct the code to a redirect.

Running the Monitor Off-Line

As a first step to being able to run the monitoring system completely off-line (no internet access), Claude was asked to source the canvasjs package locally instead of from the internet. Claude just changed the path in the chart.html to /canvasjs.min.js but did not update the web server code to serve that file. So that had to be requested as well and also to set the cache-control to 1 year.

Claude Code proved very useful in refactoring the web pages to move all the %..% variables to the top of the file and then extract them to a separate file and turn them into a 'header' string. Claude also generated a simple method to parse the string for these %...% variables and pass them to a processor for replacement. This implemented AsyncWebServer's processing function without needing to import that entire library.

Claude was also useful in generating methods to handle the conversions between struct tm and unix timestamps, with and without timezone adjustments. A cut down version of Adafruit's fork of JeeLabs RTC code was used to read and write the DS3231 RTC. Claude also generated an method to smoothly update the ESP32 system time from the RTC, but Claude's first (and second) attempts did not work!!.

Conclusion

Claude is like an enthusiastic intern and is very helpful in generating code to do the tasks you request. However Claude almost never produces error free code. Either there are small compile errors or logical errors, sometimes major ones. Also sometimes Claude will make changes (that you did not ask for) the breaks existing working code, so ask Claude to tell you what it plans to do first.

Also don't expect Claude to produce optimal code, unless you give it a lot of context and ask it to optimize a particular aspect. As Donald Knuth says
“Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3% “
Knuth D., "Structured Programming with Goto Statements". Computing Surveys 6:4 (December 1974), pp. 261–301, §1. doi:10.1145/356635.356640 (from https://en.wikiquote.org/wiki/Donald_Knuth)

So let Claude take a first pass to get something working and then carefully check and test the generated code and then identify those areas that need optimization, i.e. from the examples above high bandwidth, out-of-memory, slow processing etc and then ask Claude to work on those.

AndroidTM is a trademark of Google Inc. For use of the Arduino name see http://arduino.cc/en/Main/FAQ


The General Purpose Android/Arduino Control App.
pfodDevice™ and pfodApp™ are trade marks of Forward Computing and Control Pty. Ltd.


Forward home page link (image)

Contact Forward Computing and Control by
©Copyright 1996-2024 Forward Computing and Control Pty. Ltd. ACN 003 669 994