CC Blog Projects Research & Design Hub

Local Isolation: Using the Sun’s Energy

FIGURE 1 Two south-facing 100W solar panels are mounted on the back of our Eagle's Nest equipment shed.
Written by Jeff Bachiochi

Part 2: Modbus Client

In last month’s column, I discussed a low-voltage solar energy system I built for lighting my Scout troop’s large storage shed. I described the battery I chose, and the Modbus protocol with RS-485 serial communication used for remote monitoring of the system’s performance (“sniffing” or listening to communication on the bus, and decoding each message). This month I expand on this node’s capacity to do much more than just listen and decode.


  • How can I build a low-voltage solar energy system?
  • How can I use the Modbus protocol with RS-485 serial communication for remote monitoring?
  • How can a Modbus communication port retrieve data and send it via Wi-Fi to another network node that can collect and display the data?
  • HUZZAH32 Feather
  • SN75176

Thanks to a grant from the Hartford Foundation for Public Giving, our newly constructed equipment shed, the Eagle’s Nest, now has a system to collect and store solar energy, and then use it to power LED lighting throughout the shed’s two floors. The system uses two south-facing solar panels, a solar charge controller, a lithium iron phosphate battery, and a Bluetooth module (Figure 1Figure 2, and Figure 3). Previously, the Scouts used the flashlights on their cell phones to search for and pack equipment needed for meetings and campouts. This caused safety issues that were eliminated by providing sufficient permanent lighting.

FIGURE 1
Two south-facing 100W solar panels are mounted on the back of our Eagle's Nest equipment shed.
FIGURE 1
Two south-facing 100W solar panels are mounted on the back of our Eagle’s Nest equipment shed.
FIGURE 2
The solar controller, along with the communication hub, Bluetooth module and power hub, are mounted to the wall opposite the solar panels, just under the stairway leading to the shed's attic. The 12V power hub has individually fused circuits for all the LED lighting.
FIGURE 2
The solar controller, along with the communication hub, Bluetooth module and power hub, are mounted to the wall opposite the solar panels, just under the stairway leading to the shed’s attic. The 12V power hub has individually fused circuits for all the LED lighting.
FIGURE 3
Since our shed is unheated, the Lithium Iron Phosphate 100A-h battery has internal heating pads to prevent it from reaching the low temperatures of our sometimes cruel Northeastern winters. Internal lithium cells are made up of four series groups of pouch cells. The Modbus communications port allows each of the four groups of cells to be individually monitored.
FIGURE 3
Since our shed is unheated, the Lithium Iron Phosphate 100A-h battery has internal heating pads to prevent it from reaching the low temperatures of our sometimes cruel Northeastern winters. Internal lithium cells are made up of four series groups of pouch cells. The Modbus communications port allows each of the four groups of cells to be individually monitored.

We can light the area with simple 12V LED lighting bars, so we won’t need to convert the energy into 120VAC; a low voltage (12V) system will suffice and will be safer. It can all be monitored with a cell phone app that allows you to see real-time and historical data, as long as you’re within range of the shed. Unfortunately, it is too far from our sponsor’s building for power or an Internet connection, so we’re on our own. The Bluetooth module communicates to the controller node and battery node (yes, this is a smart battery) through a twisted-wire RS-485 network, using the Modbus protocol. So far, our solar supplier is not publishing the Modbus specifications for each node. This makes it a bit difficult to add our own node to the bus.

Fortunately, the Modbus protocol is an open standard, with specifications available from Modbus [1]. With them, we can understand the communications protocol. In my project last month (“Local Isolation: Using the Sun’s Energy,” Circuit Cellar 398, September 2023) [2], I created an RS-485 node that can listen to (“sniff”) all the communication happening on the twisted pair bus, and decode each message. This month I will expand on this node’s responsibilities to do more than just listen.

MODBUS REVIEW

The Modbus protocol uses two types of nodes: clients and servers. In our case, the Bluetooth node is a client, and the controller and battery are servers. The protocol requires a client to request data from a server. Servers have address registers where data is kept. These are defined as input registers, holding (output) registers, discrete (bit) inputs , and coils (output bits). Note that I/O bit operations are usually performed on a single bit of an input or holding register.

