Tuesday, December 27, 2016

Swim Watches

Being an avid early-morning swimmer, I'm always on the lookout for a nice water-resistant watch with a readable face. I started out using a cheap Casio MRW200H-1EV (they come in many color combos, but my favorite was the black dial with yellow numerals). Battery life on this watch was not so good as i found i had to replace the battery at least once a year (something i can do myself).

My next step up was to another Casio, the classic, and i think, bargain 200m dive watch, the MDV106-1A - beefy, with twice the battery life, screwback, good readability. I used one for at least a year. Alas, i was disappointed in the lume.

I also like the Seiko SKX007:


And then i discovered the Seiko Monsters. Big, beefy automatics with hand-winding and hacking (ability to set the watch to the second) in the 2nd generation, which uses a 4R36 movement. Great watch, outstanding lume.

I snagged a used one for about a Franklin. It's my go-to swim watch for the early morning:
More recently, i discovered that there is a real bargain automatic dive watch with hand-wind capability and fabulous accuracy out of the box: the Vostok Amphibia, made in the Russian Federation, and based on a late 60s design:

Like the cheap Casios, these watches come in a variety of configurations, dials, bezels, etc. Many parts are interchangeable, and best of all, the watches are less than US$80 shipped, with hand-wind only for US$30 less!

I really didn't believe the touted accuracy, so I slapped one of mine onto a C270 webcam's mic and ran the Tg timegrapher client under CrossOver on my MacAir running OS X:
Nice! Note that unlike the Seiko, which has a 53-degree lift angle and 21,600 beats per hour, the Amphibia has a 42-degree lift angle and 19,800 beats per hour. Even so, the accuracy of +/-5 seconds per day (more or less according to wear, temperature, etc.) is astounding!
And the newest addition to the clan - an orange scuba dude with a modded bezel and nato strap:

And here's a new Amphibia modded with a new bezel that i'm going to call my 'Diet Pepsi' (after a Seiko SKX009):


and a latest acquisition: i think this is one of the most attractive and readable of the Vostok Amphibian dials; i slapped on a dragontail Pepsi bezel and a blue 22 G10 (nato) strap:



So now I'm very happy! Anyone want a couple cheap quartz swim watches? I'm done changing batteries. Even though the Amphibia only has a 31-hour reserve and the Seiko Monsters sport a 40-hour reserve, hand-winding keeps these babies running in tip-top shape and on time!

and my very latest - the wonderfully rare SKX011J1!

Saturday, December 24, 2016

Merry Christmas

Merry Christmas from the Gulf of Mexico!






To all GNU/Linux users - have a great time with family and friends and fabulous 2017!

Wednesday, December 7, 2016

Tg Timegrapher on the Raspberry Pi

Marcello Mamino's Tg timegrapher is a fabulous piece of software you can use to maintain your automatic watch's accuracy or to diagnose and determine if service is needed. This program uses sound analysis and 'listens' to your watch, then reports information about how it is running. Again, many folks will simply use Tg in order to get their watch to the greatest degree of accuracy (shortest loss/gain of seconds per day). This short post will show you my simple setup on how I use Tg with a Raspberry Pi 3.

NOTE: For a detailed explanation of many of Tg's features, to learn how to interpret the graphical and digital output, and for training on how to use a timegrapher, get this *very* helpful document:

Witschi Training Course

To install tg, the first step is to download and build the software (I don't think Tg is in Debian mainstream - yet!). You'll need a few extra software packages and libraries, along with, of course, the normal development tools. Simply follow the instructions on Tg's page or Marcello's github page and you'll see that Tg builds and installs under Raspbian quite easily.

You'll also need an inexpensive quartz watch, and input microphone - i use a Logitech C270 webcam for a mic - and of course, your automatic watch. Please note that I am an amateur watch person, so my horological knowledge is quite in the n00b status.

One additional piece of software I found handy was Pulseaudio's pavucontrol, a gui client interface to sound devices on my Raspbian system. After installation, I checked the mic and its input level:
Calibrate

The first step is to calibrate Tg to your sound card. More details about this process may be found in this msg. Simply clamp your quartz watch with its back to the mic input. Run Tg using the tg-timer command, then click the Calibrate button. Then sit back, watch the dialog, wait until the calibration finishes, and you'll see Tg automatically enter the offset value. In my case, the C270 mic need a +2 offset. A CMedia USB sound dongle required a +3.6. Your mic may be different!

Enter Settings

Next, you'll need the 'lift angle' and beats per hour for your watch. Now understand that I'm a watch n00b, but as I have read, the lift angle is "the time the balance is in contact with the pallet fork." Most watches use 52, but my Seiko in this example uses 53, and the bph setting is 21,600. After clamping the watch onto your mic, let Tg run!

Simple Use of Tg

It's best to let the program run for a minute or so in each of six different watch positions: dial up/down, crown up/down/left/right. Keep a notepad handy and note any different readings in the s/d or seconds per day. You can use these values and orientation to 'fine-tune' your watch's accuracy once you get it running the way you like.

Here is the program running on one of my RPi3's with a 3.5" TFT. My Seiko SKX007 is clamped to the C270 and the session is displayed on a Screen Sharing VNC session on my Macbook Air in the background (Tg's dialog is too big for 320x240 resolution on the TFT):
Using this program, I've been able to achieve a decent level accuracy for my watch (temperature, position, etc. will all have an effect):

Tg offers a bit more info than i've described here. I'm not knowledgeable enough about horology to expound on its uses, so suffice to say that i simply enjoy using the program. It's a simple way for me to enjoy my inexpensive Seiko automatics.

Saturday, December 3, 2016

Tg: Open-Source Timegrapher

I love open-source software. Thanks to Marcello Mamino's Tg timegrapher, you can enjoy using your favorite computer and soundcard to help maintain your automatic watch's accuracy or to diagnose and determine if service is needed.

I'm an amateur, but I like professional tools, and this software, which runs on everything from a small Raspberry Pi to machine powered by the Beast of Redmond, presents an inside view of your watch's movement - it's  not an x-ray, but a sophisticated sound analysis!

Here we see a Seiko SRP615, which uses a 4R36 movement:

The dialog shows a beat rate (+/- seconds per day),  amplitude (orientation), beat error (lineup of tic/toc) and beat number (here, 21,600 beats per hour). Excellent accuracy is assumed on first glance, but you have to run the program with the watch in different positions for a better idea!

You can download and build the client from source, install binaries, or as I did, install non-native binaries and run the client in emulation under CrossOver for the Mac.

The first task is to 'calibrate' the software to your soundcard. Clamp a quartz watch onto your input mic (I used a Logitech webcam), then click the Calibrate button. Let Tg collect data and it will eventually display an offset value to use when running an analysis of your automatic watch. You'll want to make sure that the input sound is 'clean' (represented on the bottom line of the picture) so you'll get best results, but Tg is pretty robust - it will work!

You can use the 's/d' values to determine if your watch is running too slow or too fast. You'll want to see the differences between these values with the watch in different positions (dial up/down, crown up/down, etc.) Then crack your caseback, adjust, and check again. This can save a lot of time instead of doing the adjustment, then letting the watch run for two days or so.

Have fun, and thanks, Marcello!

btw, it also appears that my manual regulation efforts, conducted before trying this software, were pretty successful with my skx007:



Friday, November 4, 2016

Finally - A Working Sensor Recovery Python Script

Using a Raspberry Pi and learning how to read sensors via GPIO pins using Python is a wonderful learning experience. One of the important things to learn when melding hardware and software is how to 'bullet proof' against system and/or sensor failure.

I initially tried to craft a simple system reboot in response to a failure of a Python 'try' statement. Our power down here in the near Tropics, despite being within the CONUS sometimes is almost Third World. I think Baghdad must have a better electric grid than the one run here by Duke Energy.

Anyhow, I finally came up with a working Python function to reboot my Raspberry Pi 3B in the event of a BME280 sensor read failure. Without this, the system would hang and the script would fail to return to a working state.

So here's the snippet. It parses out of the currently running script to reboot the system and hopefully, restore a working order, as evidenced by yesterday's intermittent power outages/voltage fluctuations - here's the logging output (saved to a lighttpd LAN-readable Web page):

