Sunday, 2 April 2017

Web-enable the Tivoli model One with style

Turn your Tivoli Audio Model One into a web radio with Onion.io's Omega2


EDIT: you can now have a look at the build here on youtube.

Model One downsides

My Model One suffers from a few shortcomings:
  • interference from the switching power supplies of the kitchen lights
  • FM reception limited to local stations
  • AM unuseable because of interference
  • no short- or longwave reception
This particular Model One is the old version without the individual AUX setting. So with a plug in the AUX input, AM/FM reception is impossible.

So what I need is an underpiece to match the style of the Model One that provides reception of all my favourite stations, both domestic and foreign.


Underpiece design idea

Onion.io Omega2

When I was contacted by Randolf from Onion.IO, if I wanted to have a go at the Onion2, this looked like the ideal system to upgrade my Model One:

  • below 1 W power consumption (including USB audio dongle)
  • GPIOs easily accessible with the expansion dock (also has a serial-usb bridge and power regulator)
  • supported usb audio dongle available
  • all packages required for my project already available

Easy as Pi

I work with linux systems for a living and have quite a few RaspberryPis scattered around the house. I have also done projects with OpenWRT systems, so getting the Omega2 to work was an easy task. It is also extremely well documented.

Test setup


How it works

I designed the underpiece to have six pushbuttons. Each one connects one of the GPIO pins to the 3V3 rail.

The resistors are a bit of an afterthought. Still simple enough.



The command:
gpioctl dirin 1
sets GPIO #1 as an input. It's state can be queried with:

root@Omega-8D3B:/etc# gpioctl get 1
Using gpio pin 1.
Pin 1 is LOW
root@Omega-8D3B:/etc#

If scripted, this output needs a bit of tidying. I couldn't figure out where to get the value from the sysfs. If anyone knows, please let me know.

The only additional piece of software needed is mpg123 simply installed by:

opkg install mpg123

(You might need to run opkg update first)

On a microcontroller, reacting to the push of a button would normally be a job for an interrupt. The following script works without that. At the cost that the button has to be pressed for up to a second.

So when I momentarily connect any of the GPIOs (with the exception of GPIO6) to 3.3V, the script starts mpg123 with the appropriate stream URL.

The "radio.sh" script to be placed in /usr/bin

 #!/bin/ash

        gpioctl dirin 0
        gpioctl dirin 1
        gpioctl dirin 2
        gpioctl dirin 3
        gpioctl dirin 6
        gpioctl dirin 7
        gpioctl dirin 8
        gpioctl dirin 9


         COUNTER=0
         while [  $COUNTER -lt 100 ]; do
        #     echo The counter is $COUNTER
        #     let COUNTER=COUNTER+1


BUTTON0=$(gpioctl get 0 | grep Pin | cut -b 10)
BUTTON1=$(gpioctl get 1 | grep Pin | cut -b 10)
BUTTON2=$(gpioctl get 2 | grep Pin | cut -b 10)
BUTTON3=$(gpioctl get 3 | grep Pin | cut -b 10)
#BUTTON6=$(gpioctl get 6 | grep Pin | cut -b 10)
BUTTON7=$(gpioctl get 7 | grep Pin | cut -b 10)
BUTTON8=$(gpioctl get 8 | grep Pin | cut -b 10)
BUTTON9=$(gpioctl get 9 | grep Pin | cut -b 10)


if [ $BUTTON0 = 'H' ]; then
killall mpg123
               echo starting radio mode
                echo BBC 1
mpg123 http://bbcmedia.ic.llnwd.net/stream/bbcmedia_radio1_mf_p &
            fi


if [ $BUTTON1 = 'H' ]; then
killall mpg123
               echo starting radio mode
                echo BBC-World
mpg123 http://bbcwssc.ic.llnwd.net/stream/bbcwssc_mp1_ws-einws &
            fi



if [ $BUTTON2 = 'H' ]; then
killall mpg123
               echo starting radio mode
                echo France inter
mpg123 http://direct.franceinter.fr/live/franceinter-midfi.mp3 &
            fi

#world: http://bbcwssc.ic.llnwd.net/stream/bbcwssc_mp1_ws-einws

if [ $BUTTON3 = 'H' ]; then
killall mpg123
               echo starting radio mode
                echo SWR3