As of this writing, I have not seen any bit I/O requests. All requests have been of the register kind; registers are a minimum of 2 bytes or a word. However some requests are for multiple registers, and could be several separate pieces of data, or a single value that requires multiple words. The sniffer application tries to understand whether it’s seeing a request or a response. Sometimes this is clear, and other times it is not—for example, when a request and a response are identical.

The PDU (Protocol Data Unit) request and response packets are similar in makeup; they begin with a command and end with data. All servers have an assigned address associated with them. Servers require the first byte of a packet to be their assigned address, or else they neglect the PDU. To assure that the PDU gets delivered without error, a cyclic redundancy check (CRC) is added to the PDU. The PDU is therefore wrapped with the server address as the first byte and the CRC as the last bytes. The total packet is called the Application Data Unit or ADU. The first 2-3 bytes of a packet are the server address and the command. The remaining data and CRC bytes are in pairs or words.

BLUETOOTH CLIENT

All communication is initiated by the client. Here, the client is a Bluetooth (BT) module. The client receives commands from the cell phone application (DC Home). To know which nodes (servers) are in a system, you must first select the BT module. When communication is established between the application and the BT module, you can add devices by connecting them one at a time to the Modbus. The application will perform a request for register 0x1A to the general broadcast address 0x00.

0x00 0x03 0x00 0x1A 0x00 0x01 0x?? 0x??

server command register register count CRC

The connected server will respond with a packet.

0x00 0x03 0x01 0x00 0x10 0x?? 0x??

server command register count data CRC

When the connected node is the solar controller, it responds with its server address, in this case 0x0010. Once registered with the BT module, it will remember that there is a node at address 0x10. Remove it from the bus and continue adding nodes until all your nodes are identified. In fact, the BT client will ask for other information about the node, such as the product’s model and serial numbers. The server address is not revealed to the user, which is one reason last months “sniffer” project was developed. We will need this to communicate with any of the nodes. The sniffer determined that the solar controller (Rover Elite 20A) uses address 0x10, and the 100A-h Lithium Iron Phosphate battery uses address 0xF7.

SNIFFER AS A CLIENT

When the BT module is not connected to the phone application, there is no communication on the Modbus. I can use the sniffer project to look like the BT module client, and request data from any of the server nodes on the Modbus. We don’t have to add much code to allow the sniffer to become a client. Most of the code allows the serial port to become a user interface. This is presented as a menu of supported commands.

1 – Read a Register
2 – Write a Register
3 – Restore Factory Default
4 – Clear History
5 – Change Address
6 – Change myDebug

Note that the first two menu items allow for register reading and writing. The second two items are system commands and are not used in normal querying. The last two items change application parameters, the server address, and the kind of debug data that is sent to the serial port. In most cases, we will be using item #5 to select the server we want to talk to, and item #1 to query information. Let’s spend some time on item #1, “Read a Register.”

QUERYING INFORMATION

In the application described last month [2], the auxiliary serial port, if(Serial1.available()), was polled to see if there was activity on the Modbus that we should sniff (attend to). For this application, we add a poll to the primary serial port (USB), if(Serial.available()). On the primary port, we are using the Serial Monitor in the Arduino IDE to handle the user I/O. Because debug data also uses this port, it is best to keep the debug data to a minimum. Note that you can see which bits enable and disable various debug output in the sketch listing. Since we are expecting the user entry to be an integer between 1 and 6, we can use the Arduino control structure, switch…case, to branch to the appropriate routines.

In Listing 1, we make sure the serial buffer is empty before asking, “How many registers do you wish to read?” The response becomes the modbusRegisterCount. The next question is, “Enter the Starting Register Number?” The response becomes the modbusStartAddress. A final question asks, “Are You Sure? Hit Y.” This allows you to proceed unless you made an error. If you choose to proceed, then we set up the modbusArray[] with the appropriate data and call the sendModbus() routine.

LISTING 1
Once the user enters a menu choice, the switch command uses the integer found to branch to the chosen routine.