2016-11-03 02:10 error reading sensor - rebooting
2016-11-03 02:20 error reading sensor - rebooting
2016-11-03 02:31 67.6 22.7 100.0
2016-11-03 02:41 error reading sensor - rebooting
2016-11-03 02:51 67.6 22.7 100.0
2016-11-03 03:01 error reading sensor - rebooting
2016-11-03 03:11 67.6 22.7 100.0
2016-11-03 03:21 error reading sensor - rebooting
2016-11-03 03:31 67.6 22.7 100.0
2016-11-03 03:41 error reading sensor - rebooting
2016-11-03 03:52 67.6 22.7 100.0
2016-11-03 04:02 error reading sensor - rebooting
2016-11-03 04:12 67.6 22.7 100.0
2016-11-03 04:22 error reading sensor - rebooting
2016-11-03 04:32 error reading sensor - rebooting
2016-11-03 04:44 error reading sensor - rebooting
2016-11-03 04:54 error reading sensor - rebooting
2016-11-03 05:04 67.6 22.7 100.0
2016-11-03 05:14 error reading sensor - rebooting
2016-11-03 05:25 67.6 22.7 100.0
2016-11-03 05:35 error reading sensor - rebooting
2016-11-03 05:45 67.6 22.7 100.0
2016-11-03 05:55 error reading sensor - rebooting
2016-11-03 06:05 67.6 22.7 100.0
2016-11-03 06:15 error reading sensor - rebooting
2016-11-03 06:25 error reading sensor - rebooting
2016-11-03 06:36 error reading sensor - rebooting
2016-11-03 06:46 error reading sensor - rebooting
2016-11-03 06:56 67.6 22.7 100.0
2016-11-03 07:06 error reading sensor - rebooting
2016-11-03 07:16 69.7 30.2 58.9

As you can see, things started going awry at 0200. The function sets a reboot after 10 minutes. Bogus sensor readings continued until 0716, and the system has been running fine since then. Here's the code snippet/function. I hope it helps you!

def reboot():
  text_file = open("/var/www/html/weather.txt", "a")
  text_file.write(str(datetime.now().strftime('%F %H:%M ')))
  text_file.write("error reading sensor - rebooting\n")
  text_file.close()
  time.sleep(600)
  cmd = "/usr/bin/sudo /sbin/shutdown -r now"
  import subprocess
  process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
  output = process.communicate()[0]

  return  "%s" % output

The function is called like this:

# test bus write
  try:
        bus.write_byte_data(addr, REG_CONTROL, control)
  except IOError:
        reboot()


or like this:

# Read temperature/pressure/humidity
  try:
        data = bus.read_i2c_block_data(addr, REG_DATA, 8)
  except IOError:
        reboot()


Have a happy!

Monday, October 3, 2016

Pogo-pin Powered Pi Ports!

Got a little box in the mail today - a present for one of my Raspberry Pi Zeros:
 Nestled inside were two packages. On the left, a 5V 2.4A switching power supply with a power-switch USB->micro usb connection cord:
The tasty treat inside the little box is my new 4-port USB hub for a Zero:

As you can see, the good folks at MakerSpot also included nylon standoffs with screws and nuts! Putting the who shebang together was easy, and you can see the precision of the soldering points and pogo pins:
The only bad thing i might say about this product is that there was no documentation - just the little business card.
This hub makes a nice compact computer, ready for wifi, tunage, or flash storage! Booting up shows the following info:

[Mon Oct  3 13:15:28 2016] usb 1-1: New USB device found, idVendor=1a40, idProduct=0101
[Mon Oct  3 13:15:28 2016] usb 1-1: New USB device strings: Mfr=0, Product=1, SerialNumber=0
[Mon Oct  3 13:15:28 2016] usb 1-1: Product: USB 2.0 Hub
[Mon Oct  3 13:15:28 2016] hub 1-1:1.0: USB hub found
[Mon Oct  3 13:15:28 2016] hub 1-1:1.0: 4 ports detected
[Mon Oct  3 13:15:28 2016] usb 1-1.1: new high-speed USB device number 3 using dwc_otg


The vendor/device id maps out to:

1a40  Terminus Technology Inc.
        0101  Hub

One of the downsides to no included info is that while generally there is a caution to only power the Zero (when the hub is attached) through the hub's micro-usb port or the Zero, but NOT BOTH - bad things could happen!  However, according to the seller:

"has built-in a 2-way back-powering circuit to protect against power being plugged into the Pi Zero and the Stackable USB Hub simultaneously."

Not bad for US$17 with free shipping! Nice!

Wednesday, September 21, 2016

VW Dieselgate

Update: It's raining money! Looks like another check, in the amount of $350 is coming our way! This diesel wagon is the gift that keeps on giving!



Update: the software modification for Phase 1 results in great mileage - nearly 40mpg in combined city/hwy driving - and the $4,622 check from VW is a nice bonus... also, we just got word that Bosch is going to send out a check for $350 for its part in the settlement - that's about 6,000 miles of free diesel for our 2015 Golf TDI S DSG Sportwagen! EXCELLENT!!! and sometime this year there will be a hardware sensor update which will net us another check for over $2,000!!! W00T!!!! 

Update: Phase 1 Emissions Modification complete! Received and EFT of $4,622.22 - thanks, VW!!! Here's the sticker:



Update: here's a pic for a fellow on the tdiclub forum who is experiencing some constant fan noise... hopefully this pic, which matches his engine, may alleviate thought of the cabling being a problem:


Update: Just filled up Von Diesel for less than $17! We'll track the mileage to see how well the new mod does on fuel consumption:

Update: We just had the Phase 1 Emissions Modification done! here's what the car menu says:

It's going to rain money Real Soon Now... after purchasing our wonderful 2015 Volkswagen Golf TDI S Sportwagen, we have received $1,000 in dealer and cash debit cards, and we'll now be getting more than $4,250 for an electronics control unit re-flash; 18 months later, we'll get another check for more than $2,500... this means we bought the equivalent of a BMW diesel station wagon ($44.5K MSRP) for $18.5K!!! w00t!

now we're just waiting for uploaded document approval and Oct. 18th...


Saturday, September 3, 2016

IOError Exception Handling for a BMP180 Temperature & Barometric Sensor


THE PROJECT:

I recently set up a mini 'weather' recorder to make inside and outside temps, inside humidity and general barometric pressure readings available on an SSD1306 .96" yellow-blue OLED. The project uses a Raspberry Pi Zero, BMP280, OLED, and cheap Homeless Despot plastic junction box for the inside 'monitor.' In the garage, attached to a wall next to the washer and dryer, a Raspberry Pi 3 with an 18" i2c cable fed outside through the concrete, reads data from a BMP180 housed in a cut piece of reinforced tubing for protection from rain.

The BMP180 data is read every 10 minutes and logged to a file published via a lighttpd Web page on the 192.168.1.XX network. The inside junction box monitor strips off temperature and barometric pressure readings from the Web server, then combines the data with data from a BME280 via i2C off the Zero.

The result is a nice little display with date and time, along with inside and outside temps, humidity and barometric pressure.

THE PROBLEM:

So far I've had two BME280s fail when used outside. I do not know if they are Bosch Sensortec units (I doubt they are as the sensors are of Asian origin). Both have 'come back to life' when used inside. I'm guessing that the sensors are not tolerant of either high temps (90F+) or high humidity (90%+). I have ordered a set of four more, along with a 'supposed' 'real' Bosch unit from Adafruit. We shall see.

The problem is that I also had the BMP180 (installed outside) fail once or twice. Due to my n00b Python skills and the piss-poor example code on the Internet for these sensors, once a read hits an I/O error in Python, game over for the monitoring code - the script bombs and the process halts.

What to do?

Well, I put in place some error-checking, and we'll see if some Python exception handling might do the trick. I first looked for the very first instance in the logging script in which the i2c device (i.e., the sensor) is being accessed, then put in a piece of exception handling. I found that with the BMP180, simply rebooting the RPi3 worked to restore functionality. The code tests a write (or read), and if not successful, waits 10 minutes before rebooting. This *should* allow SSH access to fix or replace the sensor or shut down the script.

The first part is to use Python's 'try':

 try:
        cal = bus.read_i2c_block_data(addr, REG_CALIB, 22)
  except IOError:
        reboot()


NOTE: the below reboot() function does not work! see a later posting regarding how to properly initiate a reboot process!

 The next is to call a reboot function:

def reboot():
  text_file = open("/home/pi/Downloads/ssd1306-master/error.txt", "a")
  text_file.write(str(datetime.now().strftime('%F %H:%M ')))
  text_file.write("error reading sensor - rebooting\n")
  text_file.close()
  time.sleep(600)
  cmd = "sudo reboot"
  err = os.popen(cmd).read()
  strerr = str(err)
  return  "%s" % strerr.rstrip('\r\n')


 I use the Python try in the first instance of reading or writing the sensor. There are multiple instances in my script, so I guess I should sprinkle these liberally at each instance?

Anyway, ever since I modified my script, guess what? That's right, no problems. Rock solid readings every 10 minutes for nearly a week. Perhaps the problem is voltage related? Who knows?



Thursday, August 25, 2016

Best MicroSDHC for Beaglebone and Raspberry Pi

