diff --git a/README.md b/README.md
index 2fdab9e..a2522d4 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
-superclock
-==========
+SuperClock 3150
+===============
+
+Arduino firmware and Android application for the limited edition SuperClock 3150.
-Firmware and Android app for the SuperClock 3150
diff --git a/android/SuperClock3150/.gitignore b/android/SuperClock3150/.gitignore
new file mode 100644
index 0000000..98e570f
--- /dev/null
+++ b/android/SuperClock3150/.gitignore
@@ -0,0 +1,7 @@
+.gradle
+/local.properties
+/.idea
+.DS_Store
+/build
+*.iml
+
diff --git a/android/SuperClock3150/app/.gitignore b/android/SuperClock3150/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/android/SuperClock3150/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/android/SuperClock3150/app/build.gradle b/android/SuperClock3150/app/build.gradle
new file mode 100644
index 0000000..bab935b
--- /dev/null
+++ b/android/SuperClock3150/app/build.gradle
@@ -0,0 +1,25 @@
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion '19.1.0'
+ defaultConfig {
+ applicationId 'jeff.com.superclock3150'
+ minSdkVersion 19
+ targetSdkVersion 19
+ versionCode 1
+ versionName '1.0'
+ }
+ buildTypes {
+ release {
+ runProguard false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+ productFlavors {
+ }
+}
+
+dependencies {
+ compile fileTree(include: ['*.jar'], dir: 'libs')
+}
diff --git a/android/SuperClock3150/app/proguard-rules.pro b/android/SuperClock3150/app/proguard-rules.pro
new file mode 100644
index 0000000..8c7002e
--- /dev/null
+++ b/android/SuperClock3150/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /home/jeff/dev/android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/android/SuperClock3150/app/src/main/AndroidManifest.xml b/android/SuperClock3150/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b9abb7f
--- /dev/null
+++ b/android/SuperClock3150/app/src/main/AndroidManifest.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/SuperClock3150/app/src/main/java/jeff/com/superclock3150/ClockActivity.java b/android/SuperClock3150/app/src/main/java/jeff/com/superclock3150/ClockActivity.java
new file mode 100644
index 0000000..35fd1a7
--- /dev/null
+++ b/android/SuperClock3150/app/src/main/java/jeff/com/superclock3150/ClockActivity.java
@@ -0,0 +1,381 @@
+package jeff.com.superclock3150;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Set;
+import java.util.UUID;
+
+
+public class ClockActivity extends Activity {
+
+ // this is the UUID for Linvor Bluetooth adapter
+ private static final UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
+ public static final String BLUETOOTH_DEVICE_NAME = "linvor";
+
+ private BluetoothAdapter bluetooth;
+ private BluetoothDevice device;
+ private OutputStream out;
+
+ private SimpleDateFormat displayDateFormat = new SimpleDateFormat("d-MMM-yyyy, HH:mm:ss");
+ private SimpleDateFormat clockDateFormat = new SimpleDateFormat("d/M/yyyy HH:mm:ss");
+ private Long timeDiff = null;
+
+ private TextView txtStatus;
+ private TextView txtLocalTime;
+ private TextView txtRemoteTime;
+ private TextView txtTemperature;
+ private TextView txtHumidity;
+ private TextView txtLastSync;
+ private TextView txtTimeDiff;
+ private TextView txtFirmware;
+ private TextView txtUptime;
+ private Button btnStatus;
+ private Button btnSync;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+
+ Log.i("superclock", "Starting up SuperClock App");
+
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.clock_view);
+
+ txtStatus = (TextView) findViewById(R.id.txtStatus);
+ txtLocalTime = (TextView) findViewById(R.id.txtLocalTime);
+ txtRemoteTime = (TextView) findViewById(R.id.txtRemoteTime);
+ txtTemperature = (TextView) findViewById(R.id.txtTemperature);
+ txtHumidity = (TextView) findViewById(R.id.txtHumidity);
+ txtFirmware = (TextView) findViewById(R.id.txtFirmware);
+ txtLastSync = (TextView) findViewById(R.id.txtLastSync);
+ txtTimeDiff = (TextView) findViewById(R.id.txtTimeDiff);
+ txtUptime = (TextView) findViewById(R.id.txtUptime);
+
+ Button btnConnect = (Button) findViewById(R.id.btnConnect);
+ btnConnect.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ doConnect();
+ }
+ });
+
+ btnStatus = (Button) findViewById(R.id.btnStatus);
+ btnStatus.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ doStatus();
+ }
+ });
+
+ btnSync = (Button) findViewById(R.id.btnSync);
+ btnSync.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ doSync();
+ }
+ });
+
+ new Thread() {
+ public void run() {
+ while (true) {
+ updateText(txtLocalTime, displayDateFormat.format(new Date()));
+
+ if (timeDiff != null) {
+ updateText(txtRemoteTime, displayDateFormat.format(new Date(System.currentTimeMillis() + timeDiff)));
+ }
+
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }.start();
+ }
+
+ /**
+ * Convenience method to set the value of a text field. Can be called from any thread,
+ * will be executed on the UI thread.
+ */
+ private void updateText(final TextView txt, final String value) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ txt.setText(value);
+ }
+ });
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.clock, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ if (id == R.id.action_settings) {
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**
+ * Called once the phone has successfully connected to the clock
+ */
+ private void doConnected() {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ updateText(txtStatus, "Connected");
+ btnStatus.setEnabled(true);
+ btnSync.setEnabled(true);
+ }
+ });
+ }
+
+ /**
+ * Synchronise the clock time with the phone time
+ */
+ private void doSync() {
+ String cmd = String.format("s=%s", clockDateFormat.format(new Date()));
+ sendCommand(cmd);
+ }
+
+ /**
+ * Execute a set of commands to get the full status of the clock. Command responses
+ * are handled asynchronously in a different thread
+ */
+ private void doStatus() {
+ sendCommand("f"); // firmware version
+ sendCommand("d"); // current date/time
+ sendCommand("t"); // temperature
+ sendCommand("h"); // humidity
+ sendCommand("u"); // uptime
+ sendCommand("l"); // last sync time
+ }
+
+ /**
+ * Connect to the device and set up the input/output streams. The device should already
+ * be paired.
+ */
+ private void doConnect() {
+
+ Log.d("superclock", "btnConnect hit");
+
+ updateText(txtStatus, "Getting Bluetooth adapter");
+ bluetooth = BluetoothAdapter.getDefaultAdapter();
+
+ updateText(txtStatus, "Turning on Bluetooth");
+ Intent turnOn = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+ startActivityForResult(turnOn, 0);
+ updateText(txtStatus, "Bluetooth on");
+
+ // search for the clock among the paired devices, just using a hard-coded device name for convenience
+ Set pairedDevices = bluetooth.getBondedDevices();
+ for (BluetoothDevice device : pairedDevices) {
+ if (device.getName().equals(BLUETOOTH_DEVICE_NAME)) {
+ this.device = device;
+ }
+ }
+
+ if (device != null) {
+ updateText(txtStatus, "Found SuperClock");
+ new ConnectThread(device).start();
+ } else {
+ updateText(txtStatus, "SuperClock not found");
+ }
+ }
+
+ /**
+ * Handle responses from the clock
+ */
+ class CommandHandler {
+ public void command(final String cmd, final String data) {
+ switch (cmd.charAt(0)) {
+ case 'd':
+ Date clockTime = parseClockTime(data);
+ timeDiff = clockTime.getTime() - System.currentTimeMillis();
+ String direction = (timeDiff > 0) ? "fast" : "behind";
+
+ updateText(txtRemoteTime, displayDateFormat.format(clockTime));
+ updateText(txtTimeDiff, niceTimePeriodFormat(Math.abs(timeDiff)) + " " + direction);
+ break;
+ case 'u':
+ updateText(txtUptime, niceTimePeriodFormat(Long.parseLong(data)));
+ break;
+ case 't':
+ updateText(txtTemperature, data + (char) 0x00B0);
+ break;
+ case 'h':
+ updateText(txtHumidity, data + "%");
+ break;
+ case 'f':
+ updateText(txtFirmware, data);
+ break;
+ case 'l':
+ long ms = Long.parseLong(data);
+ updateText(txtLastSync, String.format("%s ago", niceTimePeriodFormat(ms)));
+ break;
+ case 's':
+ updateText(txtLastSync, "Synchronised!");
+ break;
+ }
+ }
+
+ private Date parseClockTime(String str) {
+ str = str.replace('T', ' ');
+ try {
+ return clockDateFormat.parse(str);
+ } catch (ParseException e) {
+ Log.e("superclock", "error parsing date/time", e);
+ }
+ return null;
+ }
+
+ private String niceTimePeriodFormat(long ms) {
+ if (ms < 100 * 1000) {
+ return String.format("%.1f secs", ms / 1000.0);
+ } else if (ms < 60 * 60 * 1000) {
+ return String.format("%.1f mins", ms / 60.0 / 1000.0);
+ } else if (ms < 24 * 60 * 60 * 1000) {
+ return String.format("%.1f hrs", ms / 60.0 / 60.0 / 1000.0);
+ } else {
+ return String.format("%.1f days", ms / 24.0 / 60.0 / 60.0 / 1000.0);
+ }
+ }
+ }
+
+ /**
+ * Buffers incoming text, triggers a command when a new line char is received
+ */
+ class BufferedCommandInput {
+
+ private StringBuffer input;
+ private CommandHandler handler;
+
+ private BufferedCommandInput(CommandHandler handler) {
+ this.handler = handler;
+ clearBuffer();
+ }
+
+ public void readByte(int b) {
+ if (b == 10) {
+ // ignore
+ } else if (b == 13) {
+ String command = String.valueOf(input.charAt(0));
+ String data = input.substring(2);
+ Log.i("superclock.bluetooth", " Received command=" + command + ", data=" + data);
+ handler.command(command, data);
+ clearBuffer();
+ } else {
+ input.append((char) b);
+ }
+ }
+
+ private void clearBuffer() {
+ input = new StringBuffer();
+ }
+ }
+
+ private void manageConnectedSocket(BluetoothSocket socket) {
+ InputStream in;
+ BufferedCommandInput handler = new BufferedCommandInput(new CommandHandler());
+
+ try {
+ in = socket.getInputStream();
+ out = socket.getOutputStream();
+
+ Log.d("superclock.bluetooth", "Starting Bluetooth listener");
+
+ while (true) {
+ while (in.available() > 0) {
+ int ch = in.read();
+ Log.v("superclock.bluetooth", "RX=" + (char) ch + " (" + ch + ")");
+ handler.readByte(ch);
+ }
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ }
+ }
+ } catch (IOException e) {
+ Log.e("superclock.bluetooth", "error with sockets", e);
+ }
+ }
+
+ private void sendCommand(String command) {
+ if (out != null) {
+ try {
+ out.write((command + "\r\n").getBytes());
+ } catch (IOException e) {
+ Log.e("superclock.bluetooth", "error while sending command", e);
+ }
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ private class ConnectThread extends Thread {
+ private final BluetoothSocket mmSocket;
+
+ public ConnectThread(BluetoothDevice device) {
+ BluetoothSocket tmp = null;
+
+ try {
+ tmp = device.createRfcommSocketToServiceRecord(uuid);
+ } catch (IOException e) {
+ Log.e("superclock.bluetooth", "error opening Bluetooth socket", e);
+ }
+
+ mmSocket = tmp;
+ }
+
+ public void run() {
+ bluetooth.cancelDiscovery();
+
+ try {
+ updateText(txtStatus, "Opening Bluetooth socket");
+ mmSocket.connect();
+ doConnected();
+ } catch (IOException connectException) {
+ Log.e("superclock.bluetooth", "error connecting to Bluetooth socket", connectException);
+
+ // Unable to connect; close the socket and get out
+ try {
+ mmSocket.close();
+ } catch (IOException closeException) {
+ Log.e("superclock.bluetooth", "error closing Bluetooth socket", closeException);
+ }
+ return;
+ }
+
+ manageConnectedSocket(mmSocket);
+ }
+
+ public void cancel() {
+ try {
+ mmSocket.close();
+ } catch (IOException e) {
+ Log.e("superclock.bluetooth", "error closing Bluetooth socket", e);
+ }
+ }
+ }
+}
diff --git a/android/SuperClock3150/app/src/main/res/drawable-hdpi/ic_launcher.png b/android/SuperClock3150/app/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
Binary files /dev/null and b/android/SuperClock3150/app/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/android/SuperClock3150/app/src/main/res/drawable-mdpi/ic_launcher.png b/android/SuperClock3150/app/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
Binary files /dev/null and b/android/SuperClock3150/app/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/android/SuperClock3150/app/src/main/res/drawable-xhdpi/ic_launcher.png b/android/SuperClock3150/app/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
Binary files /dev/null and b/android/SuperClock3150/app/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/android/SuperClock3150/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/android/SuperClock3150/app/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4df1894
Binary files /dev/null and b/android/SuperClock3150/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/android/SuperClock3150/app/src/main/res/layout/clock_view.xml b/android/SuperClock3150/app/src/main/res/layout/clock_view.xml
new file mode 100644
index 0000000..56ca298
--- /dev/null
+++ b/android/SuperClock3150/app/src/main/res/layout/clock_view.xml
@@ -0,0 +1,321 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/SuperClock3150/app/src/main/res/menu/clock.xml b/android/SuperClock3150/app/src/main/res/menu/clock.xml
new file mode 100644
index 0000000..2e5ba54
--- /dev/null
+++ b/android/SuperClock3150/app/src/main/res/menu/clock.xml
@@ -0,0 +1,8 @@
+
diff --git a/android/SuperClock3150/app/src/main/res/values-w820dp/dimens.xml b/android/SuperClock3150/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/android/SuperClock3150/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/android/SuperClock3150/app/src/main/res/values/dimens.xml b/android/SuperClock3150/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/android/SuperClock3150/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/android/SuperClock3150/app/src/main/res/values/strings.xml b/android/SuperClock3150/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..2b64e96
--- /dev/null
+++ b/android/SuperClock3150/app/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+
+
+
+ SuperClock 3150
+ Settings
+
+
diff --git a/android/SuperClock3150/app/src/main/res/values/styles.xml b/android/SuperClock3150/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..ff6c9d2
--- /dev/null
+++ b/android/SuperClock3150/app/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/android/SuperClock3150/build.gradle b/android/SuperClock3150/build.gradle
new file mode 100644
index 0000000..aadd3db
--- /dev/null
+++ b/android/SuperClock3150/build.gradle
@@ -0,0 +1,19 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.11.+'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ mavenCentral()
+ }
+}
diff --git a/android/SuperClock3150/gradle.properties b/android/SuperClock3150/gradle.properties
new file mode 100644
index 0000000..5d08ba7
--- /dev/null
+++ b/android/SuperClock3150/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Settings specified in this file will override any Gradle settings
+# configured through the IDE.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/android/SuperClock3150/gradle/wrapper/gradle-wrapper.jar b/android/SuperClock3150/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
Binary files /dev/null and b/android/SuperClock3150/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/android/SuperClock3150/gradle/wrapper/gradle-wrapper.properties b/android/SuperClock3150/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..5de946b
--- /dev/null
+++ b/android/SuperClock3150/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-all.zip
diff --git a/android/SuperClock3150/gradlew b/android/SuperClock3150/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/android/SuperClock3150/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/android/SuperClock3150/gradlew.bat b/android/SuperClock3150/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/android/SuperClock3150/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/android/SuperClock3150/settings.gradle b/android/SuperClock3150/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/android/SuperClock3150/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/arduino/doc/Bluetooth.pdf b/arduino/doc/Bluetooth.pdf
new file mode 100644
index 0000000..61159cd
Binary files /dev/null and b/arduino/doc/Bluetooth.pdf differ
diff --git a/arduino/doc/DHT11.pdf b/arduino/doc/DHT11.pdf
new file mode 100644
index 0000000..0d4b24d
Binary files /dev/null and b/arduino/doc/DHT11.pdf differ
diff --git a/arduino/doc/JY-MCU-Bluetooth-UART-R1-0.pdf b/arduino/doc/JY-MCU-Bluetooth-UART-R1-0.pdf
new file mode 100644
index 0000000..b81241c
Binary files /dev/null and b/arduino/doc/JY-MCU-Bluetooth-UART-R1-0.pdf differ
diff --git a/arduino/doc/LTC-637D1P.pdf b/arduino/doc/LTC-637D1P.pdf
new file mode 100644
index 0000000..eae2817
Binary files /dev/null and b/arduino/doc/LTC-637D1P.pdf differ
diff --git a/arduino/doc/Phillips AJ3150.pdf b/arduino/doc/Phillips AJ3150.pdf
new file mode 100644
index 0000000..c081bb8
Binary files /dev/null and b/arduino/doc/Phillips AJ3150.pdf differ
diff --git a/arduino/doc/RTC DS1307.pdf b/arduino/doc/RTC DS1307.pdf
new file mode 100644
index 0000000..fbfd54e
Binary files /dev/null and b/arduino/doc/RTC DS1307.pdf differ
diff --git a/arduino/src/LTC637D1P.h b/arduino/src/LTC637D1P.h
new file mode 100644
index 0000000..aaefa17
--- /dev/null
+++ b/arduino/src/LTC637D1P.h
@@ -0,0 +1,234 @@
+#ifndef LTC637
+#define LTC637
+
+#include "Arduino.h"
+
+const int SEGMENTS = 28;
+
+const int P6 = 0;
+const int P7 = 1;
+const int P8 = 2;
+const int P9 = 3;
+const int P10 = 4;
+const int P12 = 5;
+const int P13 = 6;
+const int P15 = 7;
+const int P16 = 8;
+const int P17 = 9;
+const int P18 = 10;
+const int P19 = 11;
+const int P20 = 12;
+const int P21 = 13;
+
+const boolean SEGMENT_PHASE[] = {
+ 0, 1, 1, 0, 1, 0, 1,
+ 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1,
+ 1, 1, 1, 0, 0, 0, 0
+};
+
+const int SEGMENT_PIN[] = {
+ P7, P6, P9, P8, P8, P6, P7,
+ P13, P10, P12, P12, P9, P13, P10,
+ P15, P16, P17, P17, P18, P15, P16,
+ P21, P19, P20, P20, P18, P21, P19
+};
+
+const boolean FONT[][7] = {
+ { 0, 0, 0, 0, 0, 0, 0 }, // BLANK (use /)
+ { 1, 1, 1, 1, 1, 1, 0 }, // 0
+ { 0, 1, 1, 0, 0, 0, 0 }, // 1
+ { 1, 1, 0, 1, 1, 0, 1 }, // 2
+ { 1, 1, 1, 1, 0, 0, 1 }, // 3
+ { 0, 1, 1, 0, 0, 1, 1 }, // 4
+ { 1, 0, 1, 1, 0, 1, 1 }, // 5
+ { 1, 0, 1, 1, 1, 1, 1 }, // 6
+ { 1, 1, 1, 0, 0, 0, 0 }, // 7
+ { 1, 1, 1, 1, 1, 1, 1 }, // 8
+ { 1, 1, 1, 1, 0, 1, 1 }, // 9
+ { 0, 0, 0, 0, 0, 0, 0 }, // :
+ { 0, 0, 0, 0, 0, 0, 0 }, // ;
+ { 0, 0, 0, 0, 0, 0, 0 }, // <
+ { 0, 0, 0, 1, 0, 0, 1 }, // =
+ { 0, 0, 0, 0, 0, 0, 0 }, // >
+ { 1, 1, 0, 0, 1, 0, 1 }, // ?
+ { 0, 0, 0, 0, 0, 0, 0 }, // @
+ { 1, 1, 1, 0, 1, 1, 1 }, // A
+ { 0, 0, 1, 1, 1, 1, 1 }, // B
+ { 0, 0, 0, 1, 1, 0, 1 }, // C
+ { 0, 1, 1, 1, 1, 0, 1 }, // D
+ { 1, 0, 0, 1, 1, 1, 1 }, // E
+ { 1, 0, 0, 0, 1, 1, 1 }, // F
+ { 1, 0, 1, 1, 1, 1, 0 }, // G
+ { 0, 0, 1, 0, 1, 1, 1 }, // H
+ { 0, 1, 1, 0, 0, 0, 0 }, // I
+ { 0, 1, 1, 1, 1, 0, 0 }, // J
+ { 0, 1, 1, 0, 1, 1, 1 }, // K
+ { 0, 0, 0, 1, 1, 1, 0 }, // L
+ { 0, 0, 0, 0, 0, 0, 0 }, // M
+ { 0, 0, 1, 0, 1, 0, 1 }, // N
+ { 0, 0, 1, 1, 1, 0, 1 }, // O
+ { 1, 1, 0, 0, 1, 1, 1 }, // P
+ { 1, 1, 1, 0, 0, 1, 1 }, // Q
+ { 0, 0, 0, 0, 1, 0, 1 }, // R
+ { 1, 0, 1, 1, 0, 1, 1 }, // S
+ { 0, 0, 0, 1, 1, 1, 1 }, // T
+ { 0, 0, 1, 1, 1, 0, 0 }, // U
+ { 0, 0, 1, 1, 1, 0, 0 }, // V
+ { 0, 0, 0, 0, 0, 0, 0 }, // W
+ { 0, 1, 1, 0, 1, 1, 1 }, // X
+ { 0, 1, 1, 1, 0, 1, 1 }, // Y
+ { 1, 1, 0, 1, 1, 0, 1 } // Z
+};
+
+const byte DOT_AM = 0;
+const byte DOT_PM = 1;
+const byte DOT_MID = 2;
+
+class View {
+ public:
+ View(int pG1, int pG2, int pClock1, int pClock2, int pLatch1, int pLatch2, int pData1, int pData2) {
+ pinClock1 = pClock1;
+ pinClock2 = pClock2;
+ pinLatch1 = pLatch1;
+ pinLatch2 = pLatch2;
+ pinData1 = pData1;
+ pinData2 = pData2;
+ pinG1 = pG1;
+ pinG2 = pG2;
+
+ for(int i=0; i
+#include
+#include
+#include
+
+#define VERSION "v2.0.3"
+//#define DIAG
+
+const int G1 = 6; // cathode 1 for LED display
+const int G2 = 7; // cathode 2 for LED display
+const int CLOCK1 = 8;
+const int LATCH1 = 9;
+const int DATA1 = 10;
+const int CLOCK2 = 11;
+const int LATCH2 = 12;
+const int DATA2 = 5; // was in D13, moved to stopped the blinking LED
+const int DHT11PIN = 2;
+const int REFRESH_DELAY = 10;
+
+// Digital Humidity and Temperature sensor
+dht11 DHT11;
+
+// Bluetooth serial adapter
+SoftwareSerial btSerial(3, 4);
+
+// LED display
+View view(G1, G2, CLOCK1, CLOCK2, LATCH1, LATCH2, DATA1, DATA2);
+
+// Real Time Clock
+RTC_DS1307 RTC;
+
+// which cathode to use for the LED display (false=1, true=2)
+boolean phase = false;
+
+short lastScreen = 0;
+unsigned long startOfDayMillis = 0;
+unsigned long lastSyncTime = 0;
+unsigned long lastUpdate = 0;
+uint16_t yr = 0;
+uint8_t mth = 0;
+uint8_t day = 0;
+uint8_t hr = 0;
+uint8_t min = 0;
+uint8_t sec = 0;
+bool rtcMode = false;
+
+void setup() {
+ // start up the I2C comms
+ Wire.begin();
+
+ // start the RTC
+ RTC.begin();
+ if(RTC.isrunning()) {
+ rtcMode = true;
+ }
+
+ Serial.begin(9600);
+ Serial.print("SUPER CLOCK ");
+ Serial.print(VERSION);
+ Serial.println(" (Terminal)");
+ Serial.println(rtcMode ? "RTC Mode enabled" : "RTC Mode disabled");
+
+ btSerial.begin(9600);
+ btSerial.print("SUPER CLOCK ");
+ btSerial.print(VERSION);
+ btSerial.print(" (Bluetooth)");
+ btSerial.println(rtcMode ? "RTC Mode enabled" : "RTC Mode disabled");
+}
+
+#ifndef DIAG
+void loop() {
+
+ // update the character buffer
+ timeTempHumidity();
+
+ // commit the character buffer to the display
+ view.write(phase);
+
+ // handle any input coming from Bluetooth
+ handleBluetoothInput();
+
+ // switch the phase (effectively switches the cathode)
+ phase = !phase;
+
+ // allow the CPU to rest
+ delay(REFRESH_DELAY);
+}
+#endif
+
+/**
+ * Test the display by cycling through the character set
+ */
+#ifdef DIAG
+void diagnostic() {
+ int seconds = (millis() / 1000);
+ int x = seconds % 26;
+ for(int i=0; i<4; i++) {
+ view.setChar(i, x + 'A');
+ }
+}
+#endif
+
+void handleBluetoothInput() {
+ if (btSerial.available()) {
+ char command = (char)btSerial.read();
+
+ if(command == '\n' || command == '\r')
+ return;
+
+ Serial.print("RX cmd=");
+ Serial.println(command);
+
+ if(command == 'f') {
+ btSerial.read();
+ btSerial.print("f=");
+ btSerial.println(VERSION);
+ }
+ else if(command == 'd') {
+ btSerial.read();
+ btSerial.print("d=");
+ btSerial.print(day, DEC);
+ btSerial.print('/');
+ btSerial.print(mth, DEC);
+ btSerial.print('/');
+ btSerial.print(yr, DEC);
+ btSerial.print('T');
+ btSerial.print(hr, DEC);
+ btSerial.print(':');
+ btSerial.print(min, DEC);
+ btSerial.print(':');
+ btSerial.println(sec, DEC);
+ }
+ else if(command == 't') {
+ btSerial.print("t=");
+ btSerial.println(DHT11.temperature);
+ }
+ else if(command == 'h') {
+ btSerial.print("h=");
+ btSerial.println(DHT11.humidity);
+ }
+ else if(command == 'u') {
+ btSerial.print("u=");
+ btSerial.println(millis());
+ }
+ else if(command == 'l') {
+ btSerial.print("l=");
+ btSerial.println(millis() - lastSyncTime);
+ }
+ else if(command == 'r') {
+ btSerial.print("r=");
+ btSerial.println(rtcMode ? 1 : 0);
+ }
+ else if(command == 's') {
+ day = btSerial.parseInt();
+ btSerial.read();
+ mth = btSerial.parseInt();
+ btSerial.read();
+ yr = btSerial.parseInt();
+ btSerial.read();
+ hr = btSerial.parseInt();
+ btSerial.read();
+ min = btSerial.parseInt();
+ btSerial.read();
+ sec = btSerial.parseInt();
+ btSerial.read();
+
+ uint32_t oldTime = RTC.now().unixtime();
+ RTC.adjust(DateTime(yr, mth, day, hr, min, sec));
+ uint32_t newTime = RTC.now().unixtime();
+ uint32_t timeDiff = newTime - oldTime;
+
+ Serial.print(" New Time: ");
+ Serial.print(day);
+ Serial.print("/");
+ Serial.print(mth);
+ Serial.print("/");
+ Serial.print(yr);
+ Serial.print(" ");
+ Serial.print(hr);
+ Serial.print(":");
+ Serial.print(min);
+ Serial.print(":");
+ Serial.println(sec);
+ Serial.print("Adjustment: ");
+ Serial.print(timeDiff / 1000.0);
+ Serial.println("s");
+
+ unsigned long secondOfDay = (hr * 60l * 60l) + (min * 60l);
+ startOfDayMillis = millis() - (secondOfDay * 1000);
+ lastSyncTime = millis();
+
+ btSerial.print("s=");
+ btSerial.println(timeDiff / 1000.0);
+ }
+ }
+}
+
+/**
+ * The display is on a 10 seconds cycle:
+ * - time for 6 seconds
+ * - temperature for 2 seconds
+ * - humidity for 2 seconds
+ */
+void timeTempHumidity() {
+
+ // calculate where we are in the cycle (0-9 seconds)
+ byte time = ((millis() % 10000) / 1000);
+
+ if(time < 6) {
+ writeTimeValue();
+ lastScreen = 0;
+ }
+ else if(time < 8) {
+ // only perform the temp/humidity read when switching screen
+ if(lastScreen != 1) {
+ readTempAndHumidity();
+ }
+ writeTemp();
+ lastScreen = 1;
+ }
+ else if(time < 10) {
+ writeHumidity();
+ lastScreen = 2;
+ }
+}
+
+/**
+ * Interrogate the DHT11 and request it to read values.
+ * Note: This is a slow operation and can make the display flicker. It is best
+ * to do the read while the display is switching
+ */
+void readTempAndHumidity() {
+ DHT11.read(DHT11PIN);
+}
+
+/**
+ * Write the temperature to the character buffer
+ * Format is " t23"
+ */
+void writeTemp() {
+ view.setCharAndDigit('T', (int)DHT11.temperature);
+ view.setDotState(DOT_AM, false);
+ view.setDotState(DOT_PM, false);
+ view.setDotState(DOT_MID, false);
+}
+
+/**
+ * Write the humidity value to the character buffer
+ * Format is " h54"
+ */
+void writeHumidity() {
+ view.setCharAndDigit('H', (int)DHT11.humidity);
+ view.setDotState(DOT_AM, false);
+ view.setDotState(DOT_PM, false);
+ view.setDotState(DOT_MID, false);
+}
+
+/**
+ * Write the time to the character buffer
+ * Format is "HH:MM" (24hr time)
+ */
+void writeTimeValue() {
+ if(millis() - lastUpdate > 1000) {
+ if(rtcMode) {
+ updateTimeValueFromRTC();
+ }
+ else {
+ updateTimeValueFromMillis();
+ }
+ lastUpdate = millis();
+ }
+
+ view.setDigits(hr, min);
+ view.setDotState(DOT_AM, false);
+ view.setDotState(DOT_PM, false);
+ view.setDotState(DOT_MID, !((millis() / 500) % 2));
+}
+
+/**
+ * Read the time from the RTC
+ * Note: this is a slow operation and cause the display to flicker
+ */
+void updateTimeValueFromRTC() {
+ DateTime now = RTC.now();
+
+ if(now.hour() < 24 && now.minute() < 60 && now.second() < 60) {
+ hr = now.hour();
+ min = now.minute();
+ sec = now.second();
+
+ if(now.year() > 2012 && now.year() < 2100 && now.month()<= 12 && now.day() <= 31) {
+ yr = now.year();
+ mth = now.month();
+ day = now.day();
+ }
+ }
+}
+
+/**
+ * Work out what the time is when RTC is not available.
+ * Time needs to be initially set via Bluetooth.
+ */
+void updateTimeValueFromMillis() {
+ unsigned long now = millis();
+ unsigned long secondOfDay = (now - startOfDayMillis) / 1000;
+ byte secondOfMinute = secondOfDay % 60;
+ byte minuteOfHour = (secondOfDay / 60) % 60;
+ byte hourOfDay = (secondOfDay / 60 / 60) % 24;
+
+ if(secondOfDay >= 86400) {
+ startOfDayMillis += 86400000;
+ }
+}
+
+
+
+
+
+
diff --git a/arduino/src/util.h b/arduino/src/util.h
new file mode 100644
index 0000000..9994179
--- /dev/null
+++ b/arduino/src/util.h
@@ -0,0 +1,24 @@
+#ifndef UTIL_H
+#define UTIL_H
+
+#include "Arduino.h"
+
+void writeByteAsBits(byte b) {
+ for (byte mask = 00000001; mask>0; mask <<= 1) {
+ boolean val = b & mask;
+ Serial.print(val ? 1 : 0);
+ }
+}
+
+void writeBitArray(boolean* buf) {
+ Serial.print("Buffer: ");
+ for(int i=0; i