mpg123 http://swr-mp3-m-swr3.akacast.akamaistream.net/7/720/137136/v1/gnl.akacast.akamaistream.net/swr-mp3-m-swr3 &
            fi


#if [ $BUTTON6 = 'H' ]; then
#killall mpg123
#               echo starting radio mode
#               echo D-Radio
#mpg123 http://stream.dradio.de/7/249/142684/v1/gnl.akacast.akamaistream.net/dradio_mp3_dlf_m &
#            fi


if [ $BUTTON9 = 'H' ]; then
killall mpg123
               echo starting radio mode
                echo D-Radio Kultur
mpg123 http://stream.dradio.de/7/530/142684/v1/gnl.akacast.akamaistream.net/dradio_mp3_dkultur_m &
            fi


if [ $BUTTON8 = 'H' ]; then
killall mpg123
               echo starting radio mode
                echo D-Radio Wissen
mpg123 http://stream.dradio.de/7/698/142684/v1/gnl.akacast.akamaistream.net/dradio_mp3_dwissen_s &
            fi


        sleep 1
         done


The script is called from the file /etc/rc.local, so it starts when the Omega2 reboots:

 # Put your custom commands here that should be executed once  
 # the system init finished. By default this file does nothing.  
 /usr/bin/radio.sh & > /dev/null  
 exit 0  





#arduinoD17 project: Arduino decodes the BSIDE ADM20 serial infrared protocol

##### THIS  POST  NEEDS  MORE WORK ####

Due to the time constraints, my video on decoding the protocol is not quite as polished as usual, but possibly more "authentic".

A look inside

Although it did not make it into my review video, I one of the first things I did was taking the meter apart. Apart from making sure that the basic safety features were in place, the thing that caught my eye was the USB Interface.
It is completely a self-contained module powered by the PC over the USB cable. The CH340 USB-UART bridge chip should be familiar to anyone using Arduino knockoffs.
The module, of course is completly insulated against the meter, which sends a stream of serial data through a single infrared LED.


Where to get the multimeter
The meter is available under several names:

Other materials

... and as I have been asked about the cute scope I used:
It is around 20€ and easy to build. There is a firmware update available from JYE Tech. I'll do a video about the process.

The protocol

As mentioned previously, the meter sends a continuous stream of data at a rate of 2400 baud. This can be captured easily.
The only part of the protocol I figured out so far are the actual digits. I still have too look into the units and the "minus". The protocol is not "human-friendly", but feels like it is derived from the meter's data stream to the LCD module.


Code

So here is the code for you to try:

 //Meater-Reater (Arduino version)  
 //Arduino infrared interface for BSIDE ADM20 Multimeter  
 //see www.AReResearch.net for details  
 //20170401 by Andy Reischle  
 #include <SoftwareSerial.h>  
 // Softserial only required during development to preserve  
 // serial debugging. When done, move to hardware UART  
 SoftwareSerial swSer(8,9);  
 uint8_t inByte = 0;  
 int initvalues[6] = {0xAA, 0x55, 0x52, 0x24, 0x01, 0x10};  
 int line[22];  
 int measures[4];  
 int counter = 0;  
 int dval;  
 String result = "";  
 bool initstart = false;  
 void setup() {  
  Serial.begin(9600);  
  swSer.begin(2400);  
  Serial.println("\nStart reading from Soft UART");  
        }  
 void loop() {  
  // Serial.println("Doing my thing");  
  Serial.println(measure());  
  delay(5000);  
 }  
 String measure() {  
  initstart = true;  
  result="";  
  counter=0;  
  swSer.flush();  
  while (initstart) {  
   if (swSer.available() > 0)  
   {  
   inByte = swSer.read();  
 // Serial.print(inByte, HEX);  
 //  Serial.print(" ");  
   if (counter >5 )  
    {  
     measures[counter-6] = inByte;  
     //Serial.print("result: ");  
     //Serial.println(inByte, HEX);  
     if (counter == 9)  
      {  
       if (measures[3] > 128) result = result + ".";  
       result = result + displval(measures[3]);  
       if (measures[2] > 128) result = result + ".";  
       result = result + displval(measures[2]);  
       if (measures[1] > 128) result = result + ".";  
       result = result + displval(measures[1]);  
       if (measures[0] > 128) result = result + ".";  
       result = result + displval(measures[0]);  
       // Serial.println (result);  
       return result;  
       initstart = false;  
      }  
     counter++;  
     if (counter > 9)  
      {  
       counter = 0;  
       result="";  
       initstart = false;  
      }  
    }  
   else if (inByte == initvalues[counter])  
    {  
     counter ++;  
    }  
                 }  
       }  
 }  
 //  
 // Convert display values  
 //  
 int displval(int dval)  
 {  
 if (dval > 128)  
  {  
   dval = dval -128;  
  }  
  if (dval == 95) return 0;  
  if (dval == 6) return 1;  
  if (dval == 107) return 2;  
  if (dval == 47) return 3;  
  if (dval == 54) return 4;  
  if (dval == 61) return 5;  
  if (dval == 125) return 6;  
  if (dval == 7) return 7;  
  if (dval == 127) return 8;  
  if (dval == 63) return 9;  
  if (dval == 0) return 0;  
 }  