//-------------------------------------// start loop//-------------------------------------void loop() {  	 // read from port 1, send to port 0: 	 if (Serial1.available())   	{		 switch(Serial.parseInt())     		 {        			case 1:          				Serial.println(“Request a Register”);          				while(Serial.available())          				{            					Serial.read();          				}          				Serial.println(“How many registers do you wish to read?”);          				Serial.setTimeout(10000);          				modbusRegisterCount = Serial.parseInt();          				Serial.println(“You asked for “ + String(modbusRegisterCount) + “ Register(s)”);          				Serial.println(“Enter the starting Register Number”);          				Serial.setTimeout(10000);          				modbusStartAddress = Serial.parseInt();          				Serial.println(“You entered the Register Number 0x” + String(modbusStartAddress, HEX));         				 if(areYouSure())          				{            					modbusIndexTX = 7;            					modbusArray[0] = modbusAddressCode;            					modbusArray[1] = 0x03;            					modbusArray[2] = modbusStartAddress >> 8;            					modbusArray[3] = modbusStartAddress & 0xFF;            					modbusArray[4] = modbusRegisterCount >> 8;            					modbusArray[5] = modbusRegisterCount & 0xFF;            					checkCRC(modbusIndexTX);            					modbusArray[6] = calculatedCRC >> 8;            					modbusArray[7] = calculatedCRC & 0xFF;            					sendModbus();          				}        	  			break;			…			...		}	}		}//-------------------------------------// end loop//-------------------------------------
LISTING 2
Here is where we actually enable a Modbus communication. The modbusArray[] holds the packet we want to send. The variable modbusIndexTX holds the packet count. 

//-------------------------------------// start function sendModbus//-------------------------------------  void sendModbus(){  	disableRX();  	enableTX();  	for(int i = 0; i < (modbusIndexTX+1); i++)  	{   		 if(myDebug & 0x40)    		{      		Serial.println(“0x”+ String(modbusArray[i], HEX));
					// will prolong the enableTX/disableTX pulse    		}    		Serial1.write(modbusArray[i]);      	}  	delay(modbusIndexTX+3);  	disableTX();  	enableRX();}//-------------------------------------// end function sendModbus//-------------------------------------

In last month’s project [2], I used an SN75176 to handle the physical interface between the application microcontroller (Adafruit’s HUZZAH32) and the twisted-pair Modbus. Although the Modbus protocol doesn’t specify what the physical communication medium is, in this case the industry standard multi-drop RS-485 is used. With this balanced and differential twisted-pair bus touting high speed and long distance, communications must be driven in only one direction at a time. This fits with the Modbus request-response protocol. Please refer to last month’s column [2] for a discussion on the SN75176 and the circuit connections to the microcontroller.

For sniffer operation, we needed only the receiver portion of the SN75176. The receiver and the transmitter have separate enable inputs, so you can control when the receiver will output data found on the Modbus, and can also enable the transmitter to drive the Modbus. Remember, only one device is allowed to drive the Modbus at a given time. That will be the client when requesting, and a server when responding. You can see from Listing 2 that the enable/disable controls are changed before and after the packet is transmitted. The control lines for the receiver and transmitter must be placed in the correct logic states to allow for packet transmission.

Since responses are already coded from the original sniffer application, once a request has been transmitted, the response will be displayed. Note that the Bluetooth module BT-2 is for use with those devices that have a RS-485 interface (RJ-45). Renogy offers controllers that are not RS-485 compatible. These devices have an RS-232 interface using an RJ-12 connector. The BT-1 Bluetooth module is designed for the controllers that use serial RS-232. This interface is a 1:1 interface, and does not support multi-drop or more than one server.

What if you have a controller with an RS-232 communication port, and you want to use it in a multi-drop system?

Luckily both RS-232 and RS-485 use the same baud rate, 9600 baud, and use the Modbus protocol. Remember, I said the Modbus protocol does not actually call out any particular physical interface. We can use the RS-232 controller by adding some circuitry to convert RS-232 to TTL and then add an RS-485 interface. I prototyped this interface using two conversion modules found on the Internet for less than $20 (and that cost was for multiple modules).

