Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

started implementing alarm logic and sync

added sync mode to sync the clock automaticly when a sync ping package is seen.
started alarm clock logic
fixed desync and ap lockup bug. pulling the ap off and on is not required. hurray :-)
fixes...
  • Loading branch information...
commit 09450ebe93c291fc4e68238e32d5f69ed9b9058c 1 parent 562c960
@poelzi authored
View
4 TODO
@@ -1 +1,3 @@
-• fix hero weather proxy
+• fix hero weather proxy
+• make it translatable
+• unittests
View
112 db/alarm.py
@@ -0,0 +1,112 @@
+"""
+Alarm clock logic
+"""
+
+
+class Manager(object):
+ """Manages Alarm Programs"""
+
+ def __init__(self):
+ self.programs = {}
+
+ def register(self, program):
+ """
+ Register a new alarm clock program
+ """
+ if not program.nid:
+ raise ValueError, "program id (nid) is not set"
+ if program.nid in self.programs and self.programs[program.nid] != program:
+ raise ValueError, "program id (nid) already exists"
+
+ self.programs[program.nid] = program
+
+ def list_programs(self):
+ """
+ Return a list of all programs
+ """
+ return [program for program in self.programs.iteritems()]
+
+ @property
+ def choices_programs(self):
+ """
+ Return a choices list used in django
+ """
+ rv = []
+ for program in self.programs.iteritems():
+ rv.append((program.nid, program))
+ return rv
+
+ def is_valid_program_id(self, nid):
+ for program in self.programs.iteritems():
+ if program.nid == nid:
+ return True
+
+ def create_session(self, user_name, program):
+ pass
+
+manager = Manager()
+
+
+class AlarmInstance(object):
+ """
+ Running instance of a alarm clock.
+ It is assigned to a user.
+ """
+
+ def __init__(self, user, detector, program=None):
+ self.user = user
+ self.detector = detector
+ self.program = program
+
+
+class BaseAction(object):
+ """
+ Action to take
+ """
+ def __init__(self, *args, **kwargs):
+ self.args = args
+ self.kwargs = kwargs
+
+ def execute(self):
+ raise NotImplemented
+
+
+class ExecuteAction(BaseAction):
+ """
+ Executes external program
+ """
+ def execute(self):
+ import subprocess
+ self.popen = subprocess.Popen(self.args)
+ self.popen.communicate()
+
+class BaseAlarm(object):
+ """
+ Base Class for all alarm logics
+ """
+ # numeric id. must be unique and change never
+ nid = None
+ title = None
+
+ def __init__(self, manager, session):
+ self.manager = manager
+ self.session = session
+ self.next_alarm = None
+
+ def check(self):
+ """
+ Returns a alarm action to execute
+ """
+ return None
+
+ def snooze(self):
+ """
+ User pressed snooze
+ """
+ pass
+
+ def feed(self, entry):
+ """
+ Feed one new Entry into the Alarm program.
+ """
+ pass
View
10 db/management/commands/uberclockd.py
@@ -13,9 +13,6 @@
import django.core.handlers.wsgi
import logging
-#if __name__ == "__main__":
-# os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
-
class Command(BaseCommand):
args = ''
help = 'Runs the UberClock background daemon'
@@ -27,7 +24,6 @@ class Command(BaseCommand):
)
def handle(self, *args, **options):
- print options, args
logging.basicConfig(level=logging.DEBUG)
thread.start_new_thread(self.start_webserver, ())
@@ -52,7 +48,7 @@ def ez_chronos(self, *args, **options):
logging.info("OpenChronos interface started on %s" %self.msp_ap)
pv.loop_smpl_get()
except KeyboardInterrupt:
- pv.reset()
+ pv.close()
ser.close()
sys.exit(0)
except Exception, e:
@@ -62,6 +58,10 @@ def ez_chronos(self, *args, **options):
except: pass
try: pv.close()
except: pass
+ try:
+ ser.close()
+ del ser
+ except: pass
def start_webserver(self):
server = wsgiserver.CherryPyWSGIServer(
View
110 db/models.py
@@ -4,21 +4,23 @@
from django.contrib import admin
from django.conf import settings
from django.utils.dateformat import time_format, format
+from . import alarm
import struct
import logging
import datetime
import time
+import random
# Create your models here.
DETECTOR_TYPES = (
(0, "OpenChronos"),
)
-SESSION_TYPES = (
- (0, "Normal Sleep"),
- (1, "Powernap"),
- (2, "One REM"),
-)
+# SESSION_TYPES = (
+# (0, "Normal Sleep"),
+# (1, "Powernap"),
+# (2, "One REM"),
+# )
RF_ID_BIT_LENGHT = 3
@@ -42,7 +44,7 @@ class WakeupTime(models.Model):
user = models.ForeignKey(User, null=False)
# fixme choices etc
weekday = models.IntegerField("Weekday", null=True)
- session_typ = models.IntegerField("Type", default=0, choices=SESSION_TYPES)
+ session_typ = models.IntegerField("Type", default=0, choices=alarm.manager.choices_programs)
wakeup = models.TimeField("Wakeup", null=False)
@@ -58,28 +60,47 @@ def get_active_sessions(self, **kwargs):
return self.filter(stop__gt=start, closed=False, **kwargs)
def get_new_rf_id(self):
- id_s = xrange(1, (2**(RF_ID_BIT_LENGHT+1)))
+ id_s = range(1, (2**(RF_ID_BIT_LENGHT+1)))
now = datetime.datetime.now()
start = now - datetime.timedelta(seconds=settings.CLOCK_SESSION_TIMEOUT)
actives = self.filter(stop__gt=start).values_list("rf_id", flat=True)
+ # return a "random" id
+ random.shuffle(id_s)
for x in id_s:
if x not in actives:
return x
+ def get_new_session(self, user):
+ """
+ Return a new Session for the user. If the user created a new session it
+ will be returned
+ """
+ rv = self.filter(user=user, new=True).order_by("id")
+ if len(rv):
+ rv = rv[0]
+ # set the new_ flag to False so it will not be returned again
+ rv.new_ = False
+ rv.save()
+ return rv
+ else:
+ # create a new session
+ pass
+
class Session(models.Model):
"""
One Sleep Session
"""
start = models.DateTimeField("Start", null=False, auto_now_add=True, editable=False)
stop = models.DateTimeField("Stop", null=False, auto_now_add=True, editable=False)
- user = models.ForeignKey(User, null=True, blank=True)
- detector = models.ForeignKey(Detector, null=True, blank=True)
- typ = models.IntegerField("Type", default=0, choices=SESSION_TYPES)
- wakeup = models.DateTimeField("Wakeup", null=True, blank=True)
- rating = models.IntegerField("Rating", null=True, blank=True)
+ user = models.ForeignKey(User, null=True)
+ detector = models.ForeignKey(Detector, null=True)
+ program = models.IntegerField("Program", default=0, choices=alarm.manager.choices_programs)
+ wakeup = models.DateTimeField("Wakeup", null=True)
+ rating = models.IntegerField("Rating", null=True)
deleted = models.BooleanField("Deleted", default=False)
- rf_id = models.IntegerField("RF Id", null=True, blank=True)
+ rf_id = models.IntegerField("RF Id", null=True)
closed = models.BooleanField("Session has ended", default=False)
+ new = models.BooleanField("Session has not yet run", default=False)
objects = SessionManager()
# Do we need this ?
@@ -112,12 +133,8 @@ def __unicode__(self):
entries = self.entry_set.all().count()
return u"Session from %s %s (%s:%0.2d) (%s Entries)" %(self.user, format(self.start, settings.DATETIME_FORMAT), length[0], length[1], entries)
- @property
- def entries_count(self):
- return self.entry_set.all().count()
-
-
def merge(self, source):
+ # FIXME: add a zero datapoint maybe if the time
source.entry_set.all().update(session=self)
source.learndata_set.all().delete()
@@ -174,11 +191,6 @@ class LearnData(models.Model):
help_text="When sleep stopped", null=True)
learned = models.BooleanField(default=False)
- @property
- def placed(self):
- """Did the user set any points"""
- return any((self.wake, self.lights, self.start, self.stop))
-
SIMPLICITI_PHASE_CLOCK_START_RESPONSE = 0x54
@@ -193,6 +205,7 @@ def __init__(self, *args, **kwargs):
super(DBWriter, self).__init__(*args, **kwargs)
self.last_msg = {}
self.session = {}
+ self.syncrun = 0
def smpl_0x01(self, data):
# acceleration data
@@ -272,11 +285,11 @@ def smpl_0x04(self, data):
if not active_session:
rf_id = Session.objects.get_new_rf_id()
- typ = ord(mdata[2])
- if not any((True for x in SESSION_TYPES if x[0] == typ)):
- typ = 0
+ program = ord(mdata[2])
+ if not alarm.manager.is_valid_program_id(program):
+ program = 0
active_session = Session(start=now, stop=now, detector=device, user=device.default_user,
- rf_id=rf_id, typ=typ)
+ rf_id=rf_id, program=program)
active_session.save()
logging.info("Send Session ID: %s" %(active_session.rf_id))
@@ -286,5 +299,44 @@ def smpl_0x04(self, data):
# we shall not do anything until the clock reads out the
# session data
time.sleep(0.030)
-
-
+
+ def smpl_0x10(self, data):
+ """
+ Start sync mode
+ """
+ logging.debug("start sync mode")
+ # taken from http://pastebin.com/f62344dbd
+ # empty buffer
+ self.read_sync_data()
+ # read old data
+ for x in xrange(1000):
+ self.send_smpl_data([ez_chronos.SYNC_AP_CMD_GET_STATUS], wait=False)
+ time.sleep(0.020)
+ if self.get_sync_buffer_status():
+ rcv = self.read_sync_data()
+ cdata = self.parse_sync_data(rcv)
+ if cdata:
+ break
+ else:
+ print "no data for next step"
+ return
+ ctime = datetime.datetime.now()
+ #{'alarm_hour': 6, 'hour': 4, 'tempCelcius': 272, 'metric': 1, 'month': 8, 'second': 56, 'year': 2009, 'alarm_minute': 30, 'altMeters': 485, 'day': 1, 'minute': 50}
+ if not cdata:
+ return
+ cdata['hour'] = ctime.hour
+ cdata['minute'] = ctime.minute
+ cdata['second'] = ctime.second
+ cdata['day'] = ctime.day
+ cdata['month'] = ctime.month
+ cdata['year'] = ctime.year
+ if settings.CLOCK_ALTITUDE is not None:
+ cdata['alt_meters'] = settings.CLOCK_ALTITUDE
+
+ print "send", cdata, self.build_sync_data(cdata)
+
+ for x in xrange(10):
+ self.send_smpl_data(self.build_sync_data(cdata))
+ time.sleep(0.100)
+
+ self.send_smpl_data([ez_chronos.SYNC_AP_CMD_EXIT])
View
3  settings.py
@@ -126,6 +126,9 @@
PISTON_STREAM_OUTPUT = True
+# hight in meters above normal hight
+CLOCK_ALTITUDE = None
+
CHUMBY_URLS = {
'default' : '<embed width="800" height="480" quality="high" bgcolor="#FFFFFF" wmode="transparent" name="virtualchumby" type="application/x-shockwave-flash" src="http://www.chumby.com/virtualchumby_noskin.swf" FlashVars="_chumby_profile_url=http%3A%2F%2Fwww.chumby.com%2Fxml%2Fvirtualprofiles%2FE8E34C1E-726B-11DF-BA50-001B24F07EF4&amp;baseURL=http%3A%2F%2Fwww.chumby.com" pluginspage="http://www.macromedia.com/go/getflashplayer"></embed>'
}
View
185 tools/ez_chronos.py
@@ -6,7 +6,10 @@
from time import sleep
def msleep(msec):
- sleep(1.0/(1000*msec))
+ sleep(0.001*msec)
+
+def splitIntoNPieces(s,n):
+ return [s[i:i+n] for i in range(0, len(s), n)]
log = logging.getLogger("ez_chronos")
@@ -78,6 +81,14 @@ def msleep(msec):
SYNC_AP_CMD_NOOP = 0x01
SYNC_DATA_SENT = 0x55
+SYNC_AP_CMD_NOP = 0x01
+SYNC_AP_CMD_GET_STATUS = 0x02
+SYNC_AP_CMD_SET_WATCH = 0x03
+SYNC_AP_CMD_GET_MEMORY_BLOCKS_MODE_1 = 0x04
+SYNC_AP_CMD_GET_MEMORY_BLOCKS_MODE_2 = 0x05
+SYNC_AP_CMD_ERASE_MEMORY = 0x06
+SYNC_AP_CMD_EXIT = 0x07
+
class Simpliciti(object):
"""
Connection class for interaction with a CC1111 dongle
@@ -93,21 +104,29 @@ def __init__(self, stream):
self.rbuffer = ""
self.autosync = True
self.last_cmd = ()
- self._nullread()
+ #self._nullread()
self.debug = False
+ self.sync()
+ print self.dstr(self.send_read(BM_GET_PRODUCT_ID, [0x00, 0x00, 0x00, 0x00]))
#def
- def _nullread(self):
- self.stream.read()
+ #def _nullread(self):
+ # self.stream.read()
@staticmethod
def dstr(data):
+ if not data:
+ return ""
rv = ""
for x in data:
rv += "\\x%02x" %ord(x)
return rv
def close(self):
+ """
+ Shut down gracefully
+ """
+ self.stop_ap()
self.stream.close()
self.stream = None
@@ -139,16 +158,26 @@ def send(self, cmd_, data=None):
res = array.array('B', [0xFF, cmd_, ln])
self.last_cmd = (cmd_, ln)
if self.debug:
- if res.tostring() != '\xff\x08\x07\x00\x00\x00\x00':
+ if self.debug >= 2:
print "send:" + self.dstr(res.tostring())
+ else:
+ if res.tostring() not in ('\xff\x08\x07\x00\x00\x00\x00', '\xff\x32\x04\x00'):
+ print "send:" + self.dstr(res.tostring())
self.stream.write(res.tostring())
msleep(15)
return ln
def sync(self):
self.last_cmd = ()
- self.stream.read()
- self.send_read(BM_GET_STATUS, [0x00])
+ print "waiting", self.stream.inWaiting()
+ self.stop_ap()
+ #self.stream.read()
+ for i in xrange(10):
+ res = self.send_read(BM_GET_STATUS, [0x00])
+ print self.dstr(res)
+ res = self.send_read(BM_RESET, [])
+ print self.dstr(res)
+ #self.stream.read()
def read(self, ln=None):
offset = 0
@@ -162,7 +191,8 @@ def read(self, ln=None):
for i in xrange(len(self.rbuffer)):
if ord(self.rbuffer[i]) == 0xFF:
log.info("offset detected %s" %i)
- if ord(self.rbuffer[i+1]) == self.last_cmd[0] and \
+ if len(self.rbuffer) >= i+2 and \
+ ord(self.rbuffer[i+1]) == self.last_cmd[0] and \
ord(self.rbuffer[i+2]) == self.last_cmd[1]:
offset = i
break
@@ -188,30 +218,65 @@ def reset(self):
def start_ap(self):
self.send_read(BM_START_SIMPLICITI)
+ def stop_ap(self):
+ #The start access point command needs to come before the stop access point command
+ #in order for the access point to turn off.
+ self.send_read(BM_START_SIMPLICITI)
+ self.send_read(BM_STOP_SIMPLICITI)
+
def send_get_smpl_data(self):
return self.send_read(BM_GET_SIMPLICITIDATA, [0x00, 0x00, 0x00, 0x00])
- def send_smpl_data(self, data, timeout=3000):
- if len(data) != BM_SYNC_DATA_LENGTH-3:
- data = data[:BM_SYNC_DATA_LENGTH-3] + [0x00 for x in range(BM_SYNC_DATA_LENGTH-len(data)-3)]
- snd = self.send_read(BM_SYNC_SEND_COMMAND, data)
- for i in xrange(timeout/20):
- msleep(20)
- buf = self.send_get_smpl_data()
- if buf != '\xff\x06\x07\xff\x00\x00\x00':
- print "read", repr(buf)
- if buf[4] == SYNC_AP_CMD_NOOP and buf[5] == SYNC_DATA_SENT:
- print "JUHU"
+ def get_sync_buffer_status(self):
+ res = self.send_read(BM_SYNC_GET_BUFFER_STATUS, [0x00])
+ if self.debug >= 2:
+ print "sync status", self.dstr(res)
+ return ord(res[3])
+
+ def wait_sync_buffer(self, timeout=1000, must=True):
+ for i in xrange(timeout/5):
+ status = self.get_sync_buffer_status()
+ if status == must:
return True
-
- status = self.status()
- print "status", repr(status)
+ msleep(5)
+ def send_smpl_data(self, data, wait=False, timeout=1000):
+ if len(data) != BM_SYNC_DATA_LENGTH:
+ data = data[:BM_SYNC_DATA_LENGTH] + [0x00 for x in range(BM_SYNC_DATA_LENGTH-len(data))]
+ snd = self.send_read(BM_SYNC_SEND_COMMAND, data)
+# return snd
+ if wait:
+ for i in xrange(timeout/30):
+ msleep(30)
+ status = self.get_sync_buffer_status()
+ if status:
+ return ""#self.read_sync_data()
+ #buf = self.send_get_smpl_data()
+ #if buf != '\xff\x06\x07\xff\x00\x00\x00':
+ # print "OH read", repr(buf)
+# # return
+ #elif buf[4] == SYNC_AP_CMD_NOOP and buf[5] == SYNC_DATA_SENT:
+ # print "JUHU"
+ # return True
+ #else:
+ # self.send_read(BM_SYNC_SEND_COMMAND, data)
+
+ #status = self.status()
+ #print "status", repr(status)
+
+ def read_sync_data(self, data=None, timeout=3000):
+ if not data:
+ data = []
+ if len(data) != BM_SYNC_DATA_LENGTH:
+ data = data[:BM_SYNC_DATA_LENGTH] + [0x00 for x in range(BM_SYNC_DATA_LENGTH-len(data))]
+ self.send(BM_SYNC_READ_BUFFER)
+ red = self.read(BM_SYNC_DATA_LENGTH+3)
+ print "read sync", self.dstr(red)
+ return red
def status(self):
return self.send_read(BM_GET_STATUS, [0x00])
-
@staticmethod
def get_smpl_data(data, add=1):
return data[SIMPLICITI_DATA_OFFSET+add:]
@@ -248,7 +313,6 @@ def get_status(self, data):
print res
def handle_smpl_data(self, data):
- #print "handle", repr(data)
if self.debug > 2:
print "got", repr(data)
if len(data) < 4:
@@ -257,6 +321,7 @@ def handle_smpl_data(self, data):
if self.debug > 3:
print "got no data"
else:
+ print "handle", self.dstr(data)
if hasattr(self, "smpl_%s" %"0x%0.2X" %ord(data[3])):
getattr(self, "smpl_%s" %"0x%0.2X" %ord(data[3]))(data)
else:
@@ -271,6 +336,78 @@ def smpl_default(self, data):
if self.debug:
print "unhandled", " ".join(["0x%0.2X" %ord(a) for a in self.get_smpl_data(data, 0)])
+ """
+ kinda messy
+
+ Here's how this works.
+ A packet for the watch sync looks like this:
+ FF 31 16 03 94 34 01 07 DA 01 17 06 1E 00 00 00 00 00 00 00 00 00
+ You have the first four bytes which we're just going to ignore.
+ The 5th byte represents the hour you want to sync to.
+ The 6th byte represents the minute you want to sync to.
+ The 7th byte represents the second you want to sync to.
+ The 9th byte represents the year you want to sync to.
+ The 10th byte represents the month you want to sync to.
+ The 11th byte represents the day you want to sync to.
+ THe 14th and 15th bytes represent the temperature in celcius.
+ The 16th and 17th bytes represent the altitude in meters.
+
+ It also stores some of these values in some unusual (to me) ways.
+
+ For hour, the value 0x80 needs to be added to the 24hr representation of the desired
+ hour to sync to.
+
+ For year the value 0x700 needs to be subtracted from the desired year to sync to.
+
+ For temperature, the temperature (in celcius) needs to be multiplied by 0x0A.
+
+ Caveat emptor: There is no error checking implemented to check for valid ranges.
+ """
+ #{'alarm_hour': 6, 'hour': 4, 'tempCelcius': 272, 'metric': 1, 'month': 8, 'second': 56, 'year': 2009, 'alarm_minute': 30, 'altMeters': 485, 'day': 1, 'minute': 50}
+ def build_sync_data(self, kwargs):
+ adjHour = kwargs['metric'] << 7 | kwargs['hour']
+ adjYear = struct.pack('>H', kwargs['year'])
+ adjTempCelcius = kwargs['temp_celcius'] # * 0x0A
+ cmd = [SYNC_AP_CMD_SET_WATCH,
+ adjHour,
+ kwargs['minute'],
+ kwargs['second'],
+ ord(adjYear[0]),ord(adjYear[1]),
+ kwargs['month'],
+ kwargs['day'],
+ kwargs['alarm_hour'],
+ kwargs['alarm_minute']]
+
+ hexCelcius = hex(adjTempCelcius)[2:].zfill(4)
+ hexMeters = hex(kwargs['alt_meters'])[2:].zfill(4)
+
+ for i in splitIntoNPieces(hexCelcius,2):
+ cmd.append(int(i,16))
+ for i in splitIntoNPieces(hexMeters,2):
+ cmd.append(int(i,16))
+
+ for i in xrange(0,3):
+ cmd.append(0)
+
+ return cmd
+
+ def parse_sync_data(self, data, offset=3):
+ if len(data) >= offset+13 and ord(data[offset]) == 0x03:
+ # status data
+ rv = {}
+ # FIXME: fix am/pm code in hours
+ rv["metric"] = ord(data[offset+1]) >> 7
+ rv["hour"] = ord(data[offset+1])&0x7F
+ rv["minute"] = ord(data[offset+2])
+ rv["second"] = ord(data[offset+3])
+ rv["year"] = struct.unpack('>H', array.array('B', [ord(data[offset+4]),ord(data[offset+5])]).tostring())[0]
+ rv["month"] = ord(data[offset+6])
+ rv["day"] = ord(data[offset+7])
+ rv["alarm_hour"] = ord(data[offset+8])
+ rv["alarm_minute"] = ord(data[offset+9])
+ rv["temp_celcius"] = struct.unpack('>H', array.array('B', [ord(data[offset+10]),ord(data[offset+11])]).tostring())[0]
+ rv["alt_meters"] = struct.unpack('>H', array.array('B', [ord(data[offset+12]),ord(data[offset+13])]).tostring())[0]
+ return rv
class ProtocolView(CommandDispatcher):
View
2  webclock/templates/stats.html
@@ -21,7 +21,7 @@
{% ifchanged session.week%}<h3>Week {{ session.start|date:"W" }}</h3>{% endifchanged %}
{% endif %}
<a href="/stats/{{ session.id }}/" class="stats_detail">{{ session.start|date:"DATETIME_FORMAT" }} - {{ session.stop|date:"DATETIME_FORMAT" }} ({{session.length|timelengh}})</a>
- {% if session.learndata.placed %}<img src="/static/webclock/generic/marker.png" />{% endif %}
+ {% if session.learndata.placed %}<img src="/static/webclock/generic/marker.png" alt="Marked" title="Marker are set" />{% endif %}
<br/>
{% endfor %}
Please sign in to comment.
Something went wrong with that request. Please try again.