yep, here it is:


don't waste your money on lesser or more expensive brands... i have used these for months on end, even in an RPi3 running 24/7, and have yet to have one fail... Sandisk? Transcend? Kingston? ackpht! i have had failures with all those...

also, make sure not to get burned on counterfeits - Asian scum have infiltrated the manufacturer and distributor chains at Amazon and mom-and-pop cellphone stores...

that said, i can generally find these for US$10.99-$12.99 at a local box-mart electronics place with the initials 'bravo bravo' (you know who i'm talking about, right?)

Ultimate Cheap Desktop Computer


here's the ultimate cheap desktop computer - i attached this to a $9 LCD monitor from a local thrift store for quick computing...

why?

because i can!

Raspberry Pi Zero (1.3)
$5 USB sound card
$12 Ableconn HDMI->VGA adapter
junk box Belkin wifi adapter
junk box 4-port USB hub
scrap piece from a crappy RPi case

works great!

Monday, August 22, 2016

Logging BME280 Weather Data

LATEST UPDATE: a replacement bme280 has been installed and has been running with no problems for a day or so; the 'failed' unit apparently came back to life and has been running inside with no problem - one unit reads 10 percent too high on humidity - i'm wondering if these aren't Bosch Sensortec units?

UPDATE: the bme280 sensor FAILED after four hours of use and queries on one-minute intervals; i'm disappointed, but may try another one to see if it was an anomaly (hey, shit happens, right?)

fortunately, i had a spare bmp180 on hand, and have put that into use instead - we'll see how it holds up... i'm sure i'm not the first n00b to run across sensor hardware failures  - i've even seen pictures of a bme280 melted from what i can only assume is an over- or reverse-voltage run...

:-)

just spent a bit of a morning crafting a logging script to read bme280 sensor data... the file has a format like this:

2016-08-22 12:17 78.80 30.14 52.60
2016-08-22 12:17 78.33 30.14 52.57
2016-08-22 12:18 78.28 30.14 52.56
...
  
the bme280 is an interesting sensor that returns temperature, barometric pressure and humidty...

HOWEVER, many users report that the temperature runs about 4F higher than that reported by most other sensors... and i found this to be true, as i gauged the bme280's temp readout against a bmp180 and an LCD digital thermometer... in light of this, i've included a 4-degree 'correction' in the conversion code in the script - just so you know why

i installed the lighttpd web server on a Raspberry Pi 2, with the sensor attached and fed out to the side of the garage... the script logs data once a minute (the log will grow to 50MB over the course of a year)... current weather info can be retrieved via a simple one-liner shell script:


#!/bin/sh
# retrieve weather info from data log
# 3 - temperature
# 4 - barometric pressure
# 5 - relative humidity
curl http://192.168.1.20/weather.txt -s -d ascii | tail -1 | cut -f $1 -d ' '


here's the logging script in Python:

#!/usr/bin/python
# version 0.1 - bme280 data reader and logger
# 082216
# added 1-minute logging
#
# data fields:
#     DATE         TEMP  BARO  HUMID
# YYYY-MM-DD HH:MM TT.TT PP.PP HH.HH

import smbus
import time
from datetime import datetime
from ctypes import c_short
from ctypes import c_byte
from ctypes import c_ubyte

DEVICE = 0x76 # Default device I2C address

bus = smbus.SMBus(1) # Rev 2 Pi, Pi 2 & Pi 3 uses bus 1
                     # Rev 1 Pi uses bus 0

def getShort(data, index):
  # return two bytes from data as a signed 16-bit value
  return c_short((data[index+1] << 8) + data[index]).value

def getUShort(data, index):
  # return two bytes from data as an unsigned 16-bit value
  return (data[index+1] << 8) + data[index]


def getChar(data,index):
  # return one byte from data as a signed char
  result = data[index]
  if result > 127:
    result -= 256
  return result

def getUChar(data,index):
  # return one byte from data as an unsigned char
  result =  data[index] & 0xFF
  return result

def readBME280ID(addr=DEVICE):
  # Chip ID Register Address
  REG_ID     = 0xD0
  (chip_id, chip_version) = bus.read_i2c_block_data(addr, REG_ID, 2)
  return (chip_id, chip_version)

def readBME280All(addr=DEVICE):
  # Register Addresses
  REG_DATA = 0xF7
  REG_CONTROL = 0xF4
  REG_CONFIG  = 0xF5

  REG_HUM_MSB = 0xFD
  REG_HUM_LSB = 0xFE

  # Oversample setting - page 27
  OVERSAMPLE_TEMP = 2
  OVERSAMPLE_PRES = 2
  MODE = 1

  control = OVERSAMPLE_TEMP<<5 | OVERSAMPLE_PRES<<2 | MODE
  bus.write_byte_data(addr, REG_CONTROL, control)


  # Read blocks of calibration data from EEPROM
  # See Page 22 data sheet
  cal1 = bus.read_i2c_block_data(addr, 0x88, 24)
  cal2 = bus.read_i2c_block_data(addr, 0xA1, 1)
  cal3 = bus.read_i2c_block_data(addr, 0xE1, 7)

  # Convert byte data to word values
  dig_T1 = getUShort(cal1, 0)
  dig_T2 = getShort(cal1, 2)
  dig_T3 = getShort(cal1, 4)

  dig_P1 = getUShort(cal1, 6)
  dig_P2 = getShort(cal1, 8)
  dig_P3 = getShort(cal1, 10)
  dig_P4 = getShort(cal1, 12)
  dig_P5 = getShort(cal1, 14)
  dig_P6 = getShort(cal1, 16)
  dig_P7 = getShort(cal1, 18)
  dig_P8 = getShort(cal1, 20)
  dig_P9 = getShort(cal1, 22)

  dig_H1 = getUChar(cal2, 0)
  dig_H2 = getShort(cal3, 0)
  dig_H3 = getUChar(cal3, 2)

  dig_H4 = getChar(cal3, 3)
  dig_H4 = (dig_H4 << 24) >> 20
  dig_H4 = dig_H4 | (getChar(cal3, 4) & 0x0F)

  dig_H5 = getChar(cal3, 5)
  dig_H5 = (dig_H5 << 24) >> 20
  dig_H5 = dig_H5 | (getUChar(cal3, 4) >> 4 & 0x0F)

  dig_H6 = getChar(cal3, 6)



  # Read temperature/pressure/humidity
  data = bus.read_i2c_block_data(addr, REG_DATA, 8)
  pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
  temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
  hum_raw = (data[6] << 8) | data[7]

  #Refine temperature
  var1 = ((((temp_raw>>3)-(dig_T1<<1)))*(dig_T2)) >> 11^M  var2 = (((((temp_raw>>4) - (dig_T1)) * ((temp_raw>>4) - (dig_T1))) >> 12) * (dig_T3)) >> 14
  t_fine = var1+var2
  temperature = float(((t_fine * 5) + 128) >> 8);

  # Refine pressure and adjust for temperature
  var1 = t_fine / 2.0 - 64000.0
  var2 = var1 * var1 * dig_P6 / 32768.0
  var2 = var2 + var1 * dig_P5 * 2.0
  var2 = var2 / 4.0 + dig_P4 * 65536.0
  var1 = (dig_P3 * var1 * var1 / 524288.0 + dig_P2 * var1) / 524288.0
  var1 = (1.0 + var1 / 32768.0) * dig_P1
  if var1 == 0:
    pressure=0
  else:
    pressure = 1048576.0 - pres_raw
    pressure = ((pressure - var2 / 4096.0) * 6250.0) / var1
    var1 = dig_P9 * pressure * pressure / 2147483648.0
    var2 = pressure * dig_P8 / 32768.0
    pressure = pressure + (var1 + var2 + dig_P7) / 16.0


  # Refine humidity
  humidity = t_fine - 76800.0
  humidity = (hum_raw - (dig_H4 * 64.0 + dig_H5 / 16384.8 * humidity)) * (dig_H2 / 65536.0 * (1.0 + dig_H6 / 67108864.0 * humidity * (1.0 + dig_H3 / 67108864.0 * humidity)))
  humidity = humidity * (1.0 - dig_H1 * humidity / 524288.0)
  if humidity > 100:
    humidity = 100
  elif humidity < 0:
    humidity = 0

  return temperature/100.0,pressure/100.0,humidity

while True:
 temperature,pressure,humidity = readBME280All()

 text_file = open("/var/www/html/weather.txt", "a")
 text_file.write(str(datetime.now().strftime('%F %H:%M ')))

 # correct for bme280's high temp readout
 text_file.write("%.2f " % (((temperature)*9/5)+32-4))
 text_file.write("%.2f " % ((pressure)/33.8638866667))
 text_file.write("%.2f\n" % humidity)
 text_file.close()
 time.sleep(60)