The tiny board in Figure 4 uses a MAX3232 to convert the bipolar serial signals (RX and TX) into inverted TTL level (RX and TX). The larger board in Figure 5 has a microcontroller as well as a MAX488 RS-485 driver. The microcontroller handles the RS-485’s TX and RX enables, so you don’t have to worry about them. It will enable TX and disable RX for a period of time, based on receiving data from the TX line. I verified that this project works with a 10A Wanderer controller (Figure 6). This adds the ability to use this type of controller with the RS-485 multi-drop system.

FIGURE 4
This tiny PCB is a serial TTL-to-bipolar RS-232 converter. It uses a MAX3232 to generate the bipolar voltage necessary to convert between TTL serial and an inverted bipolar TX and RX.
FIGURE 4
This tiny PCB is a serial TTL-to-bipolar RS-232 converter. It uses a MAX3232 to generate the bipolar voltage necessary to convert between TTL serial and an inverted bipolar TX and RX.
FIGURE 5
This PCB is a serial TTL-to-RS-485 converter. In addition, it handles the control of the TX driver and RX receiver of the RS-485 converter with a microcontroller that monitors the TTL TX line for activity. When it senses activity, it enables TX and disables RX. This operation is usually handled by your circuit. (See my September, 2023 column in Circuit Cellar [1]).
FIGURE 5
This PCB is a serial TTL-to-RS-485 converter. In addition, it handles the control of the TX driver and RX receiver of the RS-485 converter with a microcontroller that monitors the TTL TX line for activity. When it senses activity, it enables TX and disables RX. This operation is usually handled by your circuit. (See my September, 2023 column in Circuit Cellar [1]).
FIGURE 6
This month's project is assembled in my office and is used to prove the application written for the HUZZAH 32 MCU. Beneath the micro is the RS-485 converter. The 3-wire group exiting the PCB is the RS-485 Modbus connections. For this installment, I am using a Renogy Wanderer. This controller has an RS-232 output; thus, it uses the circuits shown in Figure 4 and Figure 5 to create a Modbus connection.
FIGURE 6
This month’s project is assembled in my office and is used to prove the application written for the HUZZAH 32 MCU. Beneath the micro is the RS-485 converter. The 3-wire group exiting the PCB is the RS-485 Modbus connections. For this installment, I am using a Renogy Wanderer. This controller has an RS-232 output; thus, it uses the circuits shown in Figure 4 and Figure 5 to create a Modbus connection.
LOCAL USE

Since you may be using a similar system on your property, you may be in range of a Wi-Fi connection. If so, you are in luck, because the HUZZAH32 MCU has both Bluetooth and Wi-Fi capability. We’ve already got a Bluetooth app, so let’s add in some Wi-Fi capabilities to allow the sniffer to monitor and report some parameters using MQTT.

MQTT is a standard messaging protocol for the Internet of Things. It is a lightweight publish/subscribe messaging transport that is ideal for connecting remote devices with a small code footprint and minimal network bandwidth. I’ve used this in many projects in the past. My system runs on a Raspberry Pi. The Node-RED application allow you to easily display the data on a PC (or locally on the Pi) by pointing to the MQTT port of the Pi’s IP in a browser.

First let’s briefly touch on the requirements for this HUZZAH32 to connect to my LAN, and connect to the MQTT running on my Pi. The Wi-Fi uses the Wifi.h library, and MQTT uses the PubSubClient.h library. I have hard-coded my network name and network password into the application. This module will be used in station mode (client), since my router will assign a local IP to devices that want to connect. Once it has been assigned an IP, we can attempt communication with any other device on this LAN. I have hard coded my Pi’s IP address in the application, so an attempt is made to connect to the Pi. Once it connects, we’re ready to go. Well, almost. MQTT requires two additional functions.

The reconnect() function is used to establish a connection with MQTT and, as the function name suggests, it will also be called if there should be a loss of connection. Within this function, you can request a subscription, or to be kept up to date on some function happening elsewhere on the MQTT network. In this case, I subscribe to the DateTime function. This is an MQTT routine that publishes the Date and Time every minute. Now our module can stay in sync with the world. The second function is callback(). This routine is used to processes any subscriptions. I take the Date/Time info and integrate it into this application.

