Showing posts with label temperature. Show all posts
Showing posts with label temperature. Show all posts

Monday, September 29, 2025

Plotting Temperature with the ESP32C3 Dev Module and Node Red

  

-----

Microcontrollers are getting really cheap.  They were already cheap, but now they seem crazy cheap.  Even with onboard WiFi, Bluetooth, and a small OLED display we picked up this ESP32C3 Dev Module for about ~$2 USD; so we had to get two.   One turned into an extremely useful and accurate clock while this one will be a temperature logger.

-----

We used a DS18B20 temperature sensor.  The simple connection of the sensor to the ESP32C3 looks like this: 

 
-----
Now you're ready to use the Arduino IDE to upload the software sketch at the end of this post.  Basically the software polls the DS18B20 for a temperature reading every 60 seconds and posts it as a webpage. Our ESP32C3 is connected to our LAN at 192.168.1.67 so we see this in our web browser:
----
But wait, that's not all... We have Node Red running on a Raspberry PI and parse what this web page would look like every 60 seconds to graph the reading.  This isn't a Node Red tutorial, but the flow looks like this and we will post the flow below for you to import.
 

-----

So, what do you get?   A graph like this.  Note that we are charting two temperatures on our chart.  Your chart will only show the ESP32 line: 

 -----

Now for the software code we promised.   Here is the Node Red flow to import:

 [
    {
        "id": "e19a60a2f08ac386",
        "type": "inject",
        "z": "c0bb5756099d6dbc",
        "name": "Every 60 secs",
        "props": [],
        "repeat": "60",
        "crontab": "",
        "once": true,
        "onceDelay": "1",
        "topic": "",
        "x": 160,
        "y": 120,
        "wires": [
            [
                "018abb4b7bfb7e86",
                "41314d52d14f7623"
            ]
        ]
    },
    {
        "id": "41314d52d14f7623",
        "type": "http request",
        "z": "c0bb5756099d6dbc",
        "name": "",
        "method": "GET",
        "ret": "txt",
        "paytoqs": "ignore",
        "url": "http://192.168.1.67/",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 150,
        "y": 180,
        "wires": [
            [
                "2fb863c6f3188fd2"
            ]
        ]
    },
    {
        "id": "2fb863c6f3188fd2",
        "type": "function",
        "z": "c0bb5756099d6dbc",
        "name": "Parse ESP32 Temp",
        "func": "var payload = msg.payload;\nvar match = payload.match(/Temperature is: ([0-9.]+)/);\n\nif (match) {\n    msg.payload = parseFloat(match[1]);  // Fahrenheit\n    msg.topic = \"ESP32\";   // Add this line\n} else {\n    msg.payload = null;\n}\nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 430,
        "y": 180,
        "wires": [
            [
                "ab11f8582b84df82",
                "e024d71190743b50",
                "e146810a6d814e42"
            ]
        ]
    },
    {
        "id": "ab11f8582b84df82",
        "type": "debug",
        "z": "c0bb5756099d6dbc",
        "name": "ESP32 TempF",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 880,
        "y": 180,
        "wires": []
    },
    {
        "id": "e024d71190743b50",
        "type": "ui_gauge",
        "z": "c0bb5756099d6dbc",
        "name": "",
        "group": "7",
        "order": 5,
        "width": 5,
        "height": 4,
        "gtype": "donut",
        "title": "ESP32 (°F)",
        "label": "°F",
        "format": "{{value}}",
        "min": "80",
        "max": "110",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ff0000"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 870,
        "y": 220,
        "wires": []
    },
    {
        "id": "7",
        "type": "ui_group",
        "name": "RasPI-3B",
        "tab": "6",
        "order": 1,
        "disp": true,
        "width": 16,
        "collapse": false,
        "className": ""
    },
    {
        "id": "6",
        "type": "ui_tab",
        "name": "Home",
        "icon": "dashboard",
        "order": 1
    }
]

-----

And here is the Arduino Sketch for the ESP32C3:

// ESP32-C3 Dev Module + onboard OLED 
// thermometer w/ DS18B20 data pin connected to GPIO 4
//
// OLED: Fahrenheit only (1 decimal place, no units)
// Serial Monitor: Celsius + Fahrenheit
// Web page: latest calibrated Fahrenheit reading with timestamp
//
// https://www.whiskeytangohotel.com/
// SEPT 2025

#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <WiFi.h>
#include "time.h"
#include <WebServer.h>

// WiFi credentials
const char* ssid     = "ur-ssid";
const char* password = "ur-password";

// OLED setup
U8G2_SSD1306_72X40_ER_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

// Global counter
int readingCount = 0;

// DS18B20 setup
#define ONE_WIRE_BUS 4
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

// Timezone
const char* ntpServer = "pool.ntp.org";

