Phase 3:
Last time we wrote the code for controlling an ESP32 board running the .NET nanoFramework over a Bluetooth connection. In this post we’ll be concentrating on the algorithm that calculates the position of the sun. Note that as said in the post that started this series, we already selected an existing algorithm, but we need to port it to C# and see if the ESP32 board can run it.
Sources
The code for this blog post is available on GitHub - HelioStat AllPhases. This repository will contain the code for all phases of the project, just look inside the subfolders for the code of a specific phase. Note that the repository exists only to support the writing of these blog posts. The real repository, GitHub - HelioStat, is the one that you should report issues on or create pull requests for.
Calculating the position of the sun
The algorithm we’ll be using is distributed by the National Renewable Energy Laboratory in the US. On that page you’ll find a link to a pdf that describes the mathematics behind it. It’s quite involved, and we’ll not handle it here.
Definitions
The position of the sun as seen from any place on earth is known by 2 angles, the azimuth and zenith angles.
- The zenith angle is defined as the angle between straight up, and a line from ground zero to the sun. When the sun is overhead, this angle is thus 0 degrees, and on flat ground, the sun is at 90 degrees when it sets.
- The azimuth angle tells where the sun is relative to North. East is 90 degrees, South is 180, and West 270 degrees.
The picture below shows this graphically.
Inputs of the algorithm
In order to come up with the correct angles, the algorithm needs to know about our where and when. The ‘where’ is determined by the longitude and latitude of our position.
- Longitude (negative west of Greenwich) valid range: -180 to +180 degrees
- Latitude (negative south of equator) valid range: -90 to +90 degrees
The ‘when’ is the current time, but we also need to specify the timezone.
To make the calculation more precise, also the current elevation can be set.
The algorithm allows to tweak even more parameters to reach greater precision, but we’ll stop here.
Porting the code
The original code is written in C, which resembles C# for many things. Porting the code was thus fairly easy. Problems arose however when i was using it for the first time, as i was getting the dreaded OutOfMemoryException…
The algorithm uses a lot of lookup tables for what is called the ‘Periodic Terms’. Essentially they are a bunch of 2D an 3D tables with values aiding in the calculations. These values were all defined as doubles in the original code. To save precious Ram, i switched to float values, as this reduces the memory footprint by 50%. This does have an impact on the precision of the calculations, but only in the first digit after the decimal point. As we’re not Nasa, aiming for an asteroid at 1 million kilometers away, this is just fine.
Note that the OutOfMemoryException happened when i was still using the WROOM board which has only 512K ram. Later i switched to a WROVER board with 8M ram, so probably reverting to using doubles is possible, but i saw no need for it.
Code changes when compared to Phase2
NFSpa library
When you open op the solution for Phase3, you’ll notice a new project is added, NFSpa. The calculator class contains the main code for doing the calculations. It has 1 public method, Spa_Calculate, that takes an instance of the Spa_data class as input, and of which it will update some fields as result of the calculation. ( hey, all this is ported from C, and it still looks like C, don’t shoot me for the strange formatting and inner workings )
CalcSpa Task
A new task that we can invoke over Bluetooth is added to the main project, the CalcSpa class. Inside the Execute method, we create an instance of the Spa_data class mentioned before, and we provide it some hardcoded values for the where and when. The where are the longitude and latitude values for the city of Brussels, the when is November 3 at noon. By default, the timezone inside the Spa_data instance is set to 0, so when we say noon = 12 o’clock = 12h UTC time. Next, we invoke the Spa_Calculate method, check the result for input validation errors, and if ok, output the Azimuth, Zenith, Sunrise up and down values over Bluetooth.
Testing it out
When we deploy and run the app to our ESP32 board, we can now issue the ‘calcspa’ command in the Bluetooth terminal app on our smartphone. The result is seen below, it returns the values we’re interested in.
To verify if these values are correct, we can compare them to the results of an online calculator. The one i used is the NOAA Solar Calculator. As input we need to fill in the same where and when values. Our 12h UTC time becomes 1PM local time for Brussels.
If we set the values next to each other, this is the result. ( note that we converted CET to UTC and the Elevation angle from the online calculator to Zenith )
Algorithm | ESP32 | Online |
---|---|---|
Sunrise | 06:39:16 UTC | 06:38 UTC |
Sunset | 16:12:10 UTC | 16:14 UTC |
Azimuth | 188.90° | 188.95° |
Zenith | 66.68° | 66.38° |
There are some small differences, which can either be caused by using 2 different algorithms, or by me modifying the doubles to floats.
To get to the bottom of it, i used another online calculator from the people that made our algorithm available. This one still uses doubles, so let’s see what the result is from that. You’ll find that calculator at SPA Calculator
Sure enough the Azimuth value is now 188.941, and the zenith 66.383, which is much closer to the results from the first calculator.
Should we switch back to using doubles? Nah, not yet, i’m guessing that for our purposes it doesn’t make a difference. But, feel free to modify the code when you need that extra bit of precision. Just change all the floats in the PeriodicTerms.cs class to doubles.
What’s next
With a working algorithm available, next is getting rid of these hardcoded values for the when and where, and instead use a realtime clock and values for longitude and latitude that can be set over Bluetooth and stored on the ESP32 board.