The main loop() function is fairly simple. It has six things to check: 1) increment the second variable after 1,000ms; 2) check for disconnection and reconnect if necessary; 3) check for MQTT requests; 4) if it’s time to request solar controller registers; 5) if it’s time to send response data to MQTT; and 6) if there is any traffic on the Modbus.

Before we can request or send any MQTT data, we have to decide just what to send. In this application, I will request registers from the dynamic group (live data)—one request for 32 registers starting at address 0x100. Our sniffer routines will capture the response. Note the variable successArrayCount in Listing 3. This will be incremented each time a different register found in the response is received. This should equal 32, when all 32 registers have been reported. We use this variable to determine when the response has been found and all of the register variables have been updated.

LISTING 3
All parameters of the modbusArray[] are set up. Then a call to send out a request is made (see Listing 2).

//-------------------------------------// start function requestRegisters//-------------------------------------void requestRegisters(){  	//Serial.println(“Requesting Controller Registers”);  	modbusRegisterCount = 0x20;  	modbusStartAddress = 0x0100; 	 modbusAddressCode = 0x10; 	 modbusIndexTX = 7;  	modbusArray[0] = modbusAddressCode;  	modbusArray[1] = 0x03; 	 modbusArray[2] = modbusStartAddress >> 8;  	modbusArray[3] = modbusStartAddress & 0xFF;  	modbusArray[4] = modbusRegisterCount >> 8;  	modbusArray[5] = modbusRegisterCount & 0xFF;  	checkCRC(modbusIndexTX);  	modbusArray[6] = calculatedCRC >> 8;  	modbusArray[7] = calculatedCRC & 0xFF;  	successArrayCount = 0;  	sendModbus();}//-------------------------------------// end function requestRegisters//-------------------------------------
LISTING 4
After filling in the msg and topic character arrays, the function client.publish(topic, msg) will perform the necessary step to publish the MQTT message.

// batterySOC  myString=”{\”batterySOC\”:” + String(batterySOC) + “}”;  myString.toCharArray(msg, msgSize);  myString = ID;  myString.toCharArray(topic, msgSize);          client.publish(topic, msg);

Once the solar controller has responded to our Modbus request, all local variables have been updated. We use these to prepare MQTT packets. The PubSubClient.h library will handle all the messy stuff. We only need to supply two arrays, topic[] and msg[]. The topic array is the same for all messages, “ESP32_9592FC”. The msg array contains a JSON-encoded object. JSON (JavaScript Object Notation) is a text format that is completely language independent, but uses conventions that are familiar to programmers. In its simplest form, a JSON object contains just one member, and the member is in the form {“string”:value}. While this can contain multiple members separated by a comma, I’ll send just one member in each MQTT packet.

The function client.publish(topic, msg) will use the topic and msg arrays to assemble and send an appropriate MQTT packet to the MQTT application running on my Pi. This is done for each of the solar controller registers. Listing 4 shows one of these registers. You can sample and send data at a predetermined rate. This is controlled in the loop() function. For this application, I’m publishing data every minute.

MQTT

