Improved code for Toner Reset SP C250SF/DN

This is based on the Raspberry Pi implementation of the Toner chip reset:

I am using a Raspberry Pi Zero W so the chip is BCM2835 instead and I can use 100Kbps/400KBps instead of 9600 baud as in the original code

The electrical pins we need is clustered on to top left, Pins 1 (3.3V), 3 (I2C SDATA), 5 (I2C SCLK), 9 (Ground)

Raspberry Pi Zero GPIO Pinout, Specifications and Programming language

While looking for the pinouts (, I discovered a useful tool called i2cdetect that allows me to find out the address of the chips which means I can write a program automatically figure out the right image to load to the chip without looking:

sudo apt-get install i2c-tools
sudo i2cdetect -y 1
Sorry I forgot where I got this image from.
Please remind me in the comments section if you find out who should I credit it to.

Since I don’t have cheap pogo pins lying around, I took the 2.4mm pitch (the standard size used in PC, Arduino and Raspberry Pi) jumper block I have (so all pins are set at equal lengths to make simultaneous contact) and hope somehow there’s 4 pins that kind of align with the contact, and it did. See pictures here:

Can press the pins down by using jumpers

You might be worried about shorting into the next pin or hooking something up in reverse damaging the chips, but luckily the chips survived. My guess is that it’s a good design to put the Vcc next to Ground on one side instead of making it symmetric so the polarity can be reversed. When reversed, SCL is set to low (Ground), SDA is pulled up to Vcc while there is no power supply, so no damage is done. Brilliant! The worst case for my poorly aligned jumper block is that SDA and Vcc might touch each other, but it doesn’t matter because it’s a perfectly legal hookup (just not communicating)!

So no worries if you didn’t touch the pins right! The only case it might go wrong is if you intentionally flip the block and slide it by two pins (reversing Vcc and Ground). Other cases (you are likely going to run into) are pretty much data lines getting hooked high or low levels while power lines not getting any supplies.

I’ve designed the program that it’ll detect the chip if you hook it up right and immediately program the chip (takes only a second), so you don’t have to hold the jumper for too long to worry about unstable contacts.


# This program detects rewrite the toner chips to "full" for a Ricoh SP C250SF/DN Printer using Raspberry PI (defaults to BCM2835 models such as Raspberry PI Zero)

# The chip data is in file named "black" "cyan" "magenta" and "yellow".
# The pad closest to the edge is GND (-> Pin 9), followed by VCC (-> Pin 1) , DATA (-> Pin 3), and Clock (-> Pin 5).

# Be sure i2c is enabled and installed (it's turned off by default) on Raspbian

# This line is disabled because it takes too long to unregister i2c_bcm2835 to start from a clean slate
# modprobe -r i2c_bcm2835

# This program needs sudo permissios as it involves direct access to hardware
if [[ $EUID -ne 0 ]]; then
  echo "Need root permissions as this code involves direct hardware access. Try sudo"
  exit 2

# Sets the baud rate
modprobe i2c_bcm2835 baudrate=400000

# Create I2C address to color map
COLORS=( [50]="yellow" [51]="magenta" [52]="cyan" [53]="black" )
# Detect chip I2C address
I2C_address=$( sudo i2cdetect -y 1 | grep 50 | sed -e 's/50: //;s/-- //g' )
# Keep the 0x5* address lines since only 0x50~0x53 is valid. Strip the 50: header, discard all "--" entries, and you are left with the detected address

# LED flash function
function turn_off {
  echo 0 > ${target_device}
function turn_on {
  echo 1 > ${target_device}
function toggle {
  # Reads 0 for OFF and 255 for ON
  readin=$(cat ${target_device})
  # Condition 255 to 1 to reduce platform dependency
  # Not equal to 1 is XOR (flip)
  flip=$[$state != 1]
  # Display output
  echo $flip > ${target_device}

function flash_once {

  sleep $period

  sleep $period

function flash {
  for((i=1; i<=times; i++)); do
    flash_once $period

if [ -v COLORS[I2C_address] ]; then
  # Meat
  echo "Detected toner chip for color: $color"

  echo "Short flashes before starting. Long flashes after done. Ctrl+C if you want to terminate get back to prompt earlier during long flashes."
  flash 5 0.1

   # "address" counter sync up with the hex code index in file
   printf "Writing"
   for i in $(cat $datafile); do
     i2cset -y 1 ${HEX_I2C_address} $address $i;
     address=$(($address +1));
     printf .
     flash 1 0
   echo "Done!"
  flash 10 0.5
  echo "Invalid I2C address for SP C250DN/SF toner chips. ${I2C_address}"
  exit 4

Turns out Linux’s dot sourcing (bash calling) syntax is relative to your current folder, not the scripts’ folder. So if you have one script calling another in the same folder when you are not currently at the same folder as the script (e.g. startup automation), the relative paths will be wrong. So instead of ./ , you should use this prefix instead:


I chose to flash the board’s only LED light quickly before starting and blink slowly a few times after it’s done for visual clues. It’s entirely optional. Here’s the guts of the code without the fancy indicators:


# Sets the baud rate
modprobe i2c_bcm2835 baudrate=400000

# Create I2C address to color map
COLORS=( [50]="yellow" [51]="magenta" [52]="cyan" [53]="black" )

# Detect chip I2C address
I2C_address=$( sudo i2cdetect -y 1 | grep 50 | sed -e 's/50: //;s/-- //g' )

if [ -v COLORS[I2C_address] ]; then
  # Meat

  # "address" counter sync up with the hex code index in file
  for i in $(cat ${color}); do
    i2cset -y 1 ${HEX_I2C_address} $address $i;
    address=$(($address +1));
  echo "Invalid I2C address for SP C250DN/SF toner chips: ${I2C_address}"

Download the package. Run program_toner

Just in case if people are wondering. The L01 chip’s datasheet is here:

To make a dedicated Raspberry Pi Zero device to run this script (program_toner) repeatedly on boot, I made a script loop_program_toner that runs the same program as infinite loop. I added 5 seconds of slow flashes after a successful write. To prevent the program from retrying too fast (sucking up computing resources) when there’s nothing connected to it, I wait for 1 second between each retries.


if [[ $EUID -ne 0 ]]; then
  echo "Needs root permissions. Try sudo"
  exit -2

while :
  bash "${BASH_SOURCE%/*}/program_toner"
  if (($?));
    sleep 1;

There are a few steps needs to auto make this automated:

  1. Have a user account dedicate to autorun the program on boot. In the example here, we’ll use Raspberry Pi’s default admin account
  2. Auto-logged on by running sudo raspi-config, select (1) System Option > (S5) Boot / Auto Login > (B2) Console Autologin
  3. Bless the said account with password free sudo rights: Run sudo visudo and add this to the end (replace admin with something else if you chose a different username)

  1. Call loop_program_toner in ./profile (startup script). Can place this at the bottom if your package folder is ~/spc250dn :
sudo ./spc250dn/loop_program_toner

The path can start with . or ~ because you are starting with at home folder anyway.

 49 total views

Get myself comfortable with Raspberry Pi

I2C is disabled by default. Use raspi-config to enable it. Editing config file /boot/config.txt directly might not work

Locale & Keyboard (105 keys) defaults to UK out of the box. Shift+3 “#” (hash) sign became “£” pound sign. Use raspi-config to change the keyboard.

It reads random garbage partitions for MFT assigned to FAT16 drives. Just use FAT32

USB drives does not automount by default. usbmount is messy as it creates dummy /media/usb[0-7] folders. Do this instead.

 33 total views

dd-wrt gotchas

dd-wrt is very a powerful firmware compared to ASUS Merlin, but the UI leaves a lot to be desired. It’s very close to editing a config file and there’s little help to what each setting. The developers of dd-wrt didn’t invest time in designing the web administration interface and used the most basic primitive HTML forms so there are no tooltip that explains the features and the interaction between different settings.

There are also some confusing (nonsensical) UI design that are a lot less work to the developer but confused users to no end. Here are the examples I’ve found so far:

  • Enabling remote admin through SSH (for embedded linux command prompt) is a two step process out of the box. You’ll need to first enable SSHd from Services -> Secure Shell before enabling SSH Management from Administration -> Management (otherwise it’s greyed out)
  • The router username (user modifiable) for dd-wrt applies to web UI only. SSH’s username remains root. They share the same password though (so login and password are decoupled in dd-wrt, they are effectively two passwords in practice except they don’t put asterisk over the username as you type). ASUS Merlin firmware’s login is consistent across both web page and SSH
  • Cron jobs is from a bare environment which means you need to manually define the paths and specify the user in the cron job syntax. e.g.
    * * * * * root {command_to_execute}

 46 total views

Updates to Linux on Pogoplug v4

I have an old Pogoplug v4 series hacked to install Debian Linux based on this instruction long ago:
which boots on an SD card, which I used as a no-ip update client.

I realized some of the URLs to the package servers are broken. So here’s my notes to update it.

First apt-get doesn’t work anymore because the files has been moved to the archive package server. The solution is to replace all the contents (now obsolete) in /etc/apt/sources.list by this line:

deb wheezy main

Then you update the package manager (apt-get) with this command:

apt-get update && apt-get upgrade

Note that it’s still Debian 7 and it will not work with new software that requires later versions

 124 total views

NextCloud quirks – moving folder breaks the site

I changed the folder of where my NextCloud files is and got this error.

Adding the “.ocdata” dummy file there doesn’t work. The message is cryptic. I tried to run occ at the root folder (hoping it’s some sort of management tool) by running this at the command/SSH prompt:

php ./occ

and it spits out:

Your data directory is invalid
Ensure there is a file called ".ocdata" in the root of the data directory.

An unhandled exception has been thrown:
Exception: Environment not properly prepared. in 
{New Folder}/lib/private/Console/Application.php:168
Stack trace:
#0 {New Folder}/console.php(99): ...

I replaced my actual path for the new location of the NextCloud files with {New Folder}, so you get the idea.

I also noticed the old path was regenerated with just a /data folder with two files

This means some programmer got lazy and hard-coded the path somewhere!

Line 99 of console.php didn’t give too much hint so I looked at the code around for some sort of config-related operations before. Then I noticed this:

So I searched for config.php and found it’s located in /config/config.php. Bingo!

$CONFIG = array (
  'trusted_domains' => 
  array (
    0 => '{Old URL}',
  'datadirectory' => '{Old Path}/data',
  'overwrite.cli.url' => 'https://{Old URL}',

And to my horror the SQL password is stored in plain text in config.php! WTF! I’ll choose a password that’s dedicated to one use and not shared!

I recalled a when I rename WordPress databases, I have to manually edit the changes in wp-config.php. Turns out nobody warned us about that for NextCloud! That config file also contain database settings, so I bet if I change the database names or database usernames, I’ll have to come back and edit it manually too.

The site is working after I made the migration changes, all in /config/config.php.

 137 total views