Project in progress
Network From Solar Street-Lamps

Network From Solar Street-Lamps © GPL3+

Interconnect solar street-lamps by adding monitoring and communication devices into them.

  • 3,201 views
  • 1 comment
  • 12 respects

Components and supplies

About this project

Warning: be extremely careful when working with high voltage devices (up to 60 volts in this case). The risks of sparks, damage, fire and death are present.

Work-in-progress: this project is in development. I will be sharing updates after each hardware, software or data progress. This is my first Arduino project, and numbers are approximations (uncalibrated). Thanks for reading!

Background

We installed about 12 solar street-lamps inside our farm as a first step to improve security, where only the main house in connected to the electric-grid.

Recently, security became a priority and we are aiming to install cameras and sensors on key points in the land; all solar powered.

We realized that we have many of these street-lamps available (installed and still-boxed), and we wonder if we could use them as solar-storage systems, in-addition-to or instead-of the lamp functionality.

About us

I'm a professional software/app developer with a lot of C background. I have been learning Arduino and microcontrollers for about three weeks before this project, and I'm finding this world very exciting to keep me busy at home between professional projects.

The owner of the property is the investor in this project. I'm using "we" and "us" to refer to him and me.

We are located in Central America; where the sun says "hello" from 6am to 6pm all year.

Our final goal

We want to insert into the lamps small, waterproof and removable Arduino devices for monitoring and data-transmission from Arduino, to Arduino, to server; in short, monitor and use the lamps as a transmission network.

We want to stream security footage to be stored in a local server and in the cloud; by attaching IP-cameras and wired/wireless network devices using the lamp's panel or battery as power source.

We hope the data collected by the Arduino network help us to understand if we are compromising too much the autonomy of the solar street-lamps; and how much solar power is not getting collected due to abundant sun-hours in our location.

First step

The first step is to collect data, so we need to build an Arduino module to monitor the voltage and current of a lamp; in other words, the charge and discharge behaviors of the battery and the panel.

The next step will be to add RF communication to pass the data to a central server for storage and remote monitoring.

The lamps and their internals

The lamps were imported from China by us. Luckily (or unluckily) the provider billed and sent the double amount of lamps, but we were ok with it since we find them very useful.

Their price is around U$250 each; and the manual states they can run up to 3 days without sun; IP66 waterproof; 5W of average consumption; 2 illumination levels, up to 100W when motion detected; solar panel should live 25 years; recommends the cleaning of the LEDs every 2-3 years; and mentions battery failure is possible after 2 years of usage.

The aluminum body is heavy and strong. The internals of the lamps can be easily accessed after removing two screws in the sensor plate. Luckily for us, the design is simple and modular, and the controller can be detached from the system with enough cable length to work with.

These were the first numbers with a multimeter:

  • Battery: 13-15V
  • Array of LEDs: 60V (high illumination mode)
  • Panel: 20V x2A when not charging; 15V x2.2A when charging; this means ~30W panel.

Monitoring prototype

After setting up a controlled environment; cutting, peeling, joining and protecting the cables the best I could, this was the first monitoring prototype:

The Arduino output is send each second via serial communications to the server device, in my case, my Mac or my Raspberry Pi, but you could use other.

I added a buzzer to the prototype, just to make sure the system is running, since from time to time the power device was turning off due to a bad USB cable, and due to my initial fear of possibly burning the house. Every 3 seconds I hear a small "beep" or get worried about its absence.

The server device is running a python script to capture the serial input and append the lines to an output text-file, including the current date and time on each line. Each line has the values separated by a tabulation and can be copy-pasted or opened in any spreadsheet program for post-processing, like Excel or Open/LibreOffice.

The Arduino code is below, in the Software section of this project.

Local server for capture and storage

For the local server this python program "read_serial.py" was added to the home folder of our RaspberryPi:

#!/usr/bin/env python3
import datetime
import serial
import sys
if __name__ == '__main__':
   ser = serial.Serial('/dev/ttyACM0', 9600, timeout=1)
   ser.flush()
   while True:
       if ser.in_waiting > 0:
           line = ser.readline().decode('utf-8').rstrip()
           now = datetime.datetime.now()
           file_object = open('/home/pi/samples.txt', 'a')
           file_object.write(now.strftime("%Y-%m-%d %H:%M:%S"))
           file_object.write("\t")
           file_object.write(line)
           file_object.write("\n")
           file_object.close()

This script can be executed from terminal for testing, like this:

 ./read_serial.py >> samples.txt

The output will be appended to the file "samples.txt", and the program can be interrupted by pressing Ctrl+C. You can monitor the growth of the "samples.txt" file with this command from other terminal:

tail -f samples.txt

Also can be interrupted by pressing Ctrl+C.

