FarmBot is an open-source CNC farming machine

Comment

FarmBot is an open-source CNC farming machine

With hopes of reinventing the way food is grown, Rory Aronson has developed humanity’s first open-source CNC farming machine. The FarmBot Genesis — which will begin taking pre-orders in July — is capable of planting seeds and then watering them precisely.
— https://blog.arduino.cc/2016/06/07/farmbot-is-an-open-source-cnc-farming-machine/

Arduino.cc have a nice write up of what looks to be a nice open source project; FarmBot Genesis 

Comment

EmonCMS and multiple OpenTRV nodes

1 Comment

EmonCMS and multiple OpenTRV nodes

As a follow up on my initial experiments with getting data from OpenTRV to Open Energy Monitor this post is going to build on that and add support for parsing data received from the other OpenTRV nodes other than the one directly connected via the USB serial.

The Hardware

The hardware I am using is a Rev 2 board as the receiver as described previously and the transmitters are two Rev 11 boards. Due to the design of the firmware however this should work the same on any of the devices.

Firmware

To get these to talk to each other we need to ensure the firmware for on the receiver device has the ENABLE_STATS_RX build option defined and on the transmitting end ENABLE_STATS_TX needs to be defined.

Testing

With the new firmware built and flashed it is time to power up and see what we get. Load up the Node-RED UI with the work sheet we created last time. Turn off all the debug nodes apart from the last one of the switch node. This will now only show the console output we are not parsing, (you may need to wait about 10 mins to see anything).

You should see some JSON output that looks suspiciously like the end of the status lines from last time. It is probably of no surprise that in fact in is the same format as the JSON part of the status text. So we already have the code to parse this text.

Parsing The Output

First things first though, we need to separate out the JSON lines from the rest of the unhandled Lines. Double click on the switch node and add a new entry above the otherwise entry. Set this to look for a regex and enter the following;  

