Permalink
Browse files

implement two phase intialisation

the clock is now using a start sequence to get a sleep session id
from uberclock. this is used to identify the clock while still be
compatible with the broken firmware of the chronos access point.
but this allows much more features so it's a good thing :-)

* fixes to still partiall broken accesspoint class
  • Loading branch information...
1 parent f3aa942 commit d191ec242e3c6424f4af427d53f962a79f351543 @poelzi committed Jul 10, 2010
Showing with 164 additions and 18 deletions.
  1. +13 −4 db/management/commands/uberclockd.py
  2. +96 −11 db/models.py
  3. 0 external/__init__.py
  4. +54 −2 tools/ez_chronos.py
  5. +1 −1 uberclockd
View
17 db/management/commands/uberclockd.py
@@ -2,6 +2,7 @@
from uberclock.db.models import DBWriter
from django.conf import settings
import sys, thread
+from optparse import make_option
import serial
@@ -17,21 +18,29 @@
class Command(BaseCommand):
args = ''
- help = 'Runs a background daemon'
+ help = 'Runs the UberClock background daemon'
+ option_list = BaseCommand.option_list + (
+ make_option('--msp-ap',
+ dest='msp_ap',
+ default=None,
+ help='EZ430 Accesspoint Path'),
+ )
def handle(self, *args, **options):
+ print options, args
logging.basicConfig(level=logging.DEBUG)
thread.start_new_thread(self.start_webserver, ())
if settings.CLOCK_HARDWARE.lower() == "ezchronos":
+ self.msp_ap = options['msp_ap'] or settings.EZ_SERIAL
self.ez_chronos(*args, **options)
def ez_chronos(self, *args, **options):
while True:
try:
- ser = serial.Serial(settings.EZ_SERIAL, 115200,timeout=1)
+ ser = serial.Serial(self.msp_ap, 115200,timeout=1)
except serial.serialutil.SerialException:
- logging.error("Can't open console %s" %settings.EZ_SERIAL)
+ logging.error("Can't open console %s" %self.msp_ap)
time.sleep(5)
continue
@@ -40,7 +49,7 @@ def ez_chronos(self, *args, **options):
pv.reset()
pv.start_ap()
try:
- logging.info("OpenChronos interface started on %s" %settings.EZ_SERIAL)
+ logging.info("OpenChronos interface started on %s" %self.msp_ap)
pv.loop_smpl_get()
except KeyboardInterrupt:
pv.reset()
View
107 db/models.py
@@ -5,6 +5,7 @@
import struct
import logging
import datetime
+import time
# Create your models here.
DETECTOR_TYPES = (
@@ -17,15 +18,20 @@
(2, "One REM"),
)
+RF_ID_BIT_LENGHT = 3
class Detector(models.Model):
"""
Device which messures the sleep state
"""
name = models.CharField("Name", max_length=100, null=False)
typ = models.IntegerField("Type", choices=DETECTOR_TYPES)
+ ident = models.CharField("Identifier", null=True, max_length=100, unique=True, db_index=True)
default_user = models.ForeignKey(User, null=True)
+ def __repr__(self):
+ return '<Detector %s >' %self.ident
+
class WakeupTime(models.Model):
"""
User saved wakeup times for shortcuts
@@ -37,6 +43,27 @@ class WakeupTime(models.Model):
session_typ = models.IntegerField("Type", default=0, choices=SESSION_TYPES)
wakeup = models.TimeField("Wakeup", null=False)
+
+class SessionManager(models.Manager):
+ def get_active_session(self, rf_id):
+ now = datetime.datetime.now()
+ start = now - datetime.timedelta(seconds=settings.CLOCK_SESSION_TIMEOUT)
+ return self.get(stop__gt=start, closed=False, rf_id=rf_id)
+
+ def get_active_sessions(self, **kwargs):
+ now = datetime.datetime.now()
+ start = now - datetime.timedelta(seconds=settings.CLOCK_SESSION_TIMEOUT)
+ 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)))
+ 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)
+ for x in id_s:
+ if x not in actives:
+ return x
+
class Session(models.Model):
"""
One Sleep Session
@@ -49,6 +76,10 @@ class Session(models.Model):
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)
+ closed = models.BooleanField("Session has ended", default=False)
+
+ objects = SessionManager()
# Do we need this ?
#alone = models.BooleanField("Alone", null=True, default=True, help_text="Sleeping with someone else in the bed")
@@ -108,6 +139,12 @@ class LearnData(models.Model):
learned = models.BooleanField(default=False)
+SIMPLICITI_PHASE_CLOCK_START_RESPONSE = 0x54
+
+logging.log_level = logging.DEBUG
+
+RF_ID_MASK = 7<<5
+COUNTER_MASK = 31
class DBWriter(ez_chronos.CommandDispatcher):
@@ -137,28 +174,76 @@ def smpl_0x22(self, data):
#phase clock
def smpl_0x03(self, data):
+ """
+ Receive sleep data
+ """
# acceleration data
timeout=datetime.timedelta(seconds=settings.CLOCK_SESSION_TIMEOUT)
now = datetime.datetime.now()
#FIXME: detect device
device = 1
- # autostart a new session if there where no messages in timeout
- if not device in self.last_msg or not device in self.session or\
- now > self.last_msg[device]+timeout:
- #FIXME add device & user
- self.session[device] = session = Session(start=now, stop=now)
- session.save()
- logging.debug("start new session: %s @ %s" %(session.id, session.start))
- else:
- session = self.session[device]
self.last_msg[device] = now
mdata = self.get_smpl_data(data)
var = struct.unpack('H', mdata[:2])[0]
- counter = ord(mdata[2])
- logging.debug("%s %3d %6d %s" %(session.id, counter, var, "#"*max(min((var/500), 80),1)))
+ counter = ord(mdata[2])&COUNTER_MASK
+ rf_id = (ord(mdata[2])&RF_ID_MASK)>>5
+
+ try:
+ session = Session.objects.get_active_session(rf_id)
+ except Session.DoesNotExist:
+ logging.warning("Session id sent which is not active. Creating a new Session")
+ session = Session(start=now, stop=now, rf_id=rf_id)
+ session.save()
+
+ logging.debug("%s S:%2d C:%2d %6d %s" %(session.id, rf_id, counter, var, "#"*max(min((var/500), 80),1)))
entry = Entry(value=var, counter=counter, session=session)
session.stop = now
session.save()
entry.save()
+
+ def smpl_0x04(self, data):
+ """
+ Start new session
+ """
+ print "04", repr(data)
+ mdata = self.get_smpl_data(data)
+ ident = "0x%02x%02x" %(ord(mdata[0]), ord(mdata[1]))
+ device, created = Detector.objects.get_or_create(ident=ident, defaults={"name": "eZ430 OpenChronos",
+ "ident": ident,
+ "typ": DETECTOR_TYPES[0][0],
+ })
+ if created:
+ device.save()
+
+ logging.info("New Session Request from: %s" %(device.ident))
+
+ sessions = Session.objects.get_active_sessions(detector=device)
+
+ now = datetime.datetime.now()
+ delta = datetime.timedelta(seconds=10)
+ active_session = None
+ for session in sessions:
+ if session.start > now-delta:
+ # we have an active session running
+ active_session = session
+
+ 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
+ active_session = Session(start=now, stop=now, detector=device, user=device.default_user,
+ rf_id=rf_id, typ=typ)
+ active_session.save()
+
+ logging.info("Send Session ID: %s" %(active_session.rf_id))
+
+ data = [SIMPLICITI_PHASE_CLOCK_START_RESPONSE, active_session.rf_id]
+ self.send_smpl_data(data)
+ # we shall not do anything until the clock reads out the
+ # session data
+ time.sleep(0.030)
+
+
View
0 external/__init__.py
No changes.
View
56 tools/ez_chronos.py
@@ -2,6 +2,13 @@
import array
import sys
import struct
+import logging
+from time import sleep
+
+def msleep(msec):
+ sleep(1.0/(1000*msec))
+
+log = logging.getLogger("ez_chronos")
# // Command codes
BM_GET_STATUS = 0x00
@@ -66,6 +73,11 @@
HW_WBSL_STOPPED = 0x0C
HW_WBSL_LINK_TIMEOUT = 0x0D
+BM_SYNC_DATA_LENGTH = 19
+
+SYNC_AP_CMD_NOOP = 0x01
+SYNC_DATA_SENT = 0x55
+
class Simpliciti(object):
"""
Connection class for interaction with a CC1111 dongle
@@ -81,8 +93,20 @@ def __init__(self, stream):
self.rbuffer = ""
self.autosync = True
self.last_cmd = ()
+ self._nullread()
+ self.debug = False
#def
+ def _nullread(self):
+ self.stream.read()
+
+ @staticmethod
+ def dstr(data):
+ rv = ""
+ for x in data:
+ rv += "\\x%02x" %ord(x)
+ return rv
+
def close(self):
self.stream.close()
self.stream = None
@@ -114,24 +138,30 @@ def send(self, cmd_, data=None):
else:
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':
+ 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])
def read(self, ln=None):
offset = 0
self.rbuffer += self.stream.read(ln or self.last_cmd[1])
if len(self.rbuffer) < 2:
- raise ValueError, "Not enough bytes returned"
+ raise ValueError, "Not enough bytes returned. Buffer: %s" %len(self.rbuffer)
if ord(self.rbuffer[0]) != 0xFF:
if not self.autosync:
raise Warning, "Desync detected"
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 \
ord(self.rbuffer[i+2]) == self.last_cmd[1]:
offset = i
@@ -152,6 +182,7 @@ def send_read(self, cmd_, data=None):
### common commands
def reset(self):
+ self.stream.read()
self.send_read(BM_RESET)
def start_ap(self):
@@ -160,6 +191,27 @@ def start_ap(self):
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"
+ return True
+
+ status = self.status()
+ print "status", repr(status)
+
+
+ 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:]
@@ -189,7 +241,7 @@ def cmd(self, cmd_, data):
def default(self, data):
print "default"
if self.debug:
- print "unhandled", repr(data)
+ print "unhandled", self.dstr(data)
def get_status(self, data):
if self.debug:
View
2 uberclockd
@@ -12,4 +12,4 @@ except ImportError:
sys.exit(1)
if __name__ == "__main__":
- execute_manager(settings, argv=["uberclockd", "uberclockd"])
+ execute_manager(settings, argv=["uberclockd", "uberclockd"] + sys.argv[1:])

0 comments on commit d191ec2

Please sign in to comment.