// Latest reading
float latestTempF = 0;
time_t latestTime = 0;

// Web server
WebServer server(80);

void handleRoot() {
  char timeBuf[30];
  struct tm *tm_info = localtime(&latestTime);
  strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", tm_info);

  String html = "<html><head><title>ESP32-C3 Temp</title></head><body><pre>";
  html += timeBuf;
  html += " - Temperature is: ";
  html += String(latestTempF, 1);
  html += "</pre></body></html>";

  server.send(200, "text/html", html);
}

void setup() {
  // I2C pins for ESP32-C3 OLED dev board
  Wire.begin(5, 6);
  Wire.setClock(100000);
  delay(200);

  u8g2.begin();
  Serial.begin(115200);
  delay(200);

  sensors.begin();

  // Startup screen
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_6x10_tr);
  u8g2.drawStr(0, 15, "LAN IP is:");  // Could change this to a "Title Screen"
  u8g2.sendBuffer();
  delay(2000);

  // Connect to WiFi
  WiFi.begin(ssid, password);
  u8g2.clearBuffer();
  u8g2.drawStr(0, 15, "LAN IP is:");
  u8g2.sendBuffer();
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  // NTP
  configTzTime("CST6CDT,M3.2.0/2,M11.1.0/2", ntpServer);

  // Start server
  server.on("/", handleRoot);
  server.begin();
  Serial.print("HTTP server started at: ");
  Serial.println(WiFi.localIP());
  //Display last digits of IP address on OLED (.xxx) for easy ID
  String lastOctet = "." + WiFi.localIP().toString().substring(WiFi.localIP().toString().lastIndexOf('.')+1);
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_fur20_tf);
  int16_t x = (72 - u8g2.getStrWidth(lastOctet.c_str())) / 2;  // center horizontally
  u8g2.drawStr(x, 30, lastOctet.c_str());
  u8g2.sendBuffer();
  delay(5000);
}

void loop() {
  sensors.requestTemperatures();
  float tempC = sensors.getTempCByIndex(0);
  float tempF = tempC * 9.0 / 5.0 + 32.0;
  float calibrationOffsetF = 0.0;
  tempF += calibrationOffsetF;

  // Save latest reading
  time(&latestTime);
  latestTempF = tempF;

  // Serial output
  //readingCount++;  // If reading count is desired
  //Serial.print("#");
  //Serial.print(readingCount);

  char timeBuf[30];
  struct tm *tm_info = localtime(&latestTime);
  strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", tm_info);
  Serial.print(timeBuf);

  Serial.print(" > Temperature is: ");
  Serial.print(tempC);
  Serial.print("°C | ");
  Serial.print(tempF);
  Serial.println("°F");

  // OLED output
  char buf[10];
  snprintf(buf, sizeof(buf), "%.1f", tempF);
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_fur20_tf);
  u8g2.drawStr(0, 30, buf);
  u8g2.sendBuffer();

  server.handleClient(); // handle web requests

  delay(5000); // delay until next reading
}
-----


  

Wednesday, September 17, 2025

QRCode Clock with ESP32C3 Dev Module

-----
Nothing is more frustrating than needing the time and not having a watch, but watches can be expensive and boring so we programed this ~$2.00 USD ESP32C3 Dev Module with on-board OLED to provide the time in a low cost and interesting way.

 Oh, to make it work you also need a smart phone.... 

-----

After uploading the source code below you will get a QRCode on the OLED that conveniently provides a second by second account of the time which you can read from your smart phone camera.  Here's the demo:

 
 
This timekeeping device is cheap and extremely accurate.  
-----

// QRCode Clock
// QRCode on OLED is updated each second 
// time the time of day as HH:MM:SS in 24 hr format.
//
// Board (~$4) is ESP32C3 Dev Module with onboard OLED.
// 
// Details at: https://www.whiskeytangohotel.com/
// SEPT 2025


#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
#include <WiFi.h>
#include "time.h"
#include "QRCodeGenerator.h"

// WiFi credentials
const char* ssid     = "YOURSSID";
const char* password = "YOURWIFIPASSWORD";
const char* ntpServer = "pool.ntp.org";

// A few Google searches led me to this:
U8G2_SSD1306_72X40_ER_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

void setup() {
  Wire.begin(5, 6);         // I2C 
  Wire.setClock(100000);    // slow for stability
  delay(200);               // power-up delay
  u8g2.begin();

  // Connecting WiFi status screen
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_6x10_tr);
  u8g2.drawStr(0, 15, "Connecting...");
  u8g2.sendBuffer();

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  configTzTime("CST6CDT,M3.2.0/2,M11.1.0/2", ntpServer); //Central Time
}

