Building a Lithium battery for my electric scooter

Disclaimer

I am not a battery expert nor am I claiming to be one! I got all my information from the internet and I think designed and build my battery correctly.

Always use common sense and do not blindly trust the information I (or anybody else) am/is putting on the internet.

Novox C20

In 2010 I bought a Novox C20 25km/h electric scooter. Novox was (they are gone) Dutch manufacturer of electric scooters. It seems that they (partially) bought the scooters in China and rebranded them to their brand Novox. It seems that many parts of it were actually from the Chinese manufacturer Tysong.

I previously wrote a dutch post and a english post about this scooter.

The model I have is the 25km/h version equipped with a 2.5kW electric motor with 4 12V 40Ah batteries.

Realistically this scooter had a range of about 35 ~ 40km before running out of juice.

25km/h

In the Netherlands we have two types of scooters. We call them snorfiets (25km/h, blue license plate) or bromfiets (45km/h, yellow plate). My scooter is a snorfiets which means I do not have to wear a helmet.

The primary purpose for the scooter is going to the beach in summer, use it when it’s nice weather and when I want to go somewhere which is a bit to far to go by bicycle.

4x 12V 40Ah

Originally the scooter had 4 12V 40Ah batteries which (in theory) is roughly 1.9kWh of capacity. These were lead acid batteries and each weight 12kg, so the total weight of the batteries was nearly 50kg.

Over the years the range and performance declined until the batteries died completely. After reading about DIY batteries using 18650 Lithium batteries I decided to build my own!

Designing the battery

After I decided to design and build my own battery I went out to gather information. I watched endless hours of videos on YouTube and I also bought a book:

With the information from YouTube, that book and other sources I designed a 14S15P battery.

14S15P

A 14S15P configuration means 14 Cells in Series and 15 Cells Parallel. The total amount of cells would be: 210 (14*15).

The nominal voltage of the battery would be 50.4V (3.6*14) and the maximum voltage 58.8V (4.2*14).

The existing controller in the scooter would not be able to handle that voltage so I would have to swap the controller. That’s something I’ll explain in a upcoming post.

On AliExpress I searched for 18650 battery cell holders for a 14S5P pack and ordered three sets. Combining them would give me a 14S15P battery pack which would physically fit in my scooter.

Samsung INR18650-35E

During my search for the correct cell I selected the Samsung INR18650-35E cell. This cell has a capacity of 3.500mAh and 210 of them would sum up to a total capacity of 2646Wh (3.6 * 3500 * 210 / 1000) or 2.6kWh for my scooter.

Each cell can deliver 10A of current so 15 of them in parallel would be able to provide 150A of current. With a nominal voltage of 50V that would be 7500W or 7.5kW.

As the motor in my scooter is rated for 2.5kW I would only need 50A of current. 50A or 2.5kW was my design target. Anything above it would be a bonus.

After searching I found the Dutch shop Nkon which has a good reputation and was able to deliver the INR18650-35E for a good price. So I ordered 220 cells (10 spare).

Next to the cells I ordered 15m of 0.15mm Nickel strip to use when spotwelding all the cells together.

Battery Management System

The next important thing was a Battery Management System (BMS). A BMS makes sure the cells stay in a healthy state by doing various things:

  • Protecting them against under -or overvoltage
  • Protecting against a high charge or discharge current
  • Making sure the cells are balanced

There are good and bad BMS out there. At first I thought I’d buy one on AliExpress but after reading on the dutch forum of Tweakers in the topic about electric scooters I purchased a TinyBMS s516 from Energus Power Solutions. It’s not the cheapest, but it does do its job: Protecting my 210 cells!

In addition to the BMS I ordered:

  • USB programming cable
  • Temperature sensors
  • Bluetooth module
  • Balancing Wires

Spotwelding

To connect all the cells together you need to use spotwelding as soldering would damage the cells badly due to the heat.

At first I bought a Sunko 709A spotwelder on AliExpress. Short story: A waste of money!

After searching a bit more I found a Arduino based spotwelder made by Malectrics in Germany.

This spotwelder works great with a Bosch 12V 44A 440A car battery. I made a short video and put it on YouTube.

In addition a picture of the spotwelder connected to the battery.