Friday, 10 March 2017

Detect CO with a MQ-7 sensor module

How to detect carbon monoxide with a MQ-7 sensor module

How gas sensors work

I found an excellent thesis paper on how Tin Dioxide (SnO2) gas sensors work here. It also goes into the details of it's temperature dependency. (See details further down)

Video

Watch my video on the tests here.

The module from ICStation

You can get this module here. (Use code andyics for 15% off your order)
The intended mode of operation is to apply 5V to the module and either read analog values from AOUT or set the threshold of the comparator to the desired value and read from the DOUT pin if it has tripped.


The terminals

Comparator and trimmer


It seems to me that ICStation treats all MQ-series sensors the same way. But the MQ-7 is different from the rest. According to the data sheet, it gives the best results on the following cycle:

  • Pre-heat sensor for 48h 
  • Heat heater with 5V for 60 seconds
  • Heat at 1.4V for 90 seconds
  • Read the sensor near the end of the 90 seconds
On 5V alone, the module does "sortof" work.

You can see me breathing at the sensor

I am quite sure there is no siginificant quantity of CO in my breath. And I not a smoker. The sensor reacts to a wide range of gases, as well as moisture and ambient temperature,

Tricking the module into datasheet-like conditions

To build this, you need the following components:



The IRLZ34N is a very common N-Channel MosFET what already has a very low (0.046 Ohm) source-drain resistance with 5V at the gate. It can handle currents way beyond our reqirements for the flimsy heater on the module.
The heater can run on DC or AC, so PWM should be ok. I can then set the duty cycle of the PWM so that it is the equivalent of 1.4V. (See code below.)


The 10k resistor is optional

The setup with the "switching" mosfet.
Setup with Mosfet



The Arduino code

The code for the "proper" usage cycle:

 /*  
 MQ-7 cheater  
 Uses PWM and an N-Channel MosFET to trick an ICSTATION MQ-7 CO detector  
 into measuring CO according to the datasheet of the manufaturer.  
 */  
 int sensorPin = A0;  // select the input pin for the CO sensor  
 int sensorValue = 0; // variable to store the value coming from the sensor  
 // Initial setup  
 void setup() {  
  // initialize digital pin LED_BUILTIN as an output  
  pinMode(LED_BUILTIN, OUTPUT);  
  // initialize the serial port  
  Serial.begin(9600);  
 }  
 // the loop function runs over and over again forever  
 void loop() {  
  analogWrite(LED_BUILTIN, 255);  // turn the heater fully on  
  delay(60000);            // heat for 60 second  
 // now reduce the heating power  
  analogWrite(LED_BUILTIN, 72);  // turn the heater to approx 1,4V  
  delay(90000);            // wait for 90 seconds  
 // we need to read the sensor at 5V, but must not let it heat up. So hurry!  
  digitalWrite(LED_BUILTIN, HIGH);  
  delay (50); //don't know how long to wait without heating up too much. Getting an analog read apparently takes 100uSec  
   // read the value from the sensor:  
  sensorValue = analogRead(sensorPin);  
  Serial.println(sensorValue);  
 }  

Increased sensitivity

Under the same conditions (candle suffocated under jar), the FET-Pulsed version showed a significantly higher peak.
FET-Pulsed heater

Heater on 5v constantly
While the pulsed version of the detector has a slower detection rate (once every 2.5 minutes), the signal's signal-to-noise ratio is signigicantly better (400:14 vs 220:28), resulting in better sensitivity.