Sunday, August 21, 2016

A $30 Wireless Weather Monitor Using a Raspberry Pi Zero



My weekend project was a wireless ambient condition monitor for the home. I used an RPi Zero (yep, i got one by mail order), SSD1306 .96" OLED, micro-usb adapter, cheap nubby Edimax wifi adapter (i have two cheaper ones, $3 MT7601s, on order), BME280 sensor, $0.67 and $0.68 junction cover and box from Homeless Despot (makes a great Zero case, and there are inexpensive heavy-duty waterproof ones that can easily hold an RPi 3 for about $7!! I'll building a remote weather station over the next month), some scrap Lexan (can't see it, but i hot-glued the display onto the Lexan), a cheap 4GB Sandis, microsdhc, and a 'Y' cable w/GPIO female headers.

All in all, a lot of fun, and now I have another visual trinket for the guest room. It has already come in handy to help us cool the kitchen by monitoring lowered temps due to the use of shades (they make about a 5-degree difference).

Friday, August 19, 2016

Combat Raspbian Software Bloat

i hate software bloat - it's bad enough that the Evil Empire has caused so much misery on this planet with its Winblows software, but when GNU/Linux distros bloat it's really sad...

don't get me wrong: i like Raspbian... but the Noobs distro is just plain dumb, and with Raspbian it's feast or famine:

- either you get a bloated, soggy, sdhc-eating install with all kinds of crap and junk most users will NEVER need

or

- in the case of Jessie Lite, a bare-bones, not even wifi-friendly distro

well friends, fear no more! thanks to Andrew Vaughn's Raspbian I love you but you're too fat, you can have a nicely crafted, customized Jessie Lite with a single command line!

here's my crafted system (i did install vnc4server, GNU ddrescue and some python extras afterwards though):

sudo apt-get update && sudo apt-get install alacarte desktop-base fonts-dejavu fonts-sil-gentium-basic gksu gnome-icon-theme gnome-themes-standard-data gtk2-engines libgl1-mesa-dri libgles1-mesa libgles2-mesa libpangox-1.0-0 lightdm lxappearance lxappearance-obconf lxde lxde-common lxde-core lxde-icon-theme lxinput lxpanel menu-xdg pcmanfm raspberrypi-net-mods raspberrypi-ui-mods xcompmgr xdg-utils xinit xserver-xorg xserver-xorg-video-fbdev xserver-xorg-video-fbturbo gpicview leafpad lxrandr lxtask lxterminal openbox pi-package rc-gui xarchiver xpdf gstreamer1.0-alsa gstreamer1.0-libav gstreamer1.0-omx gstreamer1.0-plugins-bad gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-x policykit-1 raspberrypi-artwork rpi-update python-gpiozero python-picamera python-pifacecommon python-pifacedigitalio python-pip python-serial python-tk python3-serial raspi-gpio tree udisks python-pygame

btw, this was done on my latest addition to my stable of tiny GNU/Linux boxen - a used Raspberry Pi Model 2 B, which i snagged for a Jackson - bazinga! bargain time!

Sunday, August 14, 2016

Easy Backup of the Beaglebone Black

I just spent an entire morning installing and removing software, and configuring my new Beaglebone Black. Thank heavens I finally found a working wifi adapter - a MediaTek MT7601 (Ralink 7601) Controller!

We all know the drill:

- boot a new computer with a new system
- spend hours configuring it
- then put a backup plan in place and back up all your hard work

It used to be quite a chore to back up a system. However, thanks to Mr. Robert Nelson, Beaglebone guru and kernel master,  backing up and cloning your eMMC (configure internal disk) on your Beaglebone system is as easy as:

1. Take a FAT-formatted microsdhc and insert into the original Beaglebone.

2. Run a simple command to start a backup script:

sudo /opt/scripts/tools/eMMC/beaglebone-black-make-microSD-flasher-from-eMMC.sh

3. Shutdown.