^{

This will now pull out the JSON by detecting the opening  { . 

Now connect a new function node to a new switch output and grab the JSON code we wrote before; 

var obj = JSON.parse('{'+parts[16]+'}');
for(var i in obj) 
{
    switch(i)
    {
        case '@':
            newMsg.nodeid = obj['@'];
            break;
        case 'T|C16':
            var temp16 = parseInt(obj[i]);
            var temp = ((temp16 & ~0xf) >> 4) + 
                       ((temp16 & 0xf) / 16);
            newMsg.payload.temp = temp;
            break;
        default:
            var mapping = {
                'B|mV': 'battery',
                'occ|%': 'occupancy',
                'v|%': 'valve',
                'O': 'occupancyState',
                'vac|h': 'vacancyHours',
                'L': 'ambientLight',
                'H|%': 'humidity',
                'vC|%': 'cumulativeValveMovement',
                'tT|C': 'nominalTarget'
            }
            
            if(mapping[i]) {
                newMsg.payload[mapping[i]] = obj[i];
            }
    }
}

We have to make a few changes to make this stand alone. First the JSON is coming from msg.payload  rather than parts[16] and we are going to write the parsed output back directly to msg.payload. The reason for this will become clear soon.

var json = msg.payload;
msg.payload = {};

var obj = JSON.parse(json);
for(var i in obj) 
{
    switch(i)
    {
        case '@':
            msg.nodeid = obj['@'];
            break;
        case 'T|C16':
            var temp16 = parseInt(obj[i]);
            var temp = ((temp16 & ~0xf) >> 4) + 
                       ((temp16 & 0xf) / 16);
            msg.payload.temp = temp;
            break;
        default:
            var mapping = {
                'B|mV': 'battery',
                'occ|%': 'occupancy',
                'v|%': 'valve',
                'O': 'occupancyState',
                'vac|h': 'vacancyHours',
                'L': 'ambientLight',
                'H|%': 'humidity',
                'vC|%': 'cumulativeValveMovement',
                'tT|C': 'nominalTarget'
            };
            
            if(mapping[i]) {
                msg.payload[mapping[i]] = obj[i];
            }
    }
}

return msg;

Now let's insert a debug node and test see if this works.

Sending to EmonCMS

Now all that is left to do disconnect the function output to the EmonCMS node, or is it? You may have noticed I the previous example that we entered the EmonCMS node ID in the settings of the EmonCMS node in Node-RED. Now we are receiving from more than one OpenTRV device we need to translate the OpenTRV ID to an EmonCMS node ID. So we need to write a function to map the IDs from one system to another. 

Place a function node after the two parsing nodes. and before the EmonCMS node;

Our parsing code is writing the OpenTRV ID in to msg.nodeid and if the Node setting is left blank the EmonCMS node will usemsg.nodegroup for the Node. This can be done with the following;

var nodeMapping = {
    'f9ea': 26,
    'a7de': 27
};

if(msg.nodeid && nodeMapping[msg.nodeid]) {
    msg.nodegroup = nodeMapping[msg.nodeid];
}

return msg;

You will need to alter the content of nodeMapping for your environment.

As we are dealing with more than just msg.payload it is handy to debug the whole msg object. To do this you can open a debug node's settings (double click it) and change the Output to complete msg object.

 
 

The other change we need to make is to clear the fixed Node setting in the EmonCMS node. Open the EmonCMS node settings and delete anything in the Node setting. Don't worry if the setting highlights red, this can be ignored as we are passing in the setting for this value. A warning may also be given when deploying, this too can be ignored.

 
 

We can now deploy the sheet and it should look something like this;

If all is working you should now see all the OpenTRV nodes showing data in your EmonCMS.

Making Improvements

We could leave it there, but there is a bit of tidy up we can do to help reduce the maintenance. For example the newer versions of the firmware send the battery level using 'B|cV'. To add support for this we would have to edit two similar pieces of code. We can easily update the code to put all the JSON parsing in a single node.

Instead of parsing the JSON in the OpenTRV Parse Status we can pass the JSON along to the OpenTRV Parse JSON node so it can be parse there. Lets disconnect the output of the OpenTRV Parse Status from the EmonCMS node and to the inout of theOpenTRV Parse JSON so we have something that looks like this;

Now first lets stop OpenTRV Parse Status from parsing JSON and pass it on. We do not want the JSON to be part of themsg.payload as we have not parsed it, only extracted it from the status line so we are going to pass it on as msg.json. Delete the JSON parsing code and replace with the following;

// JSON block, 16
if(null !== parts[16]) 
{
    newMsg.json = '{'+parts[16]+'}';
}

Now in the OpenTRV Parse JSON node we need to pick up any JSON data in msg.json and parse it while also keeping the ability to parse the JSON coming directly from the serial. Replace the first two lines with the following;

var json = "";
if(msg.json) 
{
    json = msg.json
}
else
{
    json = msg.payload;
    msg.payload = {};
}

We can now deploy and test that we are getting the same data uploaded to EmonCMS.

Now we have the JSON parsing in one location lets fix the previously mentioned issue with not parsing the new battery voltage. The easiest fix for this is to change the B|mV to B|cV and if you just setting up the system this is absolutely fine. In my case however I have some historical data so I am going to scale the voltage to mV before passing on, add the following to the switch statement;

        case 'B|cV':
            msg.payload.battery = parseInt(obj[i]) * 10;
            break;

While we are updating the actual JSON parsing code another change I am going to make is to not drop the unknown JSON values we do not lose any data. Be warned however if you are using an older version of EmonCMS this may not be supported. Change the following;

            if(mapping[i]) {
                msg.payload[mapping[i]] = obj[i];
            }

to;

            if(mapping[i]) {
                msg.payload[mapping[i]] = obj[i];
            } else {
                msg.payload[i] = obj[i];
            }

Finally one last change we need to make. The JSON frames sent from our remote OpenTRV devices include the '+' property, an incrementing count to help with missing frame detection so we can just give it a name in our mapping;

            var mapping = {
                '+': 'frame',
                'B|mV': 'battery',
                'occ|%': 'occupancy',
                'v|%': 'valve',
                'O': 'occupancyState',
                'vac|h': 'vacancyHours',
                'L': 'ambientLight',
                'H|%': 'humidity',
                'vC|%': 'cumulativeValveMovement',
                'tT|C': 'nominalTarget'
            };

So in the end you should end up with something like;

var json = "";
if(msg.json) 
{
    json = msg.json
}
else
{
    json = msg.payload;
    msg.payload = {};
}

var obj = JSON.parse(json);
for(var i in obj) 
{
    switch(i)
    {
        case '@':
            msg.nodeid = obj['@'];
            break;
        case 'T|C16':
            var temp16 = parseInt(obj[i]);
            var temp = ((temp16 & ~0xf) >> 4) + 
                       ((temp16 & 0xf) / 16);
            msg.payload.temp = temp;
            break;
        case 'B|cV':
            msg.payload.battery = parseInt(obj[i]) * 10;
            break;
        default:
            var mapping = {
                '+': 'frame',
                'B|mV': 'battery',
                'occ|%': 'occupancy',
                'v|%': 'valve',
                'O': 'occupancyState',
                'vac|h': 'vacancyHours',
                'L': 'ambientLight',
                'H|%': 'humidity',
                'vC|%': 'cumulativeValveMovement',
                'tT|C': 'nominalTarget'
            };
            
            if(mapping[i]) {
                msg.payload[mapping[i]] = obj[i];
            } else {
                msg.payload[i] = obj[i];
            }
    }
}

return msg;

So now we can deploy and check the results;

The Quick Way

Finally just if you get stuck here is the flow to import in to Node-RED;

[{"id":"c6a48855.395b78","type":"emoncms-server","z":"","server":"http://localhost/emoncms","name":"EmonCMS"},{"id":"e914c08d.16eb4","type":"serial-port","z":"1cd21157.e32def","serialport":"/dev/ttyUSB1","serialbaud":"4800","databits":"8","parity":"none","stopbits":"1","newline":"\\n","bin":"false","out":"char","addchar":false},{"id":"9fccc574.603338","type":"serial in","z":"1cd21157.e32def","name":"","serial":"e914c08d.16eb4","x":171,"y":327,"wires":[["f5140166.0aec"]]},{"id":"f5140166.0aec","type":"function","z":"1cd21157.e32def","name":"Trim","func":"msg.payload = msg.payload.trim();\nreturn msg;","outputs":1,"noerr":0,"x":371,"y":327,"wires":[["895c112b.76a3f","6b5f8388.94a07c"]]},{"id":"895c112b.76a3f","type":"debug","z":"1cd21157.e32def","name":"","active":true,"console":"false","complete":"true","x":551,"y":267,"wires":[]},{"id":"6b5f8388.94a07c","type":"switch","z":"1cd21157.e32def","name":"","property":"payload","propertyType":"msg","rules":[{"t":"regex","v":"^$","vt":"str","case":false},{"t":"eq","v":">","vt":"str"},{"t":"regex","v":"^=","vt":"str","case":false},{"t":"regex","v":"^{","vt":"str","case":false},{"t":"else"}],"checkall":"false","outputs":5,"x":551,"y":327,"wires":[["429c7c77.bd6384"],["64924701.9b6db8"],["350ff59f.caf00a","4983e01b.b67c2"],["21d5644.fde2a9c","bfa41a66.e84468"],["b10edb47.a92cd8"]]},{"id":"429c7c77.bd6384","type":"debug","z":"1cd21157.e32def","name":"","active":false,"console":"false","complete":"false","x":770,"y":180,"wires":[]},{"id":"64924701.9b6db8","type":"debug","z":"1cd21157.e32def","name":"","active":false,"console":"false","complete":"false","x":770,"y":220,"wires":[]},{"id":"350ff59f.caf00a","type":"debug","z":"1cd21157.e32def","name":"","active":false,"console":"false","complete":"false","x":770,"y":260,"wires":[]},{"id":"21d5644.fde2a9c","type":"debug","z":"1cd21157.e32def","name":"","active":false,"console":"false","complete":"false","x":770,"y":380,"wires":[]},{"id":"4983e01b.b67c2","type":"function","z":"1cd21157.e32def","name":"OpenTRV Parse Status","func":"var newMsg = {  };\n\nvar parts = msg.payload.match(/^=(F|W|B)([0-9]+)%@([0-9]+)C([0-9A-F]+)(;X([0-9]+))?(;T([0-9]+ [0-9]+) ([^;]*))?(;S([0-9]+) ([0-9]+) ([0-9]+))?(;H([0-9]+ [0-9]+)*)?;?.*{(.*)}$/);\nif(null === parts) {\n    node.error(\"Failed to parse input\", msg);\n    return null;\n}\n\nnewMsg.payload = {\n    //'raw': parts,\n    'frost': 'F' == parts[1] ? 1 : 0,\n    'warm': 'W' == parts[1] ? 1 : 0,\n    'bake': 'B' == parts[1] ? 1 : 0,\n    'valve': parts[2],\n    'temp': parseInt(parts[3]) + (parseInt(\"0x\"+parts[4]) / 16)\n};\n\n// Security, 5-6\nif(null !== parts[5]) {\n}\n\n// Time, 7-8\nif(null !== parts[7]) {\n}\n\n// Program, 9\nif(null !== parts[9]) {\n}\n\n// Target temps, 10-13\nif(null !== parts[10]) \n{\n    newMsg.payload.nominalTarget = parseInt(parts[11]);\n    newMsg.payload.frostTarget = parseInt(parts[12]);\n    newMsg.payload.warmTarget = parseInt(parts[13]);\n}\n\n// House code, 14-15\nif(null !== parts[14]) {\n}\n\n// JSON block, 16\nif(null !== parts[16]) \n{\n    newMsg.json = '{'+parts[16]+'}';\n}\n\nreturn newMsg;","outputs":1,"noerr":0,"x":810,"y":300,"wires":[["bfa41a66.e84468","9b8ae703.987188"]]},{"id":"297b83ac.d6847c","type":"debug","z":"1cd21157.e32def","name":"","active":true,"console":"false","complete":"true","x":1550,"y":300,"wires":[]},{"id":"d742c271.28bd4","type":"emoncms","z":"1cd21157.e32def","name":"Emoncms","emonServer":"c6a48855.395b78","nodegroup":"","x":1560,"y":340,"wires":[]},{"id":"bfa41a66.e84468","type":"function","z":"1cd21157.e32def","name":"OpenTRV Parse JSON","func":"var json = \"\";\nif(msg.json) \n{\n    json = msg.json\n}\nelse\n{\n    json = msg.payload;\n    msg.payload = {};\n}\n\nvar obj = JSON.parse(json);\nfor(var i in obj) \n{\n\tswitch(i)\n\t{\n\t\tcase '@':\n\t\t\tmsg.nodeid = obj['@'];\n\t\t\tbreak;\n\t\tcase 'T|C16':\n\t\t\tvar temp16 = parseInt(obj[i]);\n\t\t\tvar temp = ((temp16 & ~0xf) >> 4) + \n\t\t\t\t\t   ((temp16 & 0xf) / 16);\n\t\t\tmsg.payload.temp = temp;\n\t\t\tbreak;\n        case 'B|cV':\n            msg.payload.battery = parseInt(obj[i]) * 10;\n            break;\n\t\tdefault:\n\t\t\tvar mapping = {\n\t\t\t    '+': 'frame',\n\t\t\t\t'B|mV': 'battery',\n\t\t\t\t'occ|%': 'occupancy',\n\t\t\t\t'v|%': 'valve',\n\t\t\t\t'O': 'occupancyState',\n\t\t\t\t'vac|h': 'vacancyHours',\n\t\t\t\t'L': 'ambientLight',\n\t\t\t\t'H|%': 'humidity',\n\t\t\t\t'vC|%': 'cumulativeValveMovement',\n\t\t\t\t'tT|C': 'nominalTarget'\n\t\t\t};\n\t\t\t\n\t\t\tif(mapping[i]) {\n\t\t\t\tmsg.payload[mapping[i]] = obj[i];\n\t\t\t} else {\n\t\t\t\tmsg.payload[i] = obj[i];\n\t\t\t}\n\t}\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":1070,"y":340,"wires":[["8f1b296d.4a5cb8","251ad4fd.73893c"]]},{"id":"b10edb47.a92cd8","type":"debug","z":"1cd21157.e32def","name":"","active":false,"console":"false","complete":"false","x":770,"y":420,"wires":[]},{"id":"8f1b296d.4a5cb8","type":"function","z":"1cd21157.e32def","name":"Map ID for EmonCMS","func":"var nodeMapping = {\n    'f9ea': 26,\n    'a7de': 27\n};\n\nif(msg.nodeid && nodeMapping[msg.nodeid]) {\n    msg.nodegroup = nodeMapping[msg.nodeid];\n}\n\nreturn msg;\n","outputs":1,"noerr":0,"x":1340,"y":340,"wires":[["d742c271.28bd4","297b83ac.d6847c"]]},{"id":"9b8ae703.987188","type":"debug","z":"1cd21157.e32def","name":"","active":false,"console":"false","complete":"true","x":1010,"y":300,"wires":[]},{"id":"251ad4fd.73893c","type":"debug","z":"1cd21157.e32def","name":"","active":false,"console":"false","complete":"true","x":1290,"y":300,"wires":[]}]

1 Comment

Comment

Raspberry Pi FTDI shim

The first boards I have designed and then had manufactured have arrived;

upload.jpg

These are a simple adapter to allow connecting of an FTDI style cable to a Raspberry Pi or alternatively (and what I actually designed them for) connecting a Raspberry Pi peripheral that uses the serial pins to an FTDI adapter.

This was also my first experiment with KiCAD and I am quite pleased with the outcome.

The circuit is fairly basic. One 5x2 connector for the Raspberry Pi, one 6x1 connector for the FTDI adapter and two jumpers to configure if the serial data points are connected RX to TX, to enable communication with the Pi, or TX to TX and RX to RX, to enable communication with an add-on board.

g1250.png

Here is the finished adapter;

upload.jpg

and here with a RFM69Pi v3 and FTDI friend;

upload.jpg

The PCBs were manufactured by Ragworm.

The KiCAD files can be found on GitHub.

Comment

"Bring Me The Bacon" Challenge

Comment

"Bring Me The Bacon" Challenge

The challenge we’ve laid down is to receive a bacon sandwich at one location and deliver it to another location. The twist is that this must be completed autonomously by a ground based robot.
— http://roverchallenge.net/

... and I more one salute our bacon delivering overlords.

BigJungle will be sponsoring an entry into this challenge, follow the progress on our project page.

Comment

Connect Maker to anything - IFTTT

15 Comments

Connect Maker to anything - IFTTT

IFTTT have recently released a new channel for easy integration with 'DIY' projects.

For those of you that have not heard about IFTTT (If This Then That) it is a way of linking events on one service to actions on another. For example some of the things I use it for are;

  • If I post a Facebook status message then send a tweet 
  • If I upload a new YouTube video post to Facebook
  • If I weigh on my Fitbit Aria log the weight in a Google Drive Spreadsheet

You get the idea, if something happens then do something. This is all great, apart from you are limited to only the 'channels' supported by IFTTT. That is until now.

The new Maker channel can both receive events via a simple HTTP/REST API and make simple HTTP requests in response to events from other channels. This enables easy integration with your own custom projects.

As an example lets make a button press send a Tweet via IFTTT.

IFTTT setup

We will start off by connecting to the Twitter channel: https://ifttt.com/twitter;

Next we need to connect to the Maker channel so go to https://ifttt.com/maker and select Connect;

Connecting to the Maker channel

Connecting to the Maker channel

Now we are connected to the twitter and Maker channel we need to link the two. This is done using recipes. To create a new recipe click on your username in the top right and select Create;

Creating a new recipe

Creating a new recipe

Then follow through the steps;

The Hardware

Now for the hardware. For this I am using Adafruit's HUZZAH ESP8266 Breakout as this is a bit more friendly for development but this should work just as well on any ESP8266 board like the ESP-01.

It is a fairly simple circuit, just an LED for status (not strictly required with the HUZZAR as it has an LED on board but helpful for other ESP boards) and a button with a pull up resistor.

The Software

The software is nice and simple, it is based on the WiFi Client sample code from the ESP8266 Arduino core as well as the button de-bounce example.

/*
 *  This sketch sends data via HTTP GET requests to IFTTT Maker channel service.
 *
 *  You need to  privateKey from the Maker channel on IFTTT and paste it
 *  below. 
 *
 * Based on the example code for WiFi client and button debounce example from Arduno IDE
 */

#include <ESP8266WiFi.h>

void send_event(const char *event);

// constants won't change. They're used here to
// set pin numbers:

// Wifi setup
const char *ssid     = "WiFi_SSID";
const char *password = "Password";

// IFTTT setup
const char *host = "maker.ifttt.com";
const char *privateKey = "xxxxxxxxxxxxxxxxxxxxxx";

// Hardware setup
const int buttonPin = 2;     // the number of the pushbutton pin
const int ledPin = 0;        // the number of the LED pin

// Variables will change:
int buttonState;             // the current reading from the input pin
int lastButtonState = LOW;   // the previous reading from the input pin

// the following variables are long's because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long lastDebounceTime = 0;  // the last time the output pin was toggled
long debounceDelay = 50;    // the debounce time; increase if the output flickers

void setup() 
{
  // Set your pin modes
  pinMode(buttonPin, INPUT);
  pinMode(ledPin, OUTPUT);
  
  // Bring up the serial for a bit of debugging
  Serial.begin(115200);
  delay(10);

  // We start by connecting to a WiFi network
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  // Wait for the connection, flashing the LED while we wait
  int led = HIGH;  
  while (WiFi.status() != WL_CONNECTED) {
    delay(200);
    digitalWrite(ledPin, led);
    led = !led;
  }
  digitalWrite(ledPin, LOW);

  Serial.println("");
  Serial.println("WiFi connected");  
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void loop() 
{
  // read the state of the switch into a local variable:
  int reading = digitalRead(buttonPin);

  // check to see if you just pressed the button
  // (i.e. the input went from LOW to HIGH),  and you've waited
  // long enough since the last press to ignore any noise:

  // If the switch changed, due to noise or pressing:
  if (reading != lastButtonState) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) 
  {
    // whatever the reading is at, it's been there for longer
    // than the debounce delay, so take it as the actual current state:

    // if the button state has changed:
    if (reading != buttonState) 
    {
      Serial.print("Button now ");
      Serial.println(HIGH == reading ? "HIGH" : "LOW");
      buttonState = reading;

      // When the button is in the LOW state (pulled high) the button 
      // has been pressed so send the event.
      if (buttonState == LOW) {
        send_event("button_pressed");
      }
    }
  }

  // save the reading.  Next time through the loop,
  // it'll be the lastButtonState:
  lastButtonState = reading;
}

void send_event(const char *event)
{
  // set the LED on whle we are sending the event
  digitalWrite(ledPin, HIGH);

  Serial.print("Connecting to ");
  Serial.println(host);
  
  // Use WiFiClient class to create TCP connections
  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    Serial.println("Connection failed");
    return;
  }
  
  // We now create a URI for the request
  String url = "/trigger/";
  url += event;
  url += "/with/key/";
  url += privateKey;
  
  Serial.print("Requesting URL: ");
  Serial.println(url);
  
  // This will send the request to the server
  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" + 
               "Connection: close\r\n\r\n");

  // Read all the lines of the reply from server and print them to Serial,
  // the connection will close when the server has sent all the data.
  while(client.connected())
  {
    if(client.available())
    {
      String line = client.readStringUntil('\r');
      Serial.print(line);
    } else {
      // No data yet, wait a bit
      delay(50);
    };
  }
  
  // All done
  Serial.println();
  Serial.println("closing connection");

  client.stop();
  
  // Finished sending the message, turn off the LED
  digitalWrite(ledPin, LOW);
}