Other options:

Cut the traces on the PCB and rewire, so the heater and the sensor don't run from the same power source. (I.e. run cycle the heating element at 5/1.4, while keeping constant 5V on the sensing element's voltage divider)

Friday, 24 February 2017

Hacking the BSIDE ADM20 Multimeter - Software

BSIDE ADM20 hack 1: Software

How I got into this

When I worked on a review of a battery charger, I came accross some potential issues that I had to investigate things more thoroughly. I needed a multimeter to record the charge curves.
So my contact at Gearbest sent me this BSide ADM20 Multimeter. This has a built-in USB interface to display and record mesaurements on the PC.
Values imported into LibreOffice Calc
It turned out I quite like the meter. See my review video here. (Hardware-hack will follow) The software however was rather basic and wouldn't allow to set a sample rate or measurement duration.

The meter is available under several names:

I already had a look inside the meter and see pretty cool options to turn this into an IoT device. But let's not jump to conclusions. Some more work needs to go into that and I have only focussed on the software side here.

Plug&Play

Fortunately it is pretty obvious how the meter communicates with (or rather "to") the PC:
A new COM port appears, presented through the well known CH340 USB-to-SERIAL bridge driver.
And you thought COM-Ports were a thing of the past
If you then fire up the software (DMM Data logger) that came with the meter, you're good to go.

Original Software

Nooo! Boooooooring!!!!

A look at the protocol

Pretty obvious that I should see something when I start a a terminal program like TeraTerm od Putty.
In part 2 of this post, you'll see that this is strictly a one-way communication. So we can't talk back to the meter.

  • The port speed is 2400 baud.
  • There is no CR or LF at the end of each data set (see below)
  • The usual 8n1 seems to apply
  • Continuous stream of data: no xon/xoff
  • No return channel

With the width set properly, TeraTerm's hex mode shows a pattern:
The 5Fs are the Zeroes, the DF has the decimal point

Whatever I do, the transmission always starts with a series of HEX values: AA5552240110
followed by four bytes that change when stuff moves on the display. I could map the values to the following displayed digits: (excerpt from my visual basic prog)

        If SerVal = 95 Then measured = 0
        If SerVal = 6 Then measured = 1
        If SerVal = 107 Then measured = 2
        If SerVal = 47 Then measured = 3
        If SerVal = 54 Then measured = 4
        If SerVal = 61 Then measured = 5
        If SerVal = 125 Then measured = 6
        If SerVal = 7 Then measured = 7
        If SerVal = 127 Then measured = 8
        If SerVal = 63 Then measured = 9

It turns out that the most significant bit is the decimal point, the other bits map to the seven segments. It also sends the measured unit and the polarity further back in the data stream. Up to now I choose to ignore all of that.

The four bytes with the four digits are in reverse order, of course, for more programming fun.

So my VisualBasic program listens for the "AA555224110" sequence and then decodes the four following bytes.

I suspect that the data stream is derived from the communication with the display driver, as many bits in the data stream can directly be mapped to segments on the display.

More on those details in the second part where I will look at the hardware of both the meter and it's communication.

First try in VisualBasic

No decimal point yet.
That was once a 9v battery