4. Insert the sdhc into the target Beaglebone (or your original if you're doing a restore operation)

5. Power up the Beaglebone.

The Beaglebone will boot off the microsdhc, then shut down when done. It doesn't get any easier than that! Took me about 20 minutes to back up, then clone my backup Beaglebone Black (I have two).

Crafting a BME280 Ambient Condition Monitor



I have been having a lot of fun with my Beaglebone Black now that wifi is up and running smoothly (can't believe it took so long to get a good adapter!). One of the most recent acquisitions was a Bosche BME280 sensor - tiny, with readouts of temperature, barometric pressure, and humidity - i guess smartphones are getting smarter?

Well, thankfully I didn't have to shell out an outrageous amount of dinero for this sensor. You can get 'em for less than a Hamilton on-line, so I popped for a pair. I used Matt Hawkin's excellent bme280.py script as an accessible Python lib (simply drop it in the same folder as your script), and just used the function to get the data:

  from bme280 import readBME280All

the original script returns many digit accuracy - too many for the oled, so i trimmed the output, converted from Centigrade to Fahrenheit and hectopascals to inches of mercury:

return "%.2f degrees F" % (((temperature)*9/5)+32)
return "%.2f inches mercury" % ((pressure)/33.8638866667)
return "%.2f%% humidity" % humidity
 
I like the result and will use this sensor with some wifi-enabled Arduinos for inexpensive weather monitoring. I also have a couple wind sensors I haven't tried - that's for later! So for now, here's the script:

#!/usr/bin/env python
# bbssd.py version 0.6 - a simple system monitor for the
# Beaglebone Black GNU/Linux SBC
# kg4zqz.blogspot.com
# adapted from rmhull's wonderful ssd1306 Python lib
# crafted for the dual-color 128x96 OLED w/yellow top area
# 070916 - initial version ported from a version for the Raspberry Pi
#          shows date, wlan0 IP address, memory, and sd card usage
# 070916 - added Beagle splash screen, kernel version and serial #
# 071016 - added uptime, wifi sig strength, screen cleanup
# 072616 - added text wrap to uptime screen
# 081416 - added BME280 weather sensor data/readout
#
# note: it was a bear to get all the needed software libs for
# the Pillow install - must have libjpeg6-dev and associated zlib pkgs!

import os
import psutil
from lib_oled96 import ssd1306
from time import sleep
from datetime import datetime
from PIL import ImageFont, ImageDraw, Image
from smbus import SMBus       #  These are the only two variant lines !!
i2cbus = SMBus(2)             # NOTE: Beaglebone Green Wireless uses bus 2!
oled = ssd1306(i2cbus)

draw = oled.canvas

while True:

 # "draw" onto this canvas, then call display() to show on the OLED.
 draw = oled.canvas

# show SPLASH screen
# set font to 17 for yellow area splash title
 font = ImageFont.truetype('/home/debian/Downloads/ssd1306-master/FreeSans.ttf', 17)
 draw.text((1, 1), 'BEAGLEBONE', font=font, fill=1)
# put on the dawg, which is capital 'S' in the below font
 font = ImageFont.truetype('/home/debian/Downloads/ssd1306-master/Doggon.ttf', 50)

draw.text((1, 23), ' S', font=font, fill=1)

 # now show splash screen, then sleep, then clear for next screen
 oled.display()
 sleep(3)
 oled.cls()

 # set up, display overall stats screen
 # set font to 13 for yellow area
 font = ImageFont.truetype('/home/debian/Downloads/ssd1306-master/FreeSans.ttf', 13)

 # get, display date and time
 draw.text((1, 1), str(datetime.now().strftime('%a  %b  %d  %H:%M:%S')), font=font, fill=1)

 # reset font for four smaller lines
 font = ImageFont.truetype('FreeSans.ttf', 10)

 # get, return Linux kernel version
 def uname_r():
    k = os.popen('uname -r')
    rev = str(k.read())
    # strip out trailing chars for cleaner output
    return "KERNEL: %s" % rev.rstrip('\r\n')

  # display kernel version
 draw.text((1, 15),   uname_r(),  font=font, fill=1)

 # get, return wlan0's current IP address
 def wlan_ip():
    f = os.popen('ifconfig wlan0 | grep "inet\ addr" | cut -c 21-33')
    ip = str(f.read())
    # strip out trailing chars for cleaner output
    return "WIFI IP: %s" % ip.rstrip('\r\n')

 # display the IP address
 draw.text((1, 28),    wlan_ip(),  font=font, fill=1)

 # get amount of free memory
 def mem_usage():

    usage = psutil.virtual_memory()
    return "MEM USED: %s KB" % (int(str(usage.used)) / 1024)

 # display amount of free memory
 draw.text((1, 41),    mem_usage(),  font=font, fill=1)

 # get disk usage
 def disk_usage(dir):
    usage = psutil.disk_usage(dir)
    return "SD CARD USED: %.0f%%" % (usage.percent)
 # display disk usage
 draw.text((1, 53), disk_usage('/'),  font=font, fill=1)

 # now show the entire stats screen, then clear
 oled.display()
 sleep(5)
 oled.cls()

 # read bme280 data - limit 2-digit accuracy
 def get_temp():
  from bme280 import readBME280All
  temperature,pressure,humidity = readBME280All()
 # return "%sF" % str(((temperature)*9/5)+32)
  return "%.2f degrees F" % (((temperature)*9/5)+32)

 def get_pressure():
  from bme280 import readBME280All
  temperature,pressure,humidity = readBME280All()
 # return "%s hPa" % str(pressure)
  return "%.2f inches mercury" % ((pressure)/33.8638866667)

 def get_humidity():
  from bme280 import readBME280All
  temperature,pressure,humidity = readBME280All()
 # return "%s percent" % str(humidity)
  return "%.2f%% humidity" % humidity

 font = ImageFont.truetype('/home/debian/Downloads/ssd1306-master/FreeSans.ttf', 13)
 draw.text((1, 1), 'Ambient Conditions', font=font, fill=1)

 font = ImageFont.truetype('/home/debian/Downloads/ssd1306-master/FreeSans.ttf', 12)
 draw.text((1, 20),  get_temp(),  font=font, fill=1)
 draw.text((1, 35),  get_pressure(),  font=font, fill=1)
 draw.text((1, 50),  get_humidity(),  font=font, fill=1)

 oled.display()
 sleep(5)
 oled.cls()

 # begin series of LARGE stats
 # LARGE display - get wifi wlan0 signal strength
 def get_wifi():
    cmd = "iwconfig wlan0 | grep Signal | /usr/bin/awk '{print $4}' | /usr/bin/cut -d'=' -f2"
    strDbm = os.popen(cmd).read()
    dbm = int(strDbm)
    quality = 2 * (dbm + 100)
    if strDbm:
     return("{0} dbm = {1}%".format(dbm, quality))
    else:
     return("No Wifi")

 font = ImageFont.truetype('/home/debian/Downloads/ssd1306-master/FreeSans.ttf', 17)
 draw.text((1, 1), 'WIFI SIGNAL', font=font, fill=1)
 font = ImageFont.truetype('/home/debian/Downloads/ssd1306-master/FreeSans.ttf', 15)
 draw.text((1, 30),    get_wifi(),  font=font, fill=1)

 oled.display()
 sleep(3)
 oled.cls()

 # LARGE display - uptime
 def get_uptime():
    uptime = os.popen("uptime")
    ut = str(uptime.read())
    # strip out trailing chars for cleaner output
    return "%s" % ut.rstrip('\r\n')

 font = ImageFont.truetype('/home/debian/Downloads/ssd1306-master/FreeSans.ttf', 17)
 draw.text((1, 1),    '    UPTIME',  font=font, fill=1)

# now smaller font needed and wrap text to show info
 font = ImageFont.truetype('/home/debian/Downloads/ssd1306-master/FreeSans.ttf', 11)
 import textwrap
 lines = textwrap.wrap(get_uptime(), width=25)
 # what 'line' to start info
 y_text = 20
 w = 1 # we'll always start at left side of OLED
 for line in lines:
    width, height = font.getsize(line)
    draw.text((w, y_text), line, font=font, fill=1)
    y_text += height

 oled.display()
 sleep(4)
 oled.cls()

 # LARGE display - wlan0's IP address
 def wlan_lip():
    f = os.popen('ifconfig wlan0 | grep "inet\ addr" | cut -c 21-33')
    ip = str(f.read())
    # strip out trailing chars for cleaner output
    return "%s" % ip.rstrip('\r\n')

 font = ImageFont.truetype('/home/debian/Downloads/ssd1306-master/FreeSans.ttf', 17)

 draw.text((1, 1),   ' IP ADDRESS', font=font, fill=1)
 font = ImageFont.truetype('/home/debian/Downloads/ssd1306-master/FreeSans.ttf', 16)
 draw.text((1, 30),    wlan_lip(),  font=font, fill=1)

 oled.display()
 sleep(3)
 oled.cls()

 # LARGE display - amount of free memory
 def mem_usagel():
    usage = psutil.virtual_memory()
    return "%s KB" % (int(str(usage.used)) / 1024)

 font = ImageFont.truetype('/home/debian/Downloads/ssd1306-master/FreeSans.ttf', 17)

 draw.text((1, 1),   'MEM USED', font=font, fill=1)
 draw.text((1, 30),    mem_usagel(),  font=font, fill=1)

 oled.display()
 sleep(3)
 oled.cls()

 # LARGE display - Beaglebone's serial number
 def get_serial():
    serial = os.popen('/usr/local/bin/get_serial_no')
    snum = str(serial.read())
    # strip out trailing chars for cleaner output
    return "%s" % snum.rstrip('\r\n')

 font = ImageFont.truetype('/home/debian/Downloads/ssd1306-master/FreeSans.ttf', 17)
 draw.text((1, 1),    'SERIAL NBR',  font=font, fill=1)
 # now smaller font needed
 font = ImageFont.truetype('/home/debian/Downloads/ssd1306-master/FreeSans.ttf', 15)
 draw.text((1, 30),    get_serial(),  font=font, fill=1)

 oled.display()
 sleep(3)
 oled.cls()

 # get, return current local WX
 def do_wx():
    f = os.popen('/usr/local/bin/currentwx')
    wx = str(f.read())
    # strip out trailing chars for cleaner output
    return "%s" % wx.rstrip('\r\n')

 # display the WX
 font = ImageFont.truetype('/home/debian/Downloads/ssd1306-master/FreeSans.ttf', 13)
 draw.text((1, 1),    'CURRENT WEATHER:',  font=font, fill=1)
 font = ImageFont.truetype('/home/debian/Downloads/ssd1306-master/FreeSans.ttf', 12)
 draw.text((1, 15),    ' PINELLAS PARK, FL',  font=font, fill=1)
 draw.text((1, 41),    do_wx(),  font=font, fill=1)
 

oled.display()
sleep(3)
oled.cls()      # Oled still on, but screen contents now blacked out
 


Wifi That Actually Works for the Beaglebone Black

I like the Beaglebone and think the Beaglebone Green Wireless is one of the best small-form-factor GNU/Linux computers. So I thought it would be nice to try a Beaglebone Black.

Boy, was I wrong! The Beaglebone Black turned out to be one of the most frustrating computers I've run across in a long time: configuring wifi was a pain in the fucking ass. I tried at least four different wifi USB dongles (there is no on-board wifi), including one that was touted as actually working (rtl8192cu). Wasted an entire day and got that sour taste in the stomach thinking I had purchased a pig-in-the-poke.

Well, life is now good. I *finally* found one that works and holds a steady connection. It's from the folks at Logic Supply:

It is a MediaTek MT7601 (Ralink 7601).  It configured right away using connmanctl. The Media Tek folks even offer drivers for GNU/Linux! Of course, you don't have to install anything if you're using kernel 4.4.x - there's firmware and a loadable module included - works great!

My Beaglebone Black has now joined my stable of working SBCs and has been spared from the parts bin.

Saturday, August 13, 2016

A Simple Calculator for the Arduino

Here's a simple calculator for the Arduino. I recently acquired a cheap ($12) 320x240 TFT and wanted to learn how to program these microcontrollers. There are some amazing demos out there for these TFTs - look for mcufriend_kbv on the Arduino.cc forums - great stuff!

Here's the calculator - I got it working on my TFT (which uses an ILI9341) by swapping x/y coordinates in the code... for some reason the swap() function didn't work for me - or perhaps it was one needs restart the IDE between orientation changes on the TFT?

NOTE: This is a *simple* calculator. I'll be working on expanding and modifying the code at some point to make it into a reasonable calculator. Don't do your taxes using this calculator!

// calculator.ino
// originally by max:
// https://github.com/maxpromer/Arduino-Touch-Calculator
// hacked by willie
// 
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_TFTLCD.h> // Hardware-specific library
#include <TouchScreen.h>

#define YP A3  // must be an analog pin, use "An" notation!
#define XM A2  // must be an analog pin, use "An" notation!
#define YM 9   // can be a digital pin
#define XP 8   // can be a digital pin

#define TS_MINX 150
#define TS_MINY 120
#define TS_MAXX 920
#define TS_MAXY 940

TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);

#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
// optional
#define LCD_RESET A4

// Assign human-readable names to some common 16-bit color values:
#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF

#define MINPRESSURE 10
#define MAXPRESSURE 1000

Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);