For a more long-term run, is better to create a system service that will start automatically with the server and will stop before the server is turned off:

cd /lib/systemd/system/
sudo nano read_serial.service

The "read_serial.service" file will have this body:

[Unit]
Description=Read Serial Arduino
After=multi-user.target
[Service]
Type=simple
ExecStart=/usr/bin/python /home/pi/read_serial.py
Restart=on-abort
[Install]
WantedBy=multi-user.target

Now lets set permissions, enable and start the service:

sudo chmod 644 /lib/systemd/system/read_serial.service
chmod +x /home/pi/read_serial.py
sudo systemctl daemon-reload
sudo systemctl enable read_serial.service
sudo systemctl start read_serial.service

Here a couple of useful commands:

sudo systemctl status read_serial.service
sudo systemctl stop read_serial.service

First monitoring

After setting up the local server and confirming the software is running as intended, was time to set up the panel on the roof, connect it to our monitoring prototype with long cables and wait for the first batch of data.

This is the representation of the first batch of data collected with the Arduino:

Next steps

  • Normal operation data: collect data of another couple of days.
  • Full discharge data: cover the solar panel and collect data until the battery is discharged.
  • Full charge data: turn off the lamp during the night and collect data until the battery is fully charged.
  • External/parasitic load data: attach my router (12V 2A) and modem (9V 1A) to the lamp's battery and collect data for 3 or more days.
  • RF communication: implement grid communication between Arduinos; I'm interested into implementing something from scratch; with focus on reduce collision and energy consumption.
  • Final modules: build miniature, waterproof and removable modules; and install them into the lamps.
  • App: build the app and services for remote monitoring, including alerts via push notifications.

Thanks for reading, I expect to update this project after completing each step.

Code

Monitoring solar street lamps voltage and currentArduino
/*
2020-08-06 by Marcos Ortega
Project: https://create.arduino.cc/projecthub/marcosjom/network-from-solar-street-lamps-e39e39

This code constantly reads the input from two voltage and current sensors,
builds and average about every 100ms, and 10 avgs for each second.

Each second an output is send to the serial port in CSV format, using tabulations as separator, as example:

Bat  14.48 v -0.684  a Pnl 0.00  v 0.293 a
Bat 14.48 v -0.586  a Pnl 0.00  v 0.342 a
Bat 14.45 v -0.586  a Pnl 0.00  v 0.293 a
Bat 14.45 v -0.684  a Pnl 0.00  v 0.244 a
Bat 14.45 v -0.684  a Pnl 0.06  v 0.244 a
Bat 14.70 v -0.635  a Pnl 0.06  v 0.195 a

A buzzer beeps every 3 secons.

*/

//---------
//- Header declarations
//---------

//Analog read

typedef enum ENAnalogSampleType_ {
  ENAnalogSampleType_BatteryV = 0,
  ENAnalogSampleType_BatteryA,
  ENAnalogSampleType_PanelV,
  ENAnalogSampleType_PanelA,
  ENAnalogSampleType_Count
} ENAnalogSampleType;

typedef struct STNBAnalogClock_ {
  unsigned long     readLastTime    = 0; //Last time a sample was read
  unsigned long     readWaitAccum   = 0; //Time accumulated since last read
  unsigned long     readsCount      = 0; //Ammount of read accumulated
  int               avgsCount       = 0; //Ammount of avgs populated
  int               avgsIdxLast     = 0; //Latest avg idx on circular queue
} STNBAnalogClock;

typedef struct STNBAnalogSample_ {
  int pin                 = A0; //analog pin
  unsigned long accum     = 0;  //current accumulation
  int avgs[10]            = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
} STNBAnalogSample;

typedef struct STNBAnalogReader_ {
  STNBAnalogClock    aClock; //reads coordinator
  STNBAnalogSample   samples[ENAnalogSampleType_Count]; //samples
} STNBAnalogReader;

//Buzzer

typedef struct STNBBuzzer_ {
  const int pin             = 52; //pin
  unsigned long lastActTime = 0;  //Last time a beep was made
  unsigned long waitAccum   = 0;  //Time accumulated since last beep
} STNBBuzzer;

//Config

typedef struct STNBCfg_ {
  //------
  //System
  //------
  struct {
    //Arduino reference voltage
    const float voltsRef = 5.0f; //(verify yours with a multimeter)
  } sys;
  //------
  //Voltage sensor
  //------
  struct {
    //Manual divider
    struct {
      const float r1 = 33000.f; //ohms
      const float r2 = 4700.f; //ohms
    } divider;
    //Pre-made module
    struct {
      const float r1 = 30000.f; //ohms
      const float r2 =  7500.f; //ohms
    } module;
  } voltage;
  //------
  //Current sensor
  //------
  struct {
    //Pre-made module
    struct {
      //ACS712 ELC-05; +/- 5A; 185 mV/A
      //ACS712 ELC-20; +/- 20A; 100 mV/A
      //ACS712 ELC-30; +/- 30A; 66 mV/A
      const float miliVoltsPerAmp = 100.f;
    } module;
  } current;
} STNBCfg;