Assuming MQTT is receiving our published data, we can choose to display this from any browser. If we point our browser to the MQTT port of the Pi (http://<my Raspberry PI IP>:1880/), we are brought to the Node-RED application (Figure 7). You can check out a couple of my past “From the Bench” columns [3][4] on MQTT and Node-RED, or visit the websites for MQTT [5] and Node-RED [6] for more information.

FIGURE 7
This column sends JSON elements to my Raspberry Pi running MQTT and Node-RED. MQTT handles all publish/subscribe communications, and routes them as necessary. Node-RED is a graphical programming language that allows data to be manipulated in many ways. Here the program "flow" gathers MQTT elements and massages member data so it can be displayed to a user.
FIGURE 7
This column sends JSON elements to my Raspberry Pi running MQTT and Node-RED. MQTT handles all publish/subscribe communications, and routes them as necessary. Node-RED is a graphical programming language that allows data to be manipulated in many ways. Here the program “flow” gathers MQTT elements and massages member data so it can be displayed to a user.

Node-RED provides a graphical palette of nodes that can be used to perform operations on your MQTT data. While this might look a bit intimidating, its pretty easy to understand. In the upper left of Figure 6 is a pink-colored node that grabs the MQTT data that has the topic “ESP32_9592FC.” This feeds two yellow nodes that separate each JSON object member into separate pieces—the topic or sensor name, and the payload or sensor value. The switch node then routes each topic to a separate output, so each sensor’s value can be displayed. The blue nodes are two different types of display—a chart or a text box. Most of the green nodes are debug nodes that can display raw in the debug panel on the right. This group of nodes is known as a “flow.” All of my present flows can be seen across the top of the display as tabs.

Once you have programmed a Node-RED flow, you can see the results in the User Interface, a second browser window at http://<my Raspberry PI IP>:1880/ui/ (Figure 8). Here the data is displayed in real time. You can see the data from the experimental home setup using a small 12V 10W panel. There are five charts and six text areas.

FIGURE 8
The display page of Node-RED demonstrates the result of my program "flow" in Figure 6. In this case, each chart will display the data received during the last week. Other data is displayed as text, which is the more appropriate choice.
FIGURE 8
The display page of Node-RED demonstrates the result of my program “flow” in Figure 6. In this case, each chart will display the data received during the last week. Other data is displayed as text, which is the more appropriate choice.
THE END?

This column shows how we might hijack the Modbus communication port, retrieve data, and send it via Wi-Fi to another network node, which can collect and display the data. But it does not satisfy the requirement that we began with. That is, Troop 96’s equipment shed has no access to electricity, and Internet is not within reach. My September and October columns in Circuit Cellar have been merely a prelude to the last piece of this puzzle. Although our solar energy system can communicate using Bluetooth, once we move off the property, that is no longer accessible. Although this month’s column shows how we might use Wi-Fi, if it were available, we are unfortunately out of range. So what can we do? Next month, I’ll use something that I’ve never discussed before. You might be able to guess what that might be. If not, then you’ll just have to wait until next time, because there is just too much to do and so little time. 

REFERENCES
[1] Modbus specifications. http://www.modbus.com[2] Bachiochi, Jeff: From the Bench: Local Isolation: Using the Sun’s Energy. Circuit Cellar, Issue 398, September 2023, p. 52-58.[3] Bachiochi, Jeff: From The Bench: MQ Telemetry Transport (Part 1): Going Aloft with Mosquitto. Circuit Cellar, Issue 351, October 2019, p. 64-71.[4] Bachiochi, Jeff: From The Bench: MQ Telemetry Transport (Part 2): Bringing It All Back Home. Circuit Cellar, Issue 352, November 2019, p. 61-69.[5] – MQTT – www.mqtt.org[6] – Node-RED – www.nodered.org

RESOURCES
Adafruit | www.adafruit.com
Arduino | www.arduino.cc
Modbus | www.modbus.com
MQTT | www.mqtt.org
Node-RED | nodered.org
Renogy | www.renogy.com
Texas Instruments | www.ti.com

Code and Supporting Files

PUBLISHED IN CIRCUIT CELLAR MAGAZINE • OCTOBER 2023 #399 – Get a PDF of the issue

Keep up-to-date with our FREE Weekly Newsletter!

Don't miss out on upcoming issues of Circuit Cellar.


Note: We’ve made the Dec 2022 issue of Circuit Cellar available as a free sample issue. In it, you’ll find a rich variety of the kinds of articles and information that exemplify a typical issue of the current magazine.

Would you like to write for Circuit Cellar? We are always accepting articles/posts from the technical community. Get in touch with us and let's discuss your ideas.

— ADVERTISMENT—

Advertise Here

Sponsor this Article
Website | + posts

Jeff Bachiochi (pronounced BAH-key-AH-key) has been writing for Circuit Cellar since 1988. His background includes product design and manufacturing. You can reach him at: jeff.bachiochi@imaginethatnow.com or at: www.imaginethatnow.com.

Supporting Companies

Upcoming Events


Copyright © KCK Media Corp.
All Rights Reserved

Copyright © 2024 KCK Media Corp.

Local Isolation: Using the Sun’s Energy

by Jeff Bachiochi time to read: 16 min