// keypad array
String Key[4][4] = {
  { "7", "8", "9", "/" },
  { "4", "5", "6", "*" },
  { "1", "2", "3", "-" },
  { "C", "0", "=", "+" }
};

String N1, N2, ShowSC, opt;
bool updata=false;
float answers=-1;

void setup() {
  Serial.begin(9600);
  tft.reset();
  tft.begin(0x9341); // SDFP5408
  tft.setRotation(2); //portrait, power on top
  tft.fillScreen(BLACK);
 
  tft.fillRect(0, 80, 240, 240, WHITE);
  tft.drawFastHLine(0, 80, 240, BLACK);
  tft.drawFastHLine(0, 140, 240, BLACK);
  tft.drawFastHLine(0, 200, 240, BLACK);
  tft.drawFastHLine(0, 260, 240, BLACK);
  tft.drawFastHLine(0, 320-1, 240, BLACK);

  tft.drawFastVLine(0, 80, 240, BLACK);
  tft.drawFastVLine(60, 80, 240, BLACK);
  tft.drawFastVLine(120, 80, 240, BLACK);
  tft.drawFastVLine(180, 80, 240, BLACK);
  tft.drawFastVLine(240-1, 80, 240, BLACK);

  for (int y=0;y<4;y++) {
    for (int x=0;x<4;x++) {
      tft.setCursor(22 + (60*x), 100 + (60*y));
      tft.setTextSize(3);
      tft.setTextColor(BLACK);
      tft.println(Key[y][x]);
    }
  }
}

void loop() {
  TSPoint p = waitTouch();
 
  updata = false;
  for (int i1=0;i1<4;i1++) {
    for (int i2=0;i2<4;i2++) {
// change to swap x,y
//      if ((p.x>=240-((i1+1)*60)+1&&p.x<=240-(i1*60)-1)&&(p.y>=(i2*60)+1&&p.y<=((i2+1)*60)-1)) {
      if ((p.y>=240-((i1+1)*60)+1&&p.y<=240-(i1*60)-1)&&(p.x>=(i2*60)+1&&p.x<=((i2+1)*60)-1)) {
        if ((i1<=2&&i2<=2)||(i1==3&&i2==1)) {
          if (opt==0) {
            if (answers!=-1) answers = -1;
            N1 = N1 + Key[i1][i2];
            ShowSC = N1;
          } else {
            N2 = N2 + Key[i1][i2];
            ShowSC = opt + N2;
          }
        } else {
          if (Key[i1][i2]=="C") {
            N1 = N2 = "";
            opt = "";
            answers = 0;
            ShowSC = N1;
          } else if (i2==3) {
            if (N1=="") N1 = String(answers);
            opt = Key[i1][i2];
            ShowSC = Key[i1][i2];
          } else if (Key[i1][i2]=="=") {
            // perform calculation
            if (opt=="+") answers = N1.toInt() + N2.toInt();
            else if (opt=="-") answers = N1.toInt() - N2.toInt();
            else if (opt=="*") answers = N1.toInt() * N2.toInt();
            else if (opt=="/") answers = N1.toInt() / N2.toInt();
            N1 = N2 = opt = "";
            ShowSC = answers;
          }
        }
        updata = true;
      }
    }
  }
  if (updata) {
    tft.fillRect(0, 0, 240, 80, BLACK);
    tft.setCursor(10, 10);
    tft.setTextSize(3);
    tft.setTextColor(WHITE);
    tft.println(ShowSC);
  }
  delay(300);
}

// get x,y touch coordinates
TSPoint waitTouch() {
  TSPoint p;
  do {
    p = ts.getPoint();
    pinMode(XM, OUTPUT);
    pinMode(YP, OUTPUT);
  } while((p.z < MINPRESSURE )|| (p.z > MAXPRESSURE));
  p.x = map(p.x, TS_MINX, TS_MAXX, tft.width(), 0);
  p.y = map(p.y, TS_MINY, TS_MAXY, tft.height(), 0);
//  Serial.println('x'); Serial.println(p.x);
//  Serial.println('y'); Serial.println(p.y);
  return p;
}

Wednesday, August 10, 2016

Yet Another Confusing Arduino Issue - YUN

found this today:

concerning the Yun on arduino.cc

quote:

As many of you know, there is a split with the Arduino organization. The two are generally two Internet domains that encompass the two parties - arduino.cc and arduino.org. To be clear on the separation, arduino.cc does NOT have the Arduino Yun, but is creating a new product with a new partner. Arduino.org has the Yun, Yun mini, Tian, Uno wireless - and other similar products.

There are several issues for developers.
- what are the new products
- support for hardware
- support for software
- will some libraries still be support, like Bridge
- forum support

For .org, it is creating new products and we see them every few months. This is organization is being helped by doghunter. Neither the .org or doghunter have stated any allegiance to open source. The issue this creates is that some developer are unlikely to contribute to the massive software pool mostly available on .cc  One small note, if you look on the back of the original Yun you will see a "dog Hunter" imprint. It is underneath the Ethernet port, on the back side of the PC board.

For .cc, which is known as Genuino in most part of the world - expect the USA, they have struggled to produce significantly new products, and their new partnerships with Adafruit and Seeed have not yield any significantly new products. The challenge is - how will they address the growing IOT market - no response yet on this.


On the libraries, both groups appear to be making advancement. However, new contributions are continuing with .cc, not as much with .org.

On the support forums, .cc definitely had the advantage.


so there you have it... but of course you may also own a Seeedstudio Cloud or a Dragino Yun Shield

i like them all, but really when you can have a Raspberry Pi 3 Model B or a Raspberry Pi Zero

why? well, if you're a real-time hacker and have a need for speed, low power consumption, the 'Yun' model may work for you - best of both worlds?

Tuesday, August 9, 2016

An SSD1306 OLED System Monitor for the Raspberry Pi

 
Here's a simple system monitor for the Raspberry Pi using an SSD1306 OLED and a BME280 sensor (to get temperature, barometric pressure and humidity). You'll need to construct an iC2 'Y' cable and plug into the GPIO pins on your Pi. Make sure to enable iC2 for Raspbian, then download and install rm-hull's Github SSD1306 library, along with Matt Hawkin's bme280.py script. Put everything into the same directory.

After testing to make sure that your sensor and OLED are working, use this script to get a host of information about your Pi, ambient conditions and even local weather.

Here's the script:

#!/usr/bin/env python
# myssd.py version 0.8 - a simple system monitor for the Raspberry Pi
# adapted from rmhull's wonderful ssd1306 Python lib by bball@tux.org
# crafted for the dual-color 128x96 OLED w/yellow top area
# 060316 - added date, CPU temp, wlan0 IP addr, memory, sd card used
# 060416 - added splash screen, general code cleanup, KB output for memory
# 061316 - added local current WX conditions
# 080616 - added wifi, serial number, uptime with text wrap
# 080816 - added BME280 output of temp, pressure, and humidity

import os
import psutil
from lib_oled96 import ssd1306
from time import sleep
from datetime import datetime
from PIL import ImageFont, ImageDraw, Image
#font = ImageFont.load_default()

from smbus import SMBus                  #  These are the only two variant lines !!
i2cbus = SMBus(1)                        #
oled = ssd1306(i2cbus)

draw = oled.canvas  
# set font to 13 for yellow area
font = ImageFont.truetype('FreeSans.ttf', 13)

# show splash screen
draw.text((1, 1), 'RASPBIAN SYSMON', font=font, fill=1)
logo = Image.open('rpi.png')
draw.bitmap((42, 16), logo, fill=1)

oled.display()
sleep(3)
oled.cls()

while True:

 # "draw" onto this canvas, then call display() to send the canvas contents to the hardware.
 draw = oled.canvas  

 # set font to 13 for yellow area
 font = ImageFont.truetype('FreeSans.ttf', 13)

 # get, display date and time
 draw.text((1, 1), str(datetime.now().strftime('%a  %b  %d  %H:%M:%S')), font=font, fill=1)

 # reset font for four smaller lines
 font = ImageFont.truetype('FreeSans.ttf', 10)

 # get, return CPU's current temperature
 def cpu_temp():
    tempF = (((int(open('/sys/class/thermal/thermal_zone0/temp').read()) / 1000)*9/5)+32)
    return "CPU TEMP: %sF" % str(tempF)
 
  # display CPU temp
 draw.text((1, 15),    cpu_temp(),  font=font, fill=1)

 # get, return wlan0's current IP address
 def wlan_ip():
    f = os.popen('ifconfig wlan0 | grep "inet\ addr" | cut -c 21-33')
    ip = str(f.read())
    # strip out trailing chars for cleaner output
    return "WIFI IP: %s" % ip.rstrip('\r\n')

 # display the IP address
 draw.text((1, 28),    wlan_ip(),  font=font, fill=1)

 # get amount of free memory
 def mem_usage():
    usage = psutil.virtual_memory()
    return "MEM USED: %s KB" % (int(str(usage.used)) / 1024)

 # display amount of free memory
 draw.text((1, 41),    mem_usage(),  font=font, fill=1)

 # get disk usage
 def disk_usage(dir):
    usage = psutil.disk_usage(dir)
    return "SD CARD USED: %.0f%%" % (usage.percent)

 # display disk usage
 draw.text((1, 53), disk_usage('/'),  font=font, fill=1)
 oled.display()

 sleep(6)
 oled.cls()      # Oled still on, but screen contents now blacked out