I never used a spotwelder before nor did I build a battery. I started with building a small 3S5P 12V battery to test my welding skills.

Safety First! Remember to wear gloves and eye protection when working with a spotwelder and/or batteries. Also make sure there are no loose tools or wires on your workplace and again, use common sense!

I am now using this battery as a DIY powerbank with a 12V and USB output.

Building it

After I got some practice on building the small battery I just started working on the big battery. Cutting nickel to length and I started to build the blocks.

This took a lot of time, probably over 40 hours as I slowly build my first big battery and still needed to learn.

I ended up stacking two strips of 0.15mm nickel on top of each other to handle my 50A target current.

You can also see the balancing wires of the BMS go to the positive terminal of each series. The manual of the BMS has clear instructions on how to wire them.

From the Nickel I went to 8AWG wire which I connected to a SB50 connector. Using that connector I would connect the battery Plus and Minus to the BMS and the BMS to the controller. The SB50 connector is rated for 50A and thus meets my requirements. I got the connectors from a local supplier.

Using a tape, lexan and some screw the end result (without BMS) looked like this:

And with my TinyBMS connected to it:

The tiny red wires are the balancing wires used by the BMS to monitor the cells in series and balance charge them when needed.

BMS settings

Using a Windows tool you can monitor and configure the BMS using a USB cable (which you have to buy).

There are various settings you can change, but the main values I set were:

  • Low voltage cut-off: 3.25V
  • High voltage cut-off: 4.15V
  • Maximum discharge currect: 75A

Although the cells can range from 3.2V to 4.2V I decided to take a 0.05V safety margin to increase the lifetime of the cells. I might lower the maximum voltage per cell to 4.1V in the future, but that’s something I still have to decide.

Using the BMS tool you can see the voltage of each individual series of cells and see them change while charging the battery.

Charging and Charger

To charge this battery I also needed a new charger as the old charger was not suited for Lithium and the voltage was too low.

I bought a 58.8V/5A charger on AliExpress which I’m using for now. It does seem to do the job just fine for now. The BMS isn’t complaining (yet).

Using XT90 connectors I connect my charger to the BMS although the scooter has a XLR input. But internally I’m connecting all those wires using XT90 connectors.

I got my XT90 connectors at Hobbyking as they also have a local warehouse in the Netherlands and have a good reputation.

Weight

The four old batteries had a combined weight of nearly 50kg where the new battery weighs slightly less than 12kg with everything connected.

That’s a weight reduction of 38kg! That will benefit acceleration, handling and range positively.

End result

The end result is a 2,6kWh battery consisting out of 210 cells connected in a 14S15P configuration with a nominal/maximum voltag of 50.4V/58.8V.

My initial calculations and tests tell me that when driving ~30km/h (Yes, I did some tuning!) the motor draws about 20A. That’s about 1000W at 50V.

Since the battery is 2600Wh I should be able to drive for roughly 2 hours and 30 minutes at 30km/h before the battery is empty.

30km for 2.5 hours would mean a range of 75km which is more then enough for my driving habits with this scooter.

With the original batteries I had a range of roughly 40km, but I never tried if it actually got that far. I think I improved the range with nearly 75% with this new battery.

In this picture you see the battery installed in the scooter and hooked up to the controller. A post about the controller and connecting it all will follow later.

Costs

The total costs for the cells, BMS, wire and connectors is about EUR 940,00 which can be broken down into:

  • Cells: EUR 650,00
  • BMS: EUR 200,00
  • Wires: EUR 40,00
  • Connectors: EUR 50,00
  • Time: 50 hours!

For me it was a cool project and something I could learn a lot from.

BIG Thanks

There are a few people and sources I have to thank big-time for their information on the internet.

  • jehugarcia on YouTube
  • User flippy.nl on Tweakers.net
  • All the other people sharing information!

I wouldn’t have been able to do this without those people. Thanks!

Performance

The battery is currently installed in the scooter and I’m still in the process of fine-tuning the controller.

My initial test drives tell me that the performance improved a lot! Acceleration to 30km/h is a lot quicker and I feel the motor has a lot more torque than it had before. This is mainly due to the battery being able to provide the current required to power the motor. The weight reduction can also be felt clearly and also contributes to the improved acceleration.