void loop() {
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    delay(2000);
    return;
  }

  // Format time string (HH:MM:SS) 24 hour time
  char timeStr[16];
  strftime(timeStr, sizeof(timeStr), "%H:%M:%S", &timeinfo);

  // Generate QR code
  QRCode qrcode;
  const uint8_t qrVersion = 3;  // 29x29 QRCode
  uint8_t qrcodeData[qrcode_getBufferSize(qrVersion)];
  qrcode_initText(&qrcode, qrcodeData, qrVersion, 0, timeStr);

  // Scale/Center QRCode to fit 64x32 OLED but remember QRCodes are square
  int scale = 1;  // keep modules square
  int width = qrcode.size * scale;
  int height = qrcode.size * scale;
  int xOffset = (64 - width) / 2;
  int yOffset = (32 - height) / 2;

  // Draw QRCode to OLED
  u8g2.clearBuffer();
  for (uint8_t y = 0; y < qrcode.size; y++) {
    for (uint8_t x = 0; x < qrcode.size; x++) {
      if (qrcode_getModule(&qrcode, x, y)) {
        u8g2.drawBox(xOffset + x * scale, yOffset + y * scale, scale, scale);
      }
    }
  }
  u8g2.sendBuffer();

  delay(1000);  // refresh once per second
}

-----


 

Tuesday, July 16, 2013

Raspberry PI: Charting Ambient vs Outside Temperature

How to use a Raspberry PI to chart ambient temperature vs outside temperature.  Source code and schematics below.

What you need:
-----
What you get:

Reading the graph above is pretty obvious.  It plots the temperature of the DS18B20 sensor connected to the Raspberry PI vs. the outside temperature that is provided by a local weather forecast feed.  Just for fun, we also display Min and Max temperatures (which can be reset).
-----
The graphing is provided by sen.se.  The sen.se site offers a lot of flexibility with "the internet of things".  sen.se is free.  Sign up and scan the tutorials.  The site is well laid out and the tutorials are very straight forward; you'll be an expert in no time.  Basically, you want to create a "channel" for your Raspberry PI by 'adding a device'.  sen.se will give you a 5 digit channel number for your RasPI and a very long passphrase that will be your personal identifier.  You will need both of these for the source code below.
-----
Next, let's connect the DS18B20 to the Raspberry PI.  The DS18B20 transmits its temperature reading via I2C bus.  Just follow the tutorial at Adafruit.  The connection is simple and looks like this:
-----
Load the Python script below into your Raspberry Pi and run it.  Be certain you enter your personal passphrase identifier and the device channel code that you got earlier from sen.se.  After you run the Python script head back over to sen.se.  You should see that sen.se has detected a 'heartbeat' from your Raspberry PI.  After that, it is just a matter of configuring one of the graphing apps on sen.se.  You can make your sen.se data public or private and there are many many tools to manipulate and display your data.
-----
Good luck!  Python script for the RasPI follows:

# WhiskeyTangoHotel.Com
# June 2013
# Program reads DS18B20 temp sensor and plots value to sen.se
# DS18B20 connections via AdaFruit tutorial
# With thanks to @Rob_Bishop

# This program is feed customized for RasPI(2)

import httplib
import json as simplejson
from random import randint
import time
import os
import glob

# Pass os commands to set up I2C bus 
os.system('modprobe w1-gpio')  
os.system('modprobe w1-therm')

base_dir = '/sys/bus/w1/devices/'
device_folder = glob.glob(base_dir + '28*')[0]
device_file = device_folder + '/w1_slave'

run_number = 0

SENSE_API_KEY = "long sen.se passphase here. note that it is in quotes"
FEED_ID1 = 12345  # five digit sen.se channel code.  note it is NOT in quotes

def read_temp_raw():  #read the DS18B20 function
    f = open(device_file, 'r')
    lines = f.readlines()
    f.close()
    return lines

def read_temp(): #process the raw temp file output and convert to F
    lines = read_temp_raw()
    while lines[0].strip()[-3:] != 'YES':
        time.sleep(1)
        lines = read_temp_raw()
    equals_pos = lines[1].find('t=')
    if equals_pos != -1:
        temp_string = lines[1][equals_pos+2:]
        ambC = float(temp_string) / 1000.0
        ambF = ambC * 9.0 / 5.0 + 32.0
        return ambF

def send_to_opensense(data):
#    print  >> fout, "\t=> Sending to OpenSense: %s" % data
try:
# prepare data 
datalist = [{"feed_id" : FEED_ID1, "value" :data['F']},]
headers = {"sense_key": SENSE_API_KEY,"content-type": "application/json"}
conn = httplib.HTTPConnection("api.sen.se")
# format a POST request with JSON content
conn.request("POST", "/events/", simplejson.dumps(datalist), headers)
response = conn.getresponse()
# you may get interesting information here in case it fails
#   print >> fout, response.status, response.reason
#   print >> fout, response.read()
conn.close()
except:
pass

