20Wp battery-stored solar energy

Parts

Here is the list of parts using in this project:

  • 20Wp monocrystalize solar panel, $20
  • CN3722 solar charger, $7
  • 9-cell 18650 lithium battery, used, $10
  • ESP32, $5
  • TI INA3221 current-voltage sensor, $3
  • Temperature sensors: DS18B20, MCP9808
  • Neopixel
  • DC buck-down (12 -> 5V)
  • Box to house battery, MCU, sensor, $5
  • wire, hot glue

Total cost: ~$60 (est.) plus one week working (on and off)

Coding

If you want to try out the code right away, check out the file on GitHub. Because many parts involved, for problem-shooting, just start with basic sensor such as INA3221 and move on with other sensors.

For INA3221, here a function to call and read data from the sensor:

void read_INA3221(DynamicJsonDocument &doc){
    
    float shuntvoltage1 = 0;
    float busvoltage1 = 0;
    float current_mA1 = 0;
    float loadvoltage1 = 0;
    
    busvoltage1 = ina3221.getBusVoltage_V(CHANNEL_1);
    shuntvoltage1 = ina3221.getShuntVoltage_mV(CHANNEL_1);
    current_mA1 = ina3221.getCurrent_mA(CHANNEL_1); 
    loadvoltage1 = busvoltage1 + (shuntvoltage1 / 1000);
    doc["CH1"]["busV"] = busvoltage1;
    doc["CH1"]["shmV"] =  shuntvoltage1;
    doc["CH1"]["loadV"] =  loadvoltage1;
    doc["CH1"]["mA"] =  current_mA1;
    
    float shuntvoltage2 = 0;
    float busvoltage2 = 0;
    float current_mA2 = 0;
    float loadvoltage2 = 0;
    
    busvoltage2 = ina3221.getBusVoltage_V(CHANNEL_2);
    bat_vol = busvoltage2;
    shuntvoltage2 = ina3221.getShuntVoltage_mV(CHANNEL_2);
    current_mA2 = ina3221.getCurrent_mA(CHANNEL_2);
    loadvoltage2 = busvoltage2 + (shuntvoltage2 / 1000);
    
    doc["CH2"]["busV"] = busvoltage2;
    doc["CH2"]["shmV"] =  shuntvoltage2;
    doc["CH2"]["loadV"] =  loadvoltage2;
    doc["CH2"]["mA"] =  current_mA2;
    
    float shuntvoltage3 = 0;
    float busvoltage3 = 0;
    float current_mA3 = 0;
    float loadvoltage3 = 0;
    
    busvoltage3 = ina3221.getBusVoltage_V(CHANNEL_3);
    shuntvoltage3 = ina3221.getShuntVoltage_mV(CHANNEL_3);
    current_mA3 = ina3221.getCurrent_mA(CHANNEL_3);
    loadvoltage3 = busvoltage3 + (shuntvoltage3 / 1000);
    
    doc["CH3"]["busV"] = busvoltage3;
    doc["CH3"]["shmV"] =  shuntvoltage3;
    doc["CH3"]["loadV"] =  loadvoltage3;
    doc["CH3"]["mA"] =  current_mA3;
    
    if (DEBUG){
        Serial.println("------------------------------");
        Serial.printf("CHANNEL_1 Bus Voltage:   %.1f V\n",busvoltage1 );
        Serial.printf("CHANNEL_1 Shunt Voltage: %.1f mV\n", shuntvoltage1);
        Serial.printf("CHANNEL_1 Load Voltage:  %.1f V\n", loadvoltage1);
        Serial.printf("CHANNEL_1 Current 1:     %.1f mA\n", current_mA1);
        Serial.println("");
    
        Serial.printf("CHANNEL_2 Bus Voltage 2:   %.1f V\n",busvoltage2);
        Serial.printf("CHANNEL_2 Shunt Voltage 2: %.1f mV\n", shuntvoltage2);
        Serial.printf("CHANNEL_2 Load Voltage 2:  %.1f V\n", loadvoltage2);
        Serial.printf("CHANNEL_2 Current 2:       %.1f mA\n", current_mA2);
        Serial.println("");    
        
        Serial.printf("CHANNEL_3 Bus Voltage 3:   %.1f V\n",busvoltage3);
        Serial.printf("CHANNEL_3 Shunt Voltage 3: %.1f mV\n", shuntvoltage2);
        Serial.printf("CHANNEL_3 Load Voltage 3:  %.1f V\n", loadvoltage2);
        Serial.printf("CHANNEL_3 Current 3:       %.1f mA\n", current_mA2);
        Serial.println("");
    }
}

Then, in the main loop, we call read_INA3221 when needed:

void loop(){
    // main program here
    ArduinoOTA.handle();
    uint32_t now_ = millis()/1000;
    if ((now_-lastPush)>=INVL){
        DynamicJsonDocument doc(1024);
        doc["sensor"] = SENSORNAME;
        doc["uptime"] = now_;
        read_INA3221(doc);
        read_MCP9808(doc);
        read_DS18B20(doc);
        read_MAX44009(doc);
        ctrl_mosfet(bat_vol); // control mosfet
        doc["expt"] = mosfet_on;
        push_Data(doc);
        lastPush = now_;
        
        JsonObject obj = doc.to<JsonObject>(); // empty doc object
        led_bat(bat_vol); // show the voltage by color
        
        if (now_ > 86400L){
        ESP.restart();
        }
    }
    delay(1000);
}

You can see from the main loop, other sensors are called by seperated function (modular) rather one big block of code. This way is easier to maintain the code. The debug is more pleasant by checking the function in main loop, then go the function lines rather go line by line.

The data is transferred by MQTT server and logged by file or by database. These are options for you to consider.

If you are using Linux with Python 3, you may encounter this error while compiling the file:

Traceback (most recent call last):
File "/home/uno/.arduino15/packages/esp32/tools/esptool_py/3.0.0/esptool.py", line 38, in 
import serial
ImportError: No module named serial

On my system (Ubuntu 20.04 LTS), this message means the pyserial is not found for esptool.py to work. One way to solve this error is:

  1. Download pyserial from Github:
  2. Extract zip file and cd into the folder
  3. In terminal windown, run python setup.py install

Refer to this post on stackoverflow.com.