One of the cool things is that the scooter now has regenerative breaking thanks to the new controller.

A post regarding the controller and connecting it all will follow. Stay tuned!

Getting connection URL from (Flask) SQLAlchemy

While working with Flask‘s SQLAlchemy extension I ran into the case where I wanted to obtain the host I was connected to combined with the username and password.

As I didn’t want to parse the URL I provided earlier to SQLAlchemy (and I didn’t have it available at that part of the code) I looked for a method to obtain it from SQLAlchemy.

It turns out this is not (or poorly) documented, but the Engine object has a attribute called url.

>>> db.engine.url
mysql+mysqlconnector://sqlmanager:***@localhost/sqlmanager?charset=utf8
>>>

This is actually a sqlalchemy.engine.url.URL object which contains attributes with the connection information:

>>> url = db.engine.url
>>> url.host
'localhost'
>>> url.username
'sqlmanager'
>>> url.password
'supersecretpassword'
>>> url.database
'sqlmanager'
>>> 

This was the information I needed and allowed me to use this information elsewhere.

Calculating SLAAC IPv6 Address in Java

SLAAC

With IPv6 a host on a network can use StateLess Address AutoConfiguration (SLAAC) to configure it’s network.

Routers will send out Router Advertisements telling the network which subnet is used in the network.

Based on their MAC address (modified EUI-64) a host will then obtain a IPv6 it will use.

Java

For the Apache CloudStack project I had to write Java code which would take a subnet and a MAC address as an argument and would generate a IPv6 SLAAC address from it.

Combining subnet 2001:db8:100::/64 with MAC address 06:7a:88:00:00:8b yields IPv6 address 2001:db8:100:0:47a:88ff:fe00:8b.

/*
 * Java code using Java-ipv6 from Google Code to convert
 * a given IPv6 subnet and a MAC address into a IPv6 address
 * calculated using SLAAC.
 *
 * Author: Wido den Hollander 
*/
import com.googlecode.ipv6.IPv6Address;
import com.googlecode.ipv6.IPv6Network;

public class IPv6EUI64 {
    public static IPv6Address EUI64Address(final IPv6Network cidr, final String macAddress) {
        if (cidr.getNetmask().asPrefixLength() > 64) {
            throw new IllegalArgumentException("IPv6 subnet " + cidr.toString() + " is not 64 bits or larger in size");
        }

        String mac[] = macAddress.toLowerCase().split(":");

        return IPv6Address.fromString(cidr.getFirst().toString() +
                Integer.toHexString(Integer.parseInt(mac[0], 16) ^ 2) +
                mac[1] + ":" + mac[2] + "ff:fe" + mac[3] +":" + mac[4] + mac[5]);
    }

    public static void main(String[] argv) {
        IPv6Network cidr = IPv6Network.fromString("2001:db8:100::/64");
        String mac = "06:7a:88:00:00:8b";
        IPv6Address eui64addr = EUI64Address(cidr, mac);

        /* This will print 2001:db8:100:0:47a:88ff:fe00:8b */
        System.out.println(eui64addr);
    }
}

The code can also be found on my Github Gist page.

From Middelburg to Trondheim

To Hirtshals

Last Saturday we left at 08:00 from Middelburg for the 1.100km drive to Hirtshals, Denmark. From there we would take the ferry to Larvik, Norway on Sunday morning.

It took us 14 hours to reach Hirtshals. Traffic was bad, very bad starting at Hamburg towards the border. Roadworks and border controls made it stop and go over almost 100km!

A short night followed since our ferry left at 08:00.

Lier South SuperCharger

After arriving in Larvik our first SuperCharger in Norway was Lier South, 100km from Larvik.

It was busy! After we parked all 8 stalls are occupied. Other Model S had to wait in the queue.

Lier South SuperCharger

A queue is bad, but it also shows that the infrastructure is used! It’s not a charger which is rarely used. From what I understood it was also a vacation period, so that might have caused the spike in traffic.

Lillehammer

After charging in Lier we headed to Lillehammer. We would stay the night there and charge again.

Fortum CHAdeMO

While heading to Lillehammer I stopped at a CHAdeMO from Fortum to see if I could charge there. The people from Fortum told me that I could use my Dutch phone and send a SMS to active it.