while(True):
try:
run_number = run_number + 1
ambF = read_temp()
print "RasPI(2) Ambient Run:", run_number, "    ambF:",ambF
data = { 'F' : ambF}
send_to_opensense(data)
time.sleep(300)
except:
pass
-----

Wednesday, May 29, 2013

PI in the Oven: Logging Raspberry PI Core Temperatures to Sen.se

Objective:  Create a method of logging Raspberry PI data to the sen.se website.  In this example I plot the core temperature of two Raspberry PIs, but the method can be adapted to log virtually any form of data that you wish to capture or generate with the PI.  The python code is below to get you running quickly.
-----

The graph above is generated by sending data from the PI to the sen.se API.  Sen.se is one cool place.  Their goal is to assist in internet connectivity of personal devices; "the internet of things".  They have widgets, tools, applications, channels and a few other things that I barely understand.  Play around with sen.se some and you will get the idea .  In my application, I have sen.se graphing the core temperature of two Raspberry PIs; a data point every 60 seconds.  Just for fun, I also keep track of the number of reading, calculate an average temperature, and display the temperature change since the last reading.  Sen.se allows you to keep this information private or display it to the public.  
----
So.... from the graph we see one PI is running about ~12F hotter than the other.  Why?  

Probably due to a few reasons:  
RasPI_1 is always running the "motion" webcam software and functioning as an OpenVPN server.  RasPI_1 is also in a fully enclosed case.  (Maybe I should take it out of that case....)

RasPI_2 is not in a case on is only running my Hand of PI project.  Hand of PI is a robot hand that you can control by sending twitter commands to it.  Click for build page.

Of course, both PIs are running the temperature logging script.
-----
If you are still with me, the python source code is below.  It has been running flawlessly for a while, so it should be solid.  Occasionally Sen.se will go down briefly for maintenance, but that is why I put in the error traps.  Good luck and tweet the Hand of PI to let us know you were here!!!
------
# whiskeytangohotel.com
# May 2013

# Python script to read RaspberryPI
# internal core temp, covert from C to F
# and log to sen.se for graphing.

# If you get errros on the import
# make certain you have the 'apts' installed

import httplib
import json as simplejson
from random import randint
import time

# init some vars
run_number = 0
tempC = 0
tempF = 0


# Enter your private sen.se API KEY in quotes.  Enter the Feed ID# without quotes
SENSE_API_KEY = "x1xxxxxy2yyyyyyz3zzzz"  
FEED_ID1 = 12345

# Function to format for sen.se
# The try/expect are there to trap errors if sen.se goes down
# or is slow.  This keeps the script running.
def send_to_opensense(data):
try:
# prepare data 
datalist = [{"feed_id" : FEED_ID1, "value" :data['F']},]
headers = {"sense_key": SENSE_API_KEY,"content-type": "application/json"}
conn = httplib.HTTPConnection("api.sen.se")
# format a POST request with JSON content
conn.request("POST", "/events/", simplejson.dumps(datalist), headers)
response = conn.getresponse()
conn.close()
except:
pass   

while(True):

# The try/expect are there to trap errors if sen.se goes down
# or is slow.  This keeps the script running
try:
# read the PI core temperture and store in tempC
# then convert from C to F and send the data to sen,se
tempC = int(open('/sys/class/thermal/thermal_zone0/temp').read()) / 1e3
tempF = (tempC * 1.8) + 32
run_number = run_number + 1
print "Run:", run_number, "    tempC:", tempC, "    tempF:",tempF
data = { 'F' : tempF}
send_to_opensense(data)
time.sleep(60)
except:
pass
-----

Saturday, June 23, 2012

"Etch a Sketch" Turned Temperature Data Logger

A friend of mine gave me an OKI office printer.  The thing was HUGE and after about five years it finally broke.  Next step, out comes the screwdriver to rescue any useful parts; of which where several stepper motors.  Since what I didn't know about stepper motors was a lot I searched for a "useful" way to learn about them.  I decided to connect two of the stepper motors to an "Etch a Sketch" and ended up with this rig that graphically logs temperature in a strip chart fashion.


Here is a video if you are not interested in the build details and just the want to see the result.  The beer was cold.  The water in the shot glass hot.  Hot makes the graph go up.  Cold makes the graph go down.  When the graph reaches the far right of the "Etch a Sketch" the stylus moves full left and the process repeats.  In the video a temperature measurement is taken (and graphed) every 750 milliseconds, but that can be adjusted to anything; one reading per hour for example. 


-----
The process of driving the steppers was not trivial in the beginning.  First, I had no documentation on these steppers.  The second being I had no idea how "noisy" and power hungry the steppers could be.    The documentation turned out not to be a big deal.  Via the magic of the internet I learned they were of 4-wire, bi-polar configuration.  An ohm meter is all that is needed to figure out the connection scheme. 