In Action


And just to finish off here is a link to another tutorial on using the Maker channel.

15 Comments

Solar Fairy Light Teardown

1 Comment

Solar Fairy Light Teardown

I have a plan to setup an automated watering system for our vegetable patch as we are constantly forgetting to water the them. It will also be really handy for times when we are away for a long period of time.

The first step is to get some data is to if the plants actually need water. I only have a limited supply of water so I do not want to waste any.

So I need a sensor in each bed monitoring the soil moisture. First on the list of requirements is it has to run outside so it needs to be waterproof. Second I do not want to be running a lot of cables around that are going to get damaged while digging so it needs to be wireless and have its own power supply, so a battery. Lastly I need some way of charging the battery so of course this means a solar panel. 

It just so happens I have something that meet some of these criteria. Yes, you have probably guessed it, some solar fairy lights. Given the ones I have are in use it is off to Amazon to get some that I can tear apart.

After very little searching I bought some "Blingstring Outdoor Solar Fairy Lights - White 100 LEDs" and a few days later...

As you can see the Solar Fairy Lights consists of the main unit with the solar panel, a string of LEDs a day and few fixings.

Of some mild interest, the LEDs are wired in two sets of 50 in opposite directions. So if you apply power directly to the LED cable only half light up. If you reverse the connection the other half light up, but I am mainly interested in the the Solar panel so the LEDs can be saved for something else. 