Well, that didn’t work. I borrowed a RFID tag from somebody else as a backup. On the Lofoten Islands I will need to use a Fortum charger, so I wanted to know if it worked. Lesson learned. It doesn’t.

Fortum CHAdeMO charger

Busy times at Lillehammer

On the E6 to Lillehammer we already spotted a lot of Model S coming from Lillehammer, so I expected the SuperCharger to be crowded.

It was! 9 of the 10 stalls we busy, so we parked at the last stall available.

As we were charging we saw more Model S arrive. We still had 100km left in the battery and we would leave the next morning. We vacated the stall and to decided to charge the next morning for the 155km drive to Dombas and Trondheim.

We checked in at the hotel and went for a dinner in Lillehammer.

SuperCharging with a cold battery

The next morning the car had been in -8C for the night. When I switched to ‘Drive’ a warning indicated that regenerative braking had been disabled. This was due to the battery being cold.

SuperCharging didn’t go very fast. When I just started it would charge with 17kW and slowly climbed to roughly 30kW before we had enough to leave for Dombas.

This was a similar experience as last year at the Krokom SuperCharger in -22C.

The picture below shows that we were charging with 24kW where under normal conditions it should have been about 80kW.

Slow Lillehammer SuperCharger

To Trondheim

From Lillehammer we drove to the Dombas SuperCharger. After a charge and lunch there we headed down to Klett (near Trondheim).

Nothing really special on this part of the trip. The temperature was about -5C and the (road) conditions were good.

To the Lofoten

Our destination is a house we rented through Airbnb on the Lofoten Islands.

From Trondheim we are taking the Hurtigruten ferry to Stamsund on the Lofoten. This will take 2 days.

From Stamsund to the house it is just 21km. Time to relax!

Energy Consumption

The tripmeter shows 1861km and a total usage of 391kWh. That’s 210Wh/km. Not bad at all!

Ceph Monitors are laggy or clock might be skewed

This weekend I got to investigate a Ceph cluster which had issues where the Monitors were constantly performing new elections.

After some investigation on of the three monitors was eating 100% CPU on a single core and kept printing this in the logs:

mon.charlie@2(peon).paxos(paxos updating c 106399655..106400232) lease_expire from mon.0 [2a00:XXX:121:XXX::6789:1]:6789/0 is 2.380296 seconds in the past; mons are probably laggy (or possibly clocks are too skewed)

Digging further I found that the LevelDB store in /var/lib/ceph/mon/X/store.db was 2.5GB in size.

Compact on Start

You can tell the monitor to compact the LevelDB database on start. Add the following to your ceph.conf:

[mon]
mon compact on start = true

Now restart the monitor and it will compact the LevelDB database.

The CPU usage now dropped and the monitors were happy again.

Last SuperCharger in Sweden, going to Norway

Leaving Sweden

In the past two days we visited Stockholm and continued our journey towards Norway.

We both never visited Stockholm before, so yesterday morning we visited Stockholm. We saw Gamla Stan (Old City) and visited the Abba Museum. (My dad is a Abba fan!)

From there on we continued to the Sollentuna and Gävle SuperChargers. We spend the night in Gävle and today we continued towards the Krokum SuperCharger via the Sundsvall.

Nothing really special actually. It started snowing and the temperatures started to drop, it’s -11 Celcius right now and snow is still falling.

Heading to Norway

Tomorrow we are going to drive from Krokum to the Grong SuperCharger in Norway. This is a 250km trip via what it seems some very small towns.

It looks like we have to cross mountains as well, so we’ll do a 100% charge at the Krokum SuperCharger before heading to Grong in Norway.

250km should be doable under the worst conditions possible with a full battery, so it’s looking good. We’ll be in Norway tomorrow!

Energy Consumption

I’m keeping a detailed spreadsheet with the energy consumption during the trip and we are currently at 225Wh/km over 2200km. That is really not bad, I expected higher consumption.

Flash LSI 2308 to IT-mode on a SuperMicro X10SL7-F mainboard

For a new SSD-only Ceph deployment I got my hands on a couple of SuperMicro SYS-1018D-73MTF 1U server with 8 Intel SSDs.