----
Close up of one of the OKI printer 4-wire bipolar stepper motors:

-----
Driving a stepper motor requires a microcontroller.  My choice for a microcontroller was the PICAXE 18M2.  
-----
A stepper motor is not like a 'common' DC motor.  You cannot just apply a current and have the stepper spin.  The current has to be applied in sequence across the four available wires.  You also have to control the polarity (direction) of the current.  That said, steppers motors take way more current than a microcontroller can provide.  An H-Bridge motor driver solves the problem by providing more available drive current for the stepper and the ability to switch current drive polarity.

I was familiar with the SN754410NE H-Bridge motor driver.  Plus, I had some in my kit.  So, originally I decided to use one SN754410NE to drive each stepper motor.  This was a mistake that added much frustration.  The stepper motors are incredibly noisy and current hungry.  The noise caused by the steppers and energy from their back EMF (I think) caused nothing to work reliably.  After seeing on a datasheet that L293D motor drivers have protection diodes and some other features, I gave them a try instead.  The L293D is pin compatible with the SN754420NE so the swap was easy.  After inserting the L293D's everything started moving forward as planned with controlling the steppers.


-----
Now that we can control the stepper motors via the PICAXE 18M2 and L293D's we still need to interface then some way to the "Etch a Sketch".  I had a clear plastic cube thats purpose was to protect a trophy baseball.  Since I didn't have a trophy baseball I dismantled the cube and used the two "C" shaped pieces to mount the steppers to with the help of a Dremmel tool and double sided sticky tape.
-----
After mounting the steppers motors, you still have to mechanically couple them to the "Etch a Sketch".  Rubber hose and zip tie wraps worked perfectly.
-----
After getting the mechanicals figured out a "test" pattern was programmed into the PICAXE.  The test worked on the first run so we grabbed the video camera to document the success.


-----
Now that the stepper motors make the "Etch a Sketch" draw, we still need a way to measure temperature.  The PICAXE 18M2 is used to read a DS18B20 sensor (picture below) via I2C bus for this:

-----
To manually position the "Etch a Sketch" stylus, two buttons are wired into the PICAXE 18M2:
-----
After all of that it is just code and software debugging. 

I have other plans for the rig.  Stay tuned!
-----
If you are still with me, here is the build schematic (click to enlarge):
-----

Friday, March 23, 2012

Voltmeter Clock w/ F°, C°, and K° temperature readout.

-----
The objective was to create a real time clock using three analog voltmeters controlled via Pulse Width Modulation (PWM) to display "hours", "minutes", and "seconds".  At the press of a button the three meters display temperature in degrees F, C, and K.
-----
Here is a short (time lapse) video of the rig in action:


-----
There are four major components to the build.

  • PICAXE 14M2 microcontroller
  • DS1307 Real Time Clock (RTC) module
  • DS18B20 Temperature Sensor
  • Three 0-2VDC analog voltmeters (refaced to display as "hours", "minutes", and "seconds")
-----
The meters were taken apart to install custom faces.  A free program called "MeterBasic" was used to create the custom faces for the three voltmeters (see pic below).  Note that the meters have labeling to display temperature in degrees F, C, and K.
-----
See the black button below the "seconds" meter?  Pressing this button causes the meters to display their respective temperature.  
-----
Here are a few shots of the build process.  The "red thing with the black tip" is the DS18B20 temperature sensor.  In front of the meter in the middle (what will be the minutes meter), you can see a small PCB with two buttons.  These are used to set the time on the DS1307 Real Time Clock.  Pressing both buttons will set the seconds to zero.
------
The digital outputs on the PICAXE 14M2 put out about 5VDC maximum.  Our analog voltmeters only go to 2VDC maximum.  We set up a voltage divider circuit and a trimpot to allow the output from the PICAXE 14M2 to read full on our 2VDC meter.  The 100K trimpot (the little blue things in the pic below) allow for precise adjustment of the full scale reading.  Pushing the hour set, minute set, and read temperature button at the same time will force all meters to full scale.  This is to provide for precise tuning of the trimpots to calibrate the full scale reading of each meter.

After we have calibrated the meters to read "0" with no voltage and "full scale" from a high digital output on the PICAXE 14M2 we still have to be able to control the meters to display the time.  This is done by reading the time on DC1307 RTC via the I2C bus and the PICAXE 14M2.  Pulse Width Modulation control is used to convert the time (hours, minutes, and seconds) into a corresponding "average" voltage.  That PWM signal drives the three meters to display time.  The PICAXE 14M2 has four PWM output drivers that are well up to the job for this.