If you want to have a go at the experimental code, here is where I left off for the moment:

 Imports System.Threading.Tasks  
 Imports System.Timers  
 Imports System.IO  
 Imports System.IO.Ports  
 Imports System.Threading  
 Public Class Form1  
   Dim datensatz As String  
   Dim rohwert As Integer  
   Dim werte(22) As Integer  
   Dim decodewerte(4) As Integer  
   Dim recorddata As Boolean = False  
   Dim i As Integer = 0  
   Delegate Sub DataDelegate(ByVal sdata As Integer)  
   REM Define the method (Function) that will be called by the Invoke method   
   Private Sub PrintData(ByVal sdata As Integer)  
     Dim startsequence As String = "AA555224110"  
     Dim tmpchar As String  
     Dim str As Integer  
     Dim measured As Integer  
     Dim x As Integer  
     If recorddata Then  
       werte(i) = sdata  
       Console.Write("I= ")  
       Console.WriteLine(i)  
       If i = 4 Then  
         recorddata = False  
         i = 0  
         tmpchar = Hex(werte(1))  
         REM Console.WriteLine(werte(1))  
         x = DecodeValue(werte(1))  
         decodewerte(1) = x  
         Console.WriteLine(x)  
         Label2.Text = x  
         tmpchar = Hex(werte(2))  
         REM Console.WriteLine(werte(2))  
         x = DecodeValue(werte(2))  
         decodewerte(2) = x  
         Console.WriteLine(x)  
         Label3.Text = x  
         tmpchar = Hex(werte(3))  
         REM Console.WriteLine(werte(3))  
         x = DecodeValue(werte(3))  
         decodewerte(3) = x  
         Console.WriteLine(x)  
         Label4.Text = x  
         tmpchar = Hex(werte(4))  
         REM Console.WriteLine(werte(4))  
         x = DecodeValue(werte(4))  
         decodewerte(4) = x  
         Console.WriteLine(x)  
         Label5.Text = x  
         TextBox1.Text = CStr(decodewerte(4)) & CStr(decodewerte(3)) & CStr(decodewerte(2)) & CStr(decodewerte(1))  
         sp.DiscardInBuffer()  
       End If  
       i = i + 1  
     End If  
     tmpchar = Hex(sdata)  
     Label1.Text = tmpchar  
     datensatz = datensatz + tmpchar  
     Console.WriteLine(datensatz)  
     If (datensatz.Contains(startsequence)) Then  
       REM Console.WriteLine("Got Header")  
       datensatz = ""  
       recorddata = True  
     End If  
   End Sub  
   Public Sub New()  
     ' This call is required by the designer.  
     InitializeComponent()  
     ' Add any initialization after the InitializeComponent() call.  
   End Sub  
   Dim WithEvents sp As New SerialPort  
   Private Sub GetSerialPortNames()  
     sp.BaudRate = 2400  
     sp.PortName = "COM3"  
     sp.Open()  
     sp.DataBits = 8  
     sp.Parity = Parity.None  
     sp.StopBits = StopBits.One  
     sp.Handshake = Handshake.None  
     REM sp.Encoding = System.Text.Encoding.Default  
     sp.Encoding = System.Text.Encoding.Default  
   End Sub  
   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load  
     GetSerialPortNames()  
   End Sub  
   Private Sub SerialPort_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles sp.DataReceived  
     Dim str As Integer  
     REM Dim str2 As Char  
     str = sp.ReadChar()  
     REM Console.WriteLine(str)  
     REM str2 = Convert.ToChar(str)  
     Dim adre As New DataDelegate(AddressOf PrintData)  
     Me.Invoke(adre, str)  
   End Sub  
   Function DecodeValue(ByVal SerVal As Integer)  
     Dim decimalpoint As Boolean = 0  
     Dim measured As Integer  
     If SerVal > 128 Then  
       SerVal = SerVal - 128  
       decimalpoint = True  
     End If  
     measured = 99  
     If SerVal = 95 Then measured = 0  
     If SerVal = 6 Then measured = 1  
     If SerVal = 107 Then measured = 2  
     If SerVal = 47 Then measured = 3  
     If SerVal = 54 Then measured = 4  
     If SerVal = 61 Then measured = 5  
     If SerVal = 125 Then measured = 6  
     If SerVal = 7 Then measured = 7  
     If SerVal = 127 Then measured = 8  
     If SerVal = 63 Then measured = 9  
     If SerVal = 8097 Then measured = 7  
     If SerVal = 8096 Then measured = 1  
     If SerVal = 0 Then measured = 0  
     Console.Write("Decoder got a: ")  
     Console.Write(SerVal)  
     Console.Write(" decoded as: ")  
     Console.WriteLine(measured)  
     Return measured  
   End Function  
 End Class  

If you have done work on hard- or software-hacking those meters please let me know.


Saturday, 18 February 2017

Can't make voice call with Siri - Solution

Since the upgrade to iOS 10, I could not get Siri to dial any numbers from my address book.
I changed quite a few of Siri's settings back and forth, but was unable to fix the problem. Resets and restarts would not help either.

What finally helped was a rather unexpected:


  • I switched off the "Dial Assist" function in the "Phone" settings and Siri was able to make calls again.
  • I then switched "Dial assist" back on and Siri could still dial without any issues.




Problem solved.
I can just assume, that some not otherwise accessible parmeter has been set to a valid value when switching Dial Assist off and on.