# LARGE display - get wifi wlan0 signal strength
 def get_wifi():
    cmd = "iwconfig wlan0 | grep Signal | /usr/bin/awk '{print $4}' | /usr/bin/cut -d'=' -f2"
    strDbm = os.popen(cmd).read()
    dbm = int(strDbm)
    quality = 2 * (dbm + 100)
    if strDbm:
     return("{0} dbm = {1}%".format(dbm, quality))
    else:
     return("No Wifi")

 font = ImageFont.truetype('/home/pi/Downloads/ssd1306-master/FreeSans.ttf', 17)
 draw.text((1, 1), 'WIFI SIGNAL', font=font, fill=1)
 font = ImageFont.truetype('/home/pi/Downloads/ssd1306-master/FreeSans.ttf', 15)
 draw.text((1, 30),    get_wifi(),  font=font, fill=1)

 oled.display()
 sleep(2)
 oled.cls()

# LARGE display - uptime
 def get_uptime():
    uptime = os.popen("uptime")
    ut = str(uptime.read())
    # strip out trailing chars for cleaner output
    return "%s" % ut.rstrip('\r\n')

 font = ImageFont.truetype('/home/pi/Downloads/ssd1306-master/FreeSans.ttf', 17)
 draw.text((1, 1),    '    UPTIME',  font=font, fill=1)

 # now smaller font needed and wrap text to show info
 font = ImageFont.truetype('/home/pi/Downloads/ssd1306-master/FreeSans.ttf', 11)
 import textwrap
 lines = textwrap.wrap(get_uptime(), width=25)
 # what 'line' to start info
 y_text = 20
 w = 1 # we'll always start at left side of OLED
 for line in lines:
    width, height = font.getsize(line)
    draw.text((w, y_text), line, font=font, fill=1)
    y_text += height

 oled.display()
 sleep(2)
 oled.cls()

# LARGE display - get, display serial number
 def get_serial():
    cmd = "cat /proc/cpuinfo | grep Serial | /usr/bin/awk '{print $3}'"
    strDbm = os.popen(cmd).read()
    snum = str(strDbm)
    return "%s" % snum.rstrip('\r\n')

 font = ImageFont.truetype('/home/pi/Downloads/ssd1306-master/FreeSans.ttf', 17)
 draw.text((1, 1), 'Serial Number', font=font, fill=1)
 font = ImageFont.truetype('/home/pi/Downloads/ssd1306-master/FreeSans.ttf', 14)
 draw.text((1, 30),    get_serial(),  font=font, fill=1)

 oled.display()
 sleep(1)
 oled.cls()

# read bme280 data
 def get_temp():
  from bme280 import readBME280All
 
  temperature,pressure,humidity = readBME280All()
 
  return "%.2f degrees F" % (((temperature)*9/5)+32)

 def get_pressure():
  from bme280 import readBME280All
 
  temperature,pressure,humidity = readBME280All()
 
  return "%.2f inches mercury" % ((pressure)/33.8638866667)

 def get_humidity():
  from bme280 import readBME280All
 
  temperature,pressure,humidity = readBME280All()
 
  return "%.2f%% humidity" % humidity

 font = ImageFont.truetype('/home/pi/Downloads/ssd1306-master/FreeSans.ttf', 13)
 draw.text((1, 1), 'Ambient Conditions', font=font, fill=1)

 font = ImageFont.truetype('/home/pi/Downloads/ssd1306-master/FreeSans.ttf', 11)
 draw.text((1, 20),  get_temp(),  font=font, fill=1)
 draw.text((1, 35),  get_pressure(),  font=font, fill=1)
 draw.text((1, 50),  get_humidity(),  font=font, fill=1)

 oled.display()
 sleep(10)
 oled.cls()

 # get, return current WX
 def do_wx():
    f = os.popen('/usr/local/bin/currentwx')
    wx = str(f.read())
    # strip out trailing chars for cleaner output
    return "%s" % wx.rstrip('\r\n')

 font = ImageFont.truetype('FreeSans.ttf', 13)
 draw.text((1, 1),    'CURRENT WEATHER:',  font=font, fill=1)

 # display the WX
 font = ImageFont.truetype('FreeSans.ttf', 12)
 draw.text((1, 15),    'PINELLAS PARK',  font=font, fill=1)
 draw.text((1, 41),    do_wx(),  font=font, fill=1)

 oled.display()
 sleep(10)
 oled.cls()      # Oled still on, but screen contents now blacked out

escposf - A Thermal Printer Filter and Control Command for Linux

escposf - A Thermal Printer Filter and Control Command for Linux

There are millions of thermal printers out there! After recently acquiring a Raspberry Pi 3 computer, I got interested in obtaining and using a matching inexpensive printer. This page provides a short description of a quick hack I ginned up to control printing from the command line or through shell scripts. The command, escposf, simply outputs the requisite control characters to change how the printer prints on the thermal printer. BTW, I first used a thermal printer back in the day when you had to strap a sheet of thermal paper on a metal cylinder, call a remote transmitter, then slam the phone into the rubber cups on the receiving unit - faxes were pretty crude back then!

Note that escposf sends control codes directly to the printer. On the other hand, if you just want a straightforward, easy-to-build CUPS driver, check out the zj-58 filter.  The zj-58 filter rasterizes your document image and sends that image to the printer. I created a LibreOffice Writer document template for use with the zj-58 filter. The template has the following settings: 58mm width, 219mm length, with 0 top and bottom margins and a 2mm left margin and 2.5mm right margin - this works very well, too! As you can see, you can create just about any printout you like, such as tickets, etc.





Using escposf for direct printing is pretty easy. Download the source code and build it!

/*
 * escposf - send or embed printing control data for an ESC/POS format thermal printer.
 *
 * (c)2016 by bball@tux.org for his Raspberry Pi 3 computer's thermal printer
 * Distributed under GNU GPLV3. Get it, read it, use it, love it.
 *
 * note that not all ESC/POS-compatible printers share the same ESC/POS codes!
 *
 * the primary use of this command is to set the printing mode or embed ESC/POS codes
 * into a text stream or file for printing
 *
 * version 0.1 - working bold, underline, alignment, reverse, and 4 types of text types
 * version 0.2 - changed name from escpos_util to escposf (ESC/POS filter)
 *               added font a/b selection, overstrike
 * version 0.3 - added printer initialization (clear buffer, reset to 'standard' mode)
 * version 0.4 - added code cleanup, printer_test shell script to package
 * version 0.5 - removed non-working options (boldface, double-strike)
 */

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

/* additions for command-line processing */
#include <getopt.h>
#include <argp.h>  /* <-- comment out header to build for Mac OS X */

/* command-line help */
const char *version="escposf 0.5";
const char *bug_address="<bball@tux.org>";
static char doc[]="ESC/POS thermal printer command";

void usage() {
printf("%s - %s\n",version,doc);
printf("Control ESC/POS thermal printer output.\n");
printf("Typical usage:\n");
printf("escposf --a 1 | lpr -P lpname -o raw (set text centering)\n");
printf("escposf --f 1 >filename.txt          (embed font selection)\n");
printf("Command-line options are:\n");
printf("[--align n]  [--a n] - align text (0-left, 1-ctr, 2-rght)\n");
printf("[--font n]   [--f n] - select font (0-normal, 1-condensed)\n");
printf("[--init]     [--i]   - reset [initialize] printer\n");
printf("[--rev n]    [--r n] - reverse text (0-off, 1-on)\n");
printf("[--text n]   [--t n] - set text (0-norm, 1-2Xh, 2-2Xw, 3-large)\n");
printf("[--underl n] [--u n] - underline (0-off, 1-1 dot, 2-2 dots)\n");
printf("[--help]     [--h]   - display this message \n");
printf("[--version]  [--v]   - display program version \n");
printf("bug reports, fixes to: %s\n",bug_address);
}