-----
Here is another look at the time set buttons.  They are located on the bottom side of the clock enclosure.
-----
The rig is powered by a rescued wall wart from an old Sony CD player.  The wall wart puts out 9VDC which is tamed to 5VDC with a LM7805 voltage regulator.
-----
If you want to build your own, the schematic looks like this.  The source code is below.
-----
Another pic of the finished rig.




















-----
If you're still with us, here the PICAXE code:
; *******************************
; ***** www.whiskeytangohotel.com *****
; *******************************
;    Project Name: 3 Panel Meter Clock
; REV: FIN (everything works fine with clock and temp)
;
;    Start Date:  Feb 12, 2012
;    
;    Program Rev History/Ideas:
; - to set clock push M or H button
; - push main button to display temp F, C, and K
; - Routine to drive 3 meters to full scale when main button AND (M OR H)
; is pressed.  This is to allow adjustment of trimmers to full scale.
; Adjustment to zero scale should be done first with power off.
;
; ******************************* 
;    PICAXE PE Rev: MacAXEPad 1.3.2
;
#picaxe14m2

'define memory locations as symbols
symbol hour_meter = c.0 'LEG 7
symbol minute_meter = c.2 'LEG 5
symbol second_meter = b.2 ' LEG 11
symbol temp_switch = pinc.4 ' LEG3
symbol temp_sensor = b.5 'LEG8
symbol hour_set = pinc.3 'LEG3
symbol minute_set = pinc.1 'LEG6

symbol CthermoValue = b10
symbol FthermoValue = b11
symbol KthermoValue = w6 'b12 and b13
symbol seconds = b0
symbol minutes = b1 
symbol hours = b2
symbol day = b3
symbol date = b4
symbol month = b5
symbol year = b6
symbol blinky = b7
symbol Tens_Digi = b8   ;Used to get the Tens Digit from the $HEX RTC value
symbol Ones_Digi = b9 ;Used to get the Ones Digit from the $HEX RTC value
' w7 (b14 and b15) is used to control the PCM to the meters 

' Set the time on the DS1307 RTC
i2cslave %11010000, i2cslow, i2cbyte ; set PICAXE as master and DS1307 slave address
pause 50 

'Set all meters to full scale with 100% duty cycle
pwmout hour_meter,99,400        
pwmout minute_meter,99,400    
pwmout second_meter,99,400   


'\/ \/ \/ \/ Un_REM THESE LINES (BELOW) IF SETTING UP A NEW RTC  \/ \/ \/ \/
#rem
' Set the RTC chip time
;  write time and date e.g. to 11:59:00 on Thurs 25/12/03
'; would be "writei2c 0,($00, $59, $11, $03, $25, $12, $03, 010000)"
' readi2c 0, (b0,b1,b2,b3,b4,b5,b6,b7) reads back the data
let seconds = $00 ; 00-59 Note all BCD format
let minutes = $00     ; 00-59 Note all BCD format   
let hours = $01 ; 01-12 Note all BCD format
let day = $03     ; program does not use date, date, month, year
let date = $22      
let month = $03  
let year = $12      
let blinky = 000000 ; 010000 would Enable output at 1Hz blink rate.  000000 is no blink

writei2c 0,(seconds, minutes, hours, day, date, month, year, blinky)
pause 50 
#endrem
'/\ /\ /\ /\ Un_REM THESE LINES (ABOVE) IF SETTING UP A NEW RTC  /\ /\ /\ /\


; POWER ON SELF TEST.  Send all three meters to FULL Scale and back to 0
'NOTE:  pwmduty hour_meter, w7  ---->> w7 ranges from 0 to 400 to move meter from 0VDC to 2VDC
for w7 = 400 to 0 step -1' pwmduty needs a (W)ord var, not a (B)yte var
pwmduty hour_meter, w7 'adjust PWM Duty from 100% to 0% 
pwmduty minute_meter, w7
pwmduty second_meter, w7
next w7


main:

if temp_switch = 1 then
do ' while hour_set = 1 or minute_set = 1 then 'if main buttom and H or M set button pushed then
pwmduty hour_meter, 400 ' move all meters to full scale to allow trimmer adjust.  Do 0 meter adjust  1st with power off.
pwmduty minute_meter, 400
pwmduty second_meter, 400
loop while hour_set = 1 or minute_set = 1
end if ' temp_switch = 1

if temp_switch = 1 and hour_set = 0 and minute_set = 0 then
do 'while temp_switch is pressed
gosub DisplayTemp
loop while temp_switch = 1 and hour_set = 0 and minute_set = 0
end if

'read the RTC and display the time
readi2c 0, (seconds, minutes, hours, day, date, month, year, blinky)
pause 10
'\/ \/ \/ \/ \/ SECONDS SECONDS SECONDS \/ \/ \/ \/ \/
;Convert the Seconds from the RTC to Base10 and update the meter
if temp_switch = 0 and minute_set = 1 and hour_set = 1 then 'all pressed.  Seconds to $00
gosub Seconds_set
end if 