//---------
//- Global objects
//---------

STNBAnalogReader state;
STNBBuzzer buzzer;
STNBCfg cfg;

//---------
//- Methods forward declaration
//---------

//Get the raw avg of all samples collected in the avgs-circular-queue
int samplesAvg(const STNBAnalogSample* src);

//Get the avg in voltage value collected from the voltage-sensor-module.
float samplesAvgAsVoltageFromSensor(const STNBAnalogSample* src);

//Get the avg in voltage value collected from the divider made with resistor.
float samplesAvgAsVoltageFromDivider(const STNBAnalogSample* src);

//Get the avg in current (amps) value collected from the current-sensor-module
float samplesAvgAsAmps(const STNBAnalogSample* src);

//---------
//- Setup
//---------

void setup() {
  //Init serial
  {
    Serial.begin(9600);
    while (!Serial) {
      ; // wait for serial port to connect. Needed for Native USB only
    }
  }
  //Turn off builtin-led
  {
    pinMode(LED_BUILTIN, OUTPUT);
    digitalWrite(LED_BUILTIN, LOW);
  }
  //Buzzer
  {
    pinMode(buzzer.pin, OUTPUT);// set digital IO pin pattern, OUTPUT to be output 
    buzzer.lastActTime = micros();
    buzzer.waitAccum = 0;
  }
  //Init analog reader
  {
    state.aClock.readLastTime  = micros();
    state.aClock.readWaitAccum = 0;
    state.aClock.readsCount    = 0;
    state.aClock.avgsCount     = 0;
    state.aClock.avgsIdxLast   = 0;
    {
      int i; for(i = 0; i < ENAnalogSampleType_Count; i++){
        STNBAnalogSample* ss = &state.samples[i];
        //Pin
        switch(i){
          case ENAnalogSampleType_BatteryV:
            ss->pin = A1;
            break;
          case ENAnalogSampleType_BatteryA:
            ss->pin = A2;
            break;
          case ENAnalogSampleType_PanelV:
            ss->pin = A3;
            break;
          case ENAnalogSampleType_PanelA:
            ss->pin = A4;
            break;
          default:
            //Error
            ss->pin = A0;
            break;
        }
        //Zeroes
        ss->accum = 0;
        {
          int i; for(i = 0; i < (sizeof(ss->avgs) / sizeof(ss->avgs[0])); i++){
            ss->avgs[i] = 0;
          }
        }
      }
    }
  }
}

//---------
//- Loop
//---------

void loop() {
  unsigned long curTime = micros();
  //Tick for IR detection
  {
    STNBAnalogClock* coord = &state.aClock;
    //Accumulate time
    if(coord->readLastTime < curTime){
      coord->readWaitAccum += curTime - coord->readLastTime;
    } else {
      coord->readWaitAccum = (4294967295 - coord->readLastTime) + curTime;
    }
    //Do one-tick action
    {
      int i; for(i = 0; i < ENAnalogSampleType_Count; i++){
        STNBAnalogSample* ss = &state.samples[i];
        const int val =  analogRead(ss->pin);
        ss->accum += val;
      }
      coord->readsCount++;
    }
    //Process accumulated events
    while(coord->readWaitAccum > 1000000){
      const int avgsArrSz = (sizeof(state.samples[0].avgs) / sizeof(state.samples[0].avgs[0]));
      {
        //Move index
        if(coord->avgsCount != 0){
          coord->avgsIdxLast++;
          if(coord->avgsIdxLast == avgsArrSz){
            coord->avgsIdxLast = 0;
          }
        }
        //Populate slots
        {
          if(coord->readsCount > 0){
            int i; for(i = 0; i < ENAnalogSampleType_Count; i++){
              STNBAnalogSample* ss = &state.samples[i];
              ss->avgs[coord->avgsIdxLast] = ss->accum / coord->readsCount;
            }
          } else {
            int i; for(i = 0; i < ENAnalogSampleType_Count; i++){
              STNBAnalogSample* ss = &state.samples[i];
              ss->avgs[coord->avgsIdxLast] = 0;
            }
          }
        }
        //Increase count
        if(coord->avgsCount < avgsArrSz){
          coord->avgsCount++;
        }
        //Calculate avgs and print
        {
          if(coord->avgsIdxLast == avgsArrSz - 1){
            //Battery
            {
              const float avgV  = samplesAvgAsVoltageFromSensor(&state.samples[ENAnalogSampleType_BatteryV]);
              const float avgA  = samplesAvgAsAmps(&state.samples[ENAnalogSampleType_BatteryA]);
              Serial.print("Bat\t"); 
              Serial.print(avgV, 2); Serial.print("\tv\t");
              Serial.print(avgA, 3); Serial.print("\ta\t");
            }
            //Panel
            {
              const float avgV  = samplesAvgAsVoltageFromDivider(&state.samples[ENAnalogSampleType_PanelV]);
              const float avgA  = samplesAvgAsAmps(&state.samples[ENAnalogSampleType_PanelA]);
              Serial.print("Pnl\t"); 
              Serial.print(avgV, 2); Serial.print("\tv\t");
              Serial.print(avgA, 3); Serial.print("\ta\t");
            }
            Serial.print("\n");
           }
        }
        //Reset counters
        {
          int i; for(i = 0; i < ENAnalogSampleType_Count; i++){
            STNBAnalogSample* ss = &state.samples[i];
            ss->accum = 0;
          }
        }
        coord->readsCount = 0;
      }
      coord->readWaitAccum -= 100000;
    }
    //Keep time
    coord->readLastTime = curTime;
    //Beep and delay
    {
      //Accumulate time
      if(buzzer.lastActTime < curTime){
        buzzer.waitAccum += curTime - buzzer.lastActTime;
      } else {
        buzzer.waitAccum = (4294967295 - buzzer.lastActTime) + curTime;
      }
      buzzer.lastActTime = curTime;
      //
      if(buzzer.waitAccum > 3000000){
        //Delay by beeing
        int i; for(i = 0;i < 10; i++){ // output a frequency sound
          digitalWrite(buzzer.pin, HIGH);// sound
          delay(1);
          digitalWrite(buzzer.pin, LOW);//not sound
          delay(1);
        }
        buzzer.waitAccum = 0;
      } else {
        //dealy one tick next read
        delay(10);
      }
    }
  }
}