Sprachwahl mit Siri funktioniert nicht: Lösung

Siri hat ein Problem:

Seit dem Update auf iOS 10 konnte ich per Siri - Sprachwahl nicht mehr wählen. Neustart oder die Änderung von Siri - Einstellungen blieben wirkungslos.

Keine Sache der Einstellung

Die Lösung für dieses Problem liegt nicht in den Siri-Einstellungen, sondern ist unter "Telefon" zu finden:

  • Nachdem ich die Funktion "Wählhilfe" abeschaltet hatte, ging die Sprachwahl.
  • Wählhilfe wie der aktiviert: Siri kann noch wählen.


Ich kann nur spekulieren, daß ein nicht anderweitig zugänglicher Parameter durch das Aus- und einschalten der Wählhilfe korrekt gesetzt wurde.

Friday, 3 February 2017

Using the Nitecore SC2 superb charger

Superb charger

on Nitecore's web pages for the "SC2 superb charger", there is no shortage of superlatives. Charging with "Infinite Intelligence" certainly is the boldest claim.



See for yourself >>> HERE <<< in my review video on YouTube.

Infinite intelligence

So I tried to find out more about the limits of this "active charging with infinite intelligence" thing. Gearbest sent me a unit for review and I put it through a few tests to see how it performs.

First impression

The charger feels quite tough & beefy to the to touch. The kind of plastic that doesn't feel plasticy, but rather reminds of high quality power tools. A good start.

Second impression

But why-oh-why are the settings for the current and max. charge voltage printed on what looks like a sheet of protective film that easily comes off the display.
I'm sure if I don't constantly use that charger, I'll have forgotten what which LED means what after a few days. Nitecore has to do something about that in the next iteration of the SC2.

Automatic battery capacity detection

Now this should be what is at the heart of this ominous infinite intelligence. The traditional way to determine the battery's capacity is to discharge them and measure the capacity that they charge to, possibly adding another discharge/charge cycle. Nitecore's web pages claim that it automatically detects the battery's capacity and sets the charge current accordingly.
So come on SC2, impress me!

Test setup

First, I have to know about my cell's actual capacity. To find that out, I use an improved version of my previously published Arduino battery tester.

Makeshift battery tester


I use four cells for testing:
  1. AWT 18650 35A 3000 mAh IMR cell 
  2. Ultrafire 18650 3000mAh (Really is only 300mAh)
  3. KeepPower 14500 800mAh 
  4. Ultrafire 14500 1200mAh (Really a little over 200 mAh)
With automatic battery capacity detection, The good quality cells AWT and KeepPower cells should charge at a higher rate than the Ultrafires (who in all tests had only a fraction of their nominal capacity, regardless of the charger.)

Size matters

As cells with the same size charge at the same current, I wondered what happened if I make the 14500 appear bigger. So I inserted a spacer with the 14500 cell.
14500 to 18650 converter :-)


Nut&bolt spacer inserted
It charged at 2A. Way above the recommended 0,4-0,8A (Unconfirmed, from reseller pages)

The assumption that bigger cells hold more capacity might on the whole be correct. But we all know that there are good and bad cells and that makes way more of a difference than the size.

The limits of infinite intelligence

There is more that will confuse the charger:
  1. LiFePO4 cells: The charger can't distinguish LiFePO4 cells from Li-Ion cells and would overcharge them. So the maximum charge voltage has to be set manually
  2. 3,8V Li-Ion cells. I've never had or seen any of these, But as they are indistinguishable from a 3.7V cell, The charge voltage needs to be set manually.

Example Charge

While I had initially observed some brief overvoltage conditions, All the voltage (and charge curves I took) did not exhibit that phenomenon.
0,5A charge curve
I did many of the long-term measurements with PC on the USB-Port of the bside ADM20 Multimeter that I quite like. I also got from Gearbest to get a better grip on some problems I suspected with the SC2 charger.
bside ADM20 at work with a current shunt resistor
The multimeter turned out to be quite hackable. But that will be a whole new blog entry & video.

Final word

Pros:
  • Great charger if you're in a hurry. Very fast and reasonably safe.
  • Supports all currently available battery chemistry types.
Cons:
  • Sometimes has trouble disabling the protection circuit in KeepPower batteries.
  • Needs help choosing the right battery chemistry.