Ones_digi = seconds & 0x0F   ' zero out the top four bits

Tens_Digi = seconds & 0xF0   ' zero out the lower four bits
Tens_Digi = Tens_Digi / 2 ' each divide by 2 shifts the 
Tens_Digi = Tens_Digi / 2 ' bits LEFT.  Four shift get
Tens_Digi = Tens_Digi / 2 ' them all to the lower four bits
Tens_Digi = Tens_Digi / 2
Tens_Digi = Tens_Digi * 10 'Now shift the Base10 value to Tens place

seconds = Tens_Digi + Ones_Digi 'complete. Now $ss is ss (in Base10)
' Update the seconds meter with a w9 value of 0 to 400
w7 =  seconds * 677 / 100  ; (400/59=6.77) scale 0-59 seconds to 0-400 for PWM
pwmduty second_meter, w7
'/\ /\ /\ /\ SECONDS END /\ /\ /\ /\ /\ /\ /\

'\/ \/ \/ \/ MINUTES MINUTES MINUTES \/ \/ \/ \/ \/
if temp_switch = 0 and minute_set = 1 and hour_set = 0 then 'if temp_switch and M is pressed then add on minute
gosub Minutes_set
end if 
;Convert the Minutes from the RTC to Base10 and update the meter
Ones_digi = minutes & 0x0F   ' zero out the top four bits

Tens_digi = minutes & 0xF0   ' zero out the lower four bits
Tens_Digi = Tens_Digi / 2 ' each divide by 2 shifts the 
Tens_Digi = Tens_Digi / 2 ' bits LEFT.  Four shift get
Tens_Digi = Tens_Digi / 2 ' them all to the lower four bits
Tens_Digi = Tens_Digi / 2
Tens_Digi = Tens_Digi * 10 'Now shift the Base10 value to Tens place

minutes = Tens_Digi + Ones_Digi 'complete. Now $mm is mm (in Base10)
'Update the minutes meter with a w9 value of 0 to 400
If minute_set = 0 then 'dont update if we are setting the minutes.  Should correct this in Minutes_set routine
w7 = minutes * 677 / 100 ; (400/59=6.77) scale 0-59 minutes to 0-400 for PWM
pwmduty minute_meter, w7
end if 
'/\ /\ /\ /\ /\ MINUTES END /\ /\ /\ /\ /\

'\/ \/ \/ \/ \/ HOURS HOURS HOURS \/ \/ \/ \/
if temp_switch = 0 and minute_set = 0 and hour_set = 1 then 'if temp_switch and M is pressed then add on minute
gosub Hours_set
end if 

;Convert the Hours from the RTC to Base10 and update the meter
Ones_digi = hours & 0x0F   ' zero out the top four bits

Tens_digi = hours & 0xF0   ' zero out the lower four bits
Tens_Digi = Tens_Digi / 2 ' each divide by 2 shifts the 
Tens_Digi = Tens_Digi / 2 ' bits LEFT.  Four shift get
Tens_Digi = Tens_Digi / 2 ' them all to the lower four bits
Tens_Digi = Tens_Digi / 2
Tens_Digi = Tens_Digi * 10 'Now shift the Base10 value to Tens place

hours = Tens_Digi + Ones_Digi 'complete. Now $hh is hh (in Base10)

if hours > 12 then
hours = hours - 12
end if
if hours = 0 then 
hours = 12
end if

' Update the hours meter with a w9 value of 0 to 400
w7 = hours * 3636  ; (400/11 = 36.36)  scale 1 to 12 hours to 0-400 for PWM
w7 = w7 - 3636 'subtract  y interceot to start PCM at 0 to 1 o'clock.
w7 = w7 / 100 'scale 1 to 12 hours to 0-400 for PWM
pwmduty hour_meter, w7
'/\ /\ /\ /\ /\ HOURS END /\ /\ /\ /\ /\
goto main

DisplayTemp: 
readtemp temp_sensor, CthermoValue
w7 = CthermoValue * 677 / 100 '(400/59=6.77) scale C to 0-400 for PWM
pwmduty minute_meter, w7 'C (raw) in the minutes meter)

'Convert C to F
FthermoValue = 9 * CthermoValue / 5 + 32
w7 = FthermoValue * 364  ; (400/11 = 36.36)  scale 10 to 120F  to 0-400 for PWM
w7 = w7 - 3636 'subtract  y interceot to start PCM at 0 to 10F
w7 = w7 / 100 'scale 0-400 for PWM
pwmduty hour_meter, w7 '(F (x10) on the hours meter