Great machines, but by default SuperMicro ships them with the IR (Integrated RAID) firmware and I wanted to use the IT (JBOD) firmware.

The X10SL7 mainboard has a UEFI bios, so flashing the controller was rather easy.

First, download the latest firmware for the LSI 2308 controller from SuperMicro. At this moment it is the PH19-IT firmware.

Now we have to create a small disk image with a FAT32 filesystem to use as a virtual drive for the IPMI.

I run Ubuntu on my desktops and laptops, so this is how I created that small disk image:

NOTE: You can generate the ISO with the steps below, or download it directly.

mkfs.msdos -C ipmi.iso 2880
sudo mkdir /media/ipmi/
sudo mount -o loop ipmi.iso /media/ipmi/
wget ftp://ftp.supermicro.com/Driver/SAS/LSI/2308/Firmware/IT/PH19-IT.zip
sudo unzip PH19-IT.zip UEFI/* -d /media/ipmi/
sudo umount /media/ipmi/

Now open your IPMI and attach ipmi.iso to your server.

Reboot the machine and hit the F11 key to open a boot menu and start the build in UEFI shell.

When in the UEFI shell enter these commands:

fs0:
cd UEFI
SMC2308T.NSH

At some point it will ask for the 9 remaining digits of the SAS address. Simply enter 000000000 (9 zeros) and you’re fine.

Reboot the system and the controller should now be in IT mode

Cisco 887VA with VDSL2 vectoring on XS4All/KPN

Note: This post is in Dutch since it’s targeted towards a Dutch audience.

Vandaag werd op kantoor onze VDSL2 verbinding van 50Mbit (non-vectoring) naar 65Mbit (vectoring) geupgrade door XS4All. Dat liep niet helemaal lekker. Onze Cisco 887VA router/modem kon daar niet goed mee overweg.

Na wat zoeken (uren) kwam ik er achter dat er een andere firmware nodig is, te weten VA_A_38k1_B_38h_24g1.bin

Om iedereen de moeite te besparen, deze firmware is hier te downloaden. (Hekel aan dat Cisco alles achter logins plaatst!)

Plaats vervolgens de firmware in de router door middel van TFTP of een HTTP-copy en dan is het slechts dit stukje configuratie:

!         
controller VDSL 0
 firmware filename flash:VA_A_38k1_B_38h_24g1.bin
!

Als ik nu in de Cisco kijk zie ik dit:

firewall#show controllers VDSL 0
Controller VDSL 0 is UP

Daemon Status:		 Up 

			XTU-R (DS)		XTU-C (US)
Chip Vendor ID:		'BDCM'			 'BDCM'
Chip Vendor Specific:   0x0000			 0xA45F
Chip Vendor Country:    0xB500			 0xB500
Modem Vendor ID:	'CSCO'			 '    '
Modem Vendor Specific:  0x4602			 0x0000
Modem Vendor Country:   0xB500			 0x0000
Serial Number Near:    FCZ162390P2 887VA-SE 15.3(3)   
Serial Number Far:     AA1250FE43S-05
Modem Version Near:    15.3(3)
Modem Version Far:     0xa45f

Modem Status:		 TC Sync (Showtime!) 
DSL Config Mode:	 AUTO 
Trained Mode:		 G.993.2 (VDSL2) Profile 17a
TC Mode:		 PTM 
Selftest Result:	 0x00 
DELT configuration:	 disabled 
DELT state:		 not running 
Trellis:		 ON			  ON
SRA: 			 disabled			 disabled
 SRA count: 		 0			 0
Bit swap: 		 enabled			 enabled
 Bit swap count:	 1710			 5
Line Attenuation:	  0.0 dB		  0.0 dB
Signal Attenuation:	  0.0 dB		  0.0 dB
Noise Margin:		 12.1 dB		 26.2 dB
Attainable Rate:	90384 kbits/s		 36750 kbits/s
Actual Power:		 12.4 dBm		 - 1.2 dBm
Per Band Status:       	D1 	D2 	D3 	U0 	U1 	U2 	U3
Line Attenuation(dB):   11.7	28.0	44.0	4.0	21.5	33.8	N/A	
Signal Attenuation(dB): 16.3	27.6	44.0	4.0	20.8	33.3	N/A	
Noise Margin(dB):       12.2	12.2	12.1	26.2	26.1	26.2	N/A	
Total FECC:		54			 0
Total ES:		0			 0
Total SES:		0			 0
Total LOSS:		0			 0
Total UAS:		78			 78
Total LPRS:		0			 0
Total LOFS:		0			 0
Total LOLS:		0			 0

Full inits:		1
Failed full inits:	0
Short inits:		0
Failed short inits:	0

Firmware	Source		File Name (version)
--------	------		-------------------
VDSL		user config	flash:VA_A_38k1_B_38h_24g1.bin (10)

Modem FW  Version:	130208_1314-4.02L.03.A2pv6C038k1.d24g1
Modem PHY Version:	A2pv6C038k1.d24g1
Vendor Version:		Ap6v38k1.24g1 68


 		  DS Channel1	  DS Channel0	US Channel1	  US Channel0
Speed (kbps):	          0	       83997	         0	        8399
SRA Previous Speed:       0	           0	         0	           0
Previous Speed:	          0	           0	         0	           0
Reed-Solomon EC:          0	          54	         0	           0
CRC Errors:	          0	           0	         0	           0
Header Errors:	          0	           0	         0	           0
Interleave (ms):       3.00	        0.00	      0.00	        0.00
Actual INP:	       4.00	       55.00	      4.00	       55.00

Training Log :	Stopped
Training Log Filename :	flash:vdsllog.bin

firewall#

USB boot issues on a Soekris net6501-70

After 5 years of great service it was time to replace my Soekris net5501-70 by a new net6501-70.

I tried to install Ubuntu 14.04 via USB, but the Soekris wouldn’t boot from my Kingston Datatraveler USB stick.

It seems the Soekris is rather picky on the USB stick. This is what you don’t want to see:

comBIOS ver. 1.41c  20121115  Copyright (C) 2000-2011 Soekris Engineering.

net6501

2048 Mbyte Memory                        CPU Atom E6xx 1600 Mhz 


SATA AHCI BIOS ver. 0.61 20121115  Copyright (C) 2003-2011 Intel Corporation

Controller Bus#02, Device#06, Function#00: 02 Ports, 02 Devices
  Port-00: Hard Disk, INTEL SSDMCEAC060B3            
  Port-01: Hard Disk, WDC WD10JPVX-80JC3T0           

Soekris USB Expansion ROM ver. 1.01  20111203

82: USB 01                          Xlt -2-32   Mbyte

At 82 you see the USB stick isn’t properly discovered. This is what it should look like:

Soekris USB Expansion ROM ver. 1.01  20111203

82: USB 01  General UDisk           Xlt 994-255-63  7988 Mbyte

That shows t he USB stick is detected properly and you can now install Ubuntu! Simply dd a ISO to the USB stick.

Calculating RADOS objects for RBD images

Ceph’s RBD (RADOS Block Device) is just a thin wrapper on top of RADOS, the object store of Ceph.

It stripes (by default) over 4MB objects in RADOS. It’s very simple to calculate which RADOS object corresponds with which sector on your RBD image/block device.

First you have to find out the block device’s object prefix name and the stripe size:

ceph@daisy:~$ sudo rbd info test
rbd image 'test':
	size 128 MB in 32 objects
	order 22 (4096 KB objects)
	block_name_prefix: rb.0.1066.2ae8944a
	format: 1
ceph@daisy:~$

In this case the stripe size is 4MB (order 2^22) and the object name prefix is rb.0.1066.2ae8944a

With one line of Perl we can calculate the object name in RADOS:

perl -e 'printf "BLOCK_NAME_PREFIX.%012x\n", ((SECTOR_OFFSET * 512) / (4 * 1024 * 1024))'

Let’s say that we want the object for sector 1 of our block device:

perl -e 'printf "rb.0.1066.2ae8944a.%012x\n", ((0 * 512) / (4 * 1024 * 1024))'

This tells us that we need to fetch object rb.0.1066.2ae8944a.000000000000 from RADOS. This can be done using the ‘rados’ command:

sudo rados -p rbd get rb.0.1066.2ae8944a.000000000000 rb.0.1066.2ae8944a.000000000000

Voila, you just fetched 4MB of your drive. Might be useful if you want to do some data recovery or such.