So lets pull apart the Solar panel unit and see what is inside...

As you could have probably guessed it is fairly simple. There is the Solar panel, a battery and a small PCB. Lets look a bit closer.

The Solar panel is a QFsolar 11176-8L, I cannot find much information on this from a quick internet search. but some quick probing with a multimeter it looks to be a 6V panel capable of supplying ~90mA in reasonable sunlight so probably a 1W panel. 

The battery is a basic 3 cell (3.6V) Ni-MH rechargeable battery rated at 450 mAh so should be ok for my project if I am careful about power usage when there is low power from the Solar panel.

And finally there is the control circuitry;

The main chip (HS173ND14-J) looks to be a custom chip, nothing really approaching a data sheet shows up while searching, but that is ok as it probably only controls the LEDs rather than the charging.

If I overlay the bottom of the PCB of the top as bellow you can see that there is a diode connecting the battery to the solar panel.

That diode is essentially the charging circuit, allowing current to flow from the solar panel to the battery and thus charging the battery but stopping current from flowing in the other direction when the voltage of from the solar panel drops. This is explained nicely in Simple Solar Power Circuit with Rechargeable Battery Backup.

So as a final step I am going to remove all the bits I do not need and add some breadboard friendly headers ready for some experiments.

I welcome any (constructive) feedback in the comments below



1 Comment