/* control alignment */
int align(int option)
{
    switch (option) {
        case 0: putchar(0x1b); putchar(0x61); putchar(0x00); exit(0); break;
        case 1: putchar(0x1b); putchar(0x61); putchar(0x01); exit(0); break;
        case 2: putchar(0x1b); putchar(0x61); putchar(0x02); exit(0); break;
    }
    printf("ERROR: align option out of range (0-2).\n");
    exit(0); 
}

/* reset, initialize printer */
void init() { putchar(0x1b); putchar(0x40); }

/* control font type (normal, condensed) */
int font(int option)
{
    switch (option) {
        case 0: putchar(0x1b); putchar(0x4d); putchar(0x00); exit(0); break;
        case 1: putchar(0x1b); putchar(0x4d); putchar(0x01); exit(0); break;
    }
    printf("ERROR: font-selection option out of range (0 or 1).\n");
    exit(0); 
}

/* control reverse text */
int reverse(int option)
{
    switch (option) {
        case 0: putchar(0x1d); putchar(0x42); putchar(0x00); exit(0); break;
        case 1: putchar(0x1d); putchar(0x42); putchar(0x01); exit(0); break;
    }
    printf("ERROR: reverse text option out of range (0 or 1).\n");
    exit(0); 
}

/* control underlining */
int underline(int option)
{
    switch (option) {
        case 0: putchar(0x1b); putchar(0x2d); putchar(0x00); exit(0); break;
        case 1: putchar(0x1b); putchar(0x2d); putchar(0x01); exit(0); break;
        case 2: putchar(0x1b); putchar(0x2d); putchar(0x02); exit(0); break;
    }
    printf("ERROR: underline option out of range (0-2).\n");
    exit(0); 
}

/* control text type */
int text(int option)
{
    switch (option) {
        case 0: putchar(0x1b); putchar(0x21); putchar(0x00); exit(0); break;
        case 1: putchar(0x1b); putchar(0x21); putchar(0x10); exit(0); break;
        case 2: putchar(0x1b); putchar(0x21); putchar(0x20); exit(0); break;
        case 3: putchar(0x1b); putchar(0x21); putchar(0x30); exit(0); break;
    }
    printf("ERROR: text option out of range (0-3).\n");
    exit(0); 
}

int main(int argc, char *argv[]) {

    int c = 0;
        static struct option long_options[] = {
            {"align", 1, 0, 'a'},
            {"font", 1, 0, 'f'},
            {"help", 0, 0, 'h'},
            {"init", 0, 0, 'i'},
        {"rev", 1,0, 'r'},
            {"text", 1, 0, 't'},
            {"underl", 1, 0, 'u'},
            {"version", 0, 0, 'v'}
    }; /* end of long options structure */

    while (1) {
        c = getopt_long(argc, argv, "?:a:f:h:i:r:t:u:v:", long_options, 0);
        if (c == -1) {  usage();  break; }
        switch(c) {
            case 'a': align(atoi(optarg)); break;
            case 'f': font(atoi(optarg)); break;
            case 'h': usage(); exit(0); break;
            case 'i': init(); exit(0); break;
            case 'r': reverse(atoi(optarg)); break;
            case 't': text(atoi(optarg)); break;
            case 'u': underline(atoi(optarg)); break;
            case 'v': printf("%s - %s\n",version,doc); exit(0); break;
        case ':': fprintf(stderr, "missing arg\n"); exit(-1); break;
             default: usage(); exit(0);
        } /* end switch */
    } /* end while loop */
  return 0;
}


A simple make will create the command for you. I keep it under /usr/local/bin.

Use the --help, --h, or --? options to get a quick list of commands:

$ escposf
escposf 0.5 - ESC/POS thermal printer command
Control ESC/POS thermal printer output.
Typical usage:
escposf --a 1 | lpr -P lpname -o raw (set text centering)
escposf --f 1 >filename.txt          (embed font selection)
Command-line options are:
[--align n]  [--a n] - align text (0-left, 1-ctr, 2-rght)
[--font n]   [--f n] - select font (0-normal, 1-condensed)
[--init]     [--i]   - reset [initialize] printer
[--rev n]    [--r n] - reverse text (0-off, 1-on)
[--text n]   [--t n] - set text (0-norm, 1-2Xh, 2-2Xw, 3-large)
[--underl n] [--u n] - underline (0-off, 1-1 dot, 2-2 dots)
[--help]     [--h]   - display this message
[--version]  [--v]   - display program version

Instead of going into a long description, here's an example script I use to print a shopping list (helped to get rid of a pad of paper and pencil off the kitchen counter):

#!/bin/sh
# psl - print shopping list on thermal printer

# graphic pathname
GRAPHIC=/home/pi/Desktop/thermal_pics/cat_smile.png

# text list pathname
LIST=/home/pi/Desktop/shoplist

# print custom header
/usr/local/bin/png2escpos $GRAPHIC | /usr/bin/lpr -Pthermie -o raw

# set reverse character printing
/usr/local/bin/escposf -r 1 >/tmp/out.txt

# set 2Xw font
/usr/local/bin/escposf -t 3 >>/tmp/out.txt

echo "*SHOPPING LIST*" >>/tmp/out.txt

# turn off reverse
/usr/local/bin/escposf -r 0 >>/tmp/out.txt

# select normal font
/usr/local/bin/escposf -t 0 >>/tmp/out.txt

# save each line to /tmp file (avoids multiple print jobs)
while read line; do
    echo "$line" >>/tmp/out.txt
done < $LIST

# reset to normal font
/usr/local/bin/escposf -t 0 >>/tmp/out.txt

# print the list
/usr/bin/lpr -Pthermie -o raw /tmp/out.txt

# clean up
/bin/rm -fr /tmp/out.txt


Note that I use the png2escpos utility to print little graphics at the top of the tape!

Oh, and here's a little test script for use with your printer (you'll need to edit it to suit your system):

#!/bin/sh
# ptest - test some escposf commands for a 58mm ESC/POS thermal printer
#
# note: modes are persistent between settings, not power-cycling
#
FILE=/tmp/out.txt

# initialize printer
escposf --i >/tmp/out.txt
# set 'normal' text
escposf --t 0 >/tmp/out.txt
echo "Normal text." >>/tmp/out.txt
echo "ABCDEFGHIJKLMNOPQRSTUVWXYZ" >>/tmp/out.txt
echo "abcdefghijklmnopqrstuvwxyz" >>/tmp/out.txt
echo "0123456789" >>/tmp/out.txt
echo "?&'-@';]{]}/,.$:%^\!&*)(_~" >>/tmp/out.txt

escposf --a 0 >>/tmp/out.txt
echo "Left aligned." >>/tmp/out.txt

escposf --a 1 >>/tmp/out.txt
echo "Centered." >>/tmp/out.txt

escposf --a 2 >>/tmp/out.txt
echo "Right aligned." >>/tmp/out.txt

escposf --a 0 >>/tmp/out.txt
escposf --r 1 >>/tmp/out.txt
echo "REVERSED TEXT" >>/tmp/out.txt

escposf --r 0 >>/tmp/out.txt
escposf --t 1 >>/tmp/out.txt
echo "2X high." >>/tmp/out.txt

escposf --r 1 >>/tmp/out.txt
escposf --t 1 >>/tmp/out.txt
echo "Reversed 2X high." >>/tmp/out.txt

escposf --r 0 >>/tmp/out.txt

escposf --t 2 >>/tmp/out.txt
echo "2X wide" >>/tmp/out.txt

escposf --r 1 >>/tmp/out.txt
escposf --t 2 >>/tmp/out.txt
echo "Reversed 2X" >>/tmp/out.txt

escposf --r 0 >>/tmp/out.txt
escposf --t 3 >>/tmp/out.txt
echo "Large text." >>/tmp/out.txt

escposf --r 1 >>/tmp/out.txt
escposf --t 3 >>/tmp/out.txt
echo "Reversed large" >>/tmp/out.txt

escposf --r 0 >>/tmp/out.txt
escposf --t 0 >>/tmp/out.txt
escposf --u 2 >>/tmp/out.txt
echo "Normal underlined." >>/tmp/out.txt

escposf --u 0 >>/tmp/out.txt
escposf --f 1 >>/tmp/out.txt
echo "Font B." >>/tmp/out.txt
echo "ABCDEFGHIJKLMNOPQRSTUVWXYZ" >>/tmp/out.txt
echo "abcdefghijklmnopqrstuvwxyz" >>/tmp/out.txt
echo "0123456789" >>/tmp/out.txt
echo "?&'-@';]{]}/,.$:%^\!&*)(_~" >>/tmp/out.txt

escposf --t 0 >>/tmp/out.txt
echo "End of test.\n\n\n" >>/tmp/out.txt

/usr/bin/lpr -Pthermie -o raw /tmp/out.txt
/bin/rm -fr /tmp/out.txt

 


I hope you find escposf useful.