'Convert C to K
KthermoValue = CthermoValue + 273
w7 = KthermoValue / 10
w7 = w7 * 677 / 100 
pwmduty second_meter, w7 'Kelvin *100 on the seconds meter

return 'DisplayTemp

Seconds_Set:
seconds = $00
writei2c 0,(seconds, minutes, hours, day, date, month, year, blinky)
pause 50 
w7 =  seconds * 677 / 100  ; (400/59=6.77) scale 0-59 seconds to 0-400 for PWM
pwmduty second_meter, w7
return 'Seconds_set

Minutes_Set:
readi2c 0, (seconds, minutes, hours, day, date, month, year, blinky)
;Convert the Minutes from the RTC to Base10 and update the meter
Ones_digi = minutes & 0x0F   ' zero out the top four bits
Tens_digi = minutes & 0xF0   ' zero out the lower four bits
Tens_Digi = Tens_Digi / 2 ' each divide by 2 shifts the 
Tens_Digi = Tens_Digi / 2 ' bits LEFT.  Four shift get
Tens_Digi = Tens_Digi / 2 ' them all to the lower four bits
Tens_Digi = Tens_Digi / 2
Tens_Digi = Tens_Digi * 10 'Now shift the Base10 value to Tens place
minutes = Tens_Digi + Ones_Digi 'complete. Now $mm is mm (in Base10)
minutes = minutes + 1
if minutes > 59 then
minutes = 0
end if
'convert base10 minutes to BCD here then write new minute value to RTC
Ones_Digi = minutes // 10
Tens_Digi = minutes - Ones_Digi
Tens_Digi = Tens_Digi / 10
Tens_Digi = Tens_Digi * 2  ' shift the lower right bits
Tens_Digi = Tens_Digi * 2  ' to the upper left.
Tens_Digi = Tens_Digi * 2
Tens_Digi = Tens_Digi * 2
Tens_Digi = Tens_Digi OR Ones_Digi 'OR function on the two values to get 8 bit BDC
'Now Ten_digi = minutes in packed BCD.  Just using Tens_Digi as a temp var
writei2c 0,(seconds, Tens_Digi, hours, day, date, month, year, blinky)
pause 50 
'Update the minutes meter with a w9 value of 0 to 400
w7 = minutes * 677 / 100 ; (400/59=6.77) scale 0-59 minutes to 0-400 for PWM
pwmduty minute_meter, w7
pause 500
return ' Minutes_Set

Hours_Set: 'HOURS SET WORKS PERFECT
readi2c 0, (seconds, minutes, hours, day, date, month, year, blinky)
;Convert the Hours from the RTC to Base10 and update the meter
Ones_digi = hours & 0x0F   ' zero out the top four bits
Tens_digi = hours & 0xF0   ' zero out the lower four bits
Tens_Digi = Tens_Digi / 2 ' each divide by 2 shifts the 
Tens_Digi = Tens_Digi / 2 ' bits LEFT.  Four shift get
Tens_Digi = Tens_Digi / 2 ' them all to the lower four bits
Tens_Digi = Tens_Digi / 2
Tens_Digi = Tens_Digi * 10 'Now shift the Base10 value to Tens place
hours = Tens_Digi + Ones_Digi 'complete. Now $hh is hh (in Base10)
hours = hours + 1
if hours > 12 then
hours = hours - 12
end if
if hours = 0 then 
hours = 12
end if

if hours > 9 then
if hours = 10 then 
hours = $10
end if
if hours = 11 then
hours = $11
end if
if hours = 12 then
hours = $12
end if
end if
writei2c 0,(seconds, minutes, hours, day, date, month, year, blinky)
pause 50 
;Convert the Hours from the RTC to Base10 and update the meter
Ones_digi = hours & 0x0F   ' zero out the top four bits
Tens_digi = hours & 0xF0   ' zero out the lower four bits
Tens_Digi = Tens_Digi / 2 ' each divide by 2 shifts the 
Tens_Digi = Tens_Digi / 2 ' bits LEFT.  Four shift get
Tens_Digi = Tens_Digi / 2 ' them all to the lower four bits
Tens_Digi = Tens_Digi / 2
Tens_Digi = Tens_Digi * 10 'Now shift the Base10 value to Tens place
hours = Tens_Digi + Ones_Digi 'complete. Now $hh is hh (in Base10)
' Update the hours meter with a w9 value of 0 to 400
w7 = hours * 3636  ; (400/11 = 36.36)  scale 1 to 12 hours to 0-400 for PWM
w7 = w7 - 3636 'subtract  y interceot to start PCM at 0 to 1 o'clock.
w7 = w7 / 100 'scale 1 to 12 hours to 0-400 for PWM
pwmduty hour_meter, w7
pause 1000

return ' Hours_Set
-----