//Get the raw avg of all samples collected in the avgs-circular-queue
int samplesAvg(const STNBAnalogSample* src){
  int total = 0, count = 0, i;
  for(i = 0; i < (sizeof(src->avgs) / sizeof(src->avgs[0])); i++){
    total += src->avgs[i];
    count++;
    break;
  }
  return total / count;
}

//Get the avg in voltage value collected from the voltage-sensor-module.
float samplesAvgAsVoltageFromSensor(const STNBAnalogSample* src){
  const float value = samplesAvg(src);
  const float vout  = (value * cfg.sys.voltsRef) / 1024.0f;
  const float vin = vout / ( cfg.voltage.module.r2 / (cfg.voltage.module.r1 + cfg.voltage.module.r2) ); 
  return vin;
}

//Get the avg in voltage value collected from the divider made with resistor.
float samplesAvgAsVoltageFromDivider(const STNBAnalogSample* src){
  const float value = samplesAvg(src);
  const float vout  = (value * cfg.sys.voltsRef) / 1024.0f;
  const float vin = vout / ( cfg.voltage.divider.r2 / (cfg.voltage.divider.r1 + cfg.voltage.divider.r2) ); 
  return vin;
}

//Get the avg in current (amps) value collected from the current-sensor-module
float samplesAvgAsAmps(const STNBAnalogSample* src){
  const int adcValAbs     = samplesAvg(src); //From 0 to 1024
  const int adcValSign    = adcValAbs - (1024 / 2); //From -512 to 512
  const float adcValSignRel = (float)adcValSign / (float)(1024 / 2); //From -1.0f to 1.0f
  const float adcMiliVolts = adcValSignRel * (cfg.sys.voltsRef * 1000.0f / 2.0f);
  const float currentValue = adcMiliVolts /  cfg.current.module.miliVoltsPerAmp;
  return currentValue; //+ 0.1465; //adjustment
}

//------------
//End-of-program
//------------

Schematics

Monitoring solar street lamps voltage and current
Modelproto01 01 3lr7tx0ki6

Comments

Similar projects you might like

Arduino - PV MPPT Solar Charger

Project tutorial by Abhi Verma

  • 46,032 views
  • 18 comments
  • 54 respects

Arduino-ESP WiFi Integration

Project in progress by Turai Botond

  • 18,725 views
  • 11 comments
  • 61 respects

Morse Code Communication Using Arduino

Project tutorial by Jalal Mansoori

  • 32,631 views
  • 21 comments
  • 48 respects

Morse Code Receiver

Project showcase by Alireza Karkon

  • 8,240 views
  • 5 comments
  • 28 respects

Measuring temperatures with LM35 and DS18B20

Project tutorial by jomar

  • 5,798 views
  • 1 comment
  • 7 respects

The Solar Lamp Works as Needed

by cvzeljko

  • 3,979 views
  • 6 comments
  • 11 respects
Add projectSign up / Login