Skip to content

Commit

Permalink
Merge pull request #69 from aj-ptw/1.0.0
Browse files Browse the repository at this point in the history
1.0.0
  • Loading branch information
AJ Keller authored May 4, 2018
2 parents 04ea38f + a6f5d75 commit 8661390
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 10 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@
* Refactored library for pip
* Moved plugins folder into openbci dir so plugins can be imported when installed with pip


## Beta 0

* Adds high speed for Daisy over WiFi - now all boards are supported!

## Alpha 1

* Adds high speed for Ganglion over WiFi

## Alpha 0

* Adds high speed for Cyton over WiFi

# v0.1

## dev
Expand Down
53 changes: 53 additions & 0 deletions openbci/utils/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,57 @@ def transform_raw_data_packet_to_sample(self, raw_data):

return sample

def make_daisy_sample_object_wifi(self, lower_sample_object, upper_sample_object):
"""
/**
* @description Used to make one sample object from two sample objects. The sample number of the new daisy sample will
* be the upperSampleObject's sample number divded by 2. This allows us to preserve consecutive sample numbers that
* flip over at 127 instead of 255 for an 8 channel. The daisySampleObject will also have one `channelData` array
* with 16 elements inside it, with the lowerSampleObject in the lower indices and the upperSampleObject in the
* upper set of indices. The auxData from both channels shall be captured in an object called `auxData` which
* contains two arrays referenced by keys `lower` and `upper` for the `lowerSampleObject` and `upperSampleObject`,
* respectively. The timestamps shall be averaged and moved into an object called `timestamp`. Further, the
* un-averaged timestamps from the `lowerSampleObject` and `upperSampleObject` shall be placed into an object called
* `_timestamps` which shall contain two keys `lower` and `upper` which contain the original timestamps for their
* respective sampleObjects.
* @param lowerSampleObject {Object} - Lower 8 channels with odd sample number
* @param upperSampleObject {Object} - Upper 8 channels with even sample number
* @returns {Object} - The new merged daisy sample object
*/
"""
daisy_sample_object = OpenBCISample()

if lower_sample_object.channel_data is not None:
daisy_sample_object.channel_data = lower_sample_object.channel_data + upper_sample_object.channel_data

daisy_sample_object.sample_number = upper_sample_object.sample_number
daisy_sample_object.id = daisy_sample_object.sample_number

daisy_sample_object.aux_data = {
'lower': lower_sample_object.aux_data,
'upper': upper_sample_object.aux_data
}

if lower_sample_object.timestamp:
daisy_sample_object.timestamp = lower_sample_object.timestamp

daisy_sample_object.stop_byte = lower_sample_object.stop_byte

daisy_sample_object._timestamps = {
'lower': lower_sample_object.timestamp,
'upper': upper_sample_object.timestamp
}

if lower_sample_object.accel_data is not None:
if lower_sample_object.accel_data[0] > 0 or lower_sample_object.accel_data[1] > 0 or lower_sample_object.accel_data[2] > 0:
daisy_sample_object.accel_data = lower_sample_object.accel_data
else:
daisy_sample_object.accel_data = upper_sample_object.accel_data

daisy_sample_object.valid = True

return daisy_sample_object

"""
/**
* @description Used transform raw data packets into fully qualified packets
Expand Down Expand Up @@ -279,4 +330,6 @@ def __init__(self,
self.sample_number = sample_number
self.start_byte = start_byte
self.stop_byte = stop_byte
self.timestamp = 0
self._timestamps = {}
self.valid = valid
51 changes: 42 additions & 9 deletions openbci/wifi.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def handle_sample(sample):
import requests
import xmltodict

from openbci.utils import k, ParseRaw, ssdp
from openbci.utils import k, ParseRaw, OpenBCISample, ssdp

SAMPLE_RATE = 0 # Hz

Expand Down Expand Up @@ -56,14 +56,17 @@ class OpenBCIWiFi(object):
"""

def __init__(self, ip_address=None, shield_name=None, sample_rate=None, log=True, timeout=3,
max_packets_to_skip=20, latency=10000, high_speed=True, ssdp_attempts=5):
max_packets_to_skip=20, latency=10000, high_speed=True, ssdp_attempts=5,
num_channels=8):
# these one are used
self.daisy = False
self.high_speed = high_speed
self.impedance = False
self.ip_address = ip_address
self.latency = latency
self.log = log # print_incoming_text needs log
self.max_packets_to_skip = max_packets_to_skip
self.num_channles = num_channels
self.sample_rate = sample_rate
self.shield_name = shield_name
self.ssdp_attempts = ssdp_attempts
Expand Down Expand Up @@ -157,8 +160,14 @@ def connect(self):
gains = None
if self.board_type == k.BOARD_CYTON:
gains = [24, 24, 24, 24, 24, 24, 24, 24]
self.daisy = False
elif self.board_type == k.BOARD_DAISY:
gains = [24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24]
self.daisy = True
elif self.board_type == k.BOARD_GANGLION:
gains = [51, 51, 51, 51]
self.daisy = False
self.local_wifi_server.set_daisy(daisy=self.daisy)
self.local_wifi_server.set_parser(ParseRaw(gains=gains, board_type=self.board_type))

if self.high_speed:
Expand Down Expand Up @@ -444,7 +453,7 @@ def warn(self, text):
# log how many packets where sent succesfully in between warnings
if self.log_packet_count:
logging.info('Data packets received:' + str(self.log_packet_count))
self.log_packet_count = 0;
self.log_packet_count = 0
logging.warning(text)
print("Warning: %s" % text)

Expand Down Expand Up @@ -472,11 +481,14 @@ def reconnect(self):


class WiFiShieldHandler(asyncore.dispatcher_with_send):
def __init__(self, sock, callback=None, high_speed=True, parser=None):
def __init__(self, sock, callback=None, high_speed=True,
parser=None, daisy=False):
asyncore.dispatcher_with_send.__init__(self, sock)

self.high_speed = high_speed
self.callback = callback
self.daisy = daisy
self.high_speed = high_speed
self.last_odd_sample = OpenBCISample()
self.parser = parser if parser is not None else ParseRaw(gains=[24, 24, 24, 24, 24, 24, 24, 24])

def handle_read(self):
Expand All @@ -490,8 +502,22 @@ def handle_read(self):
samples = self.parser.transform_raw_data_packets_to_sample(raw_data_packets=raw_data_packets)

for sample in samples:
if self.callback is not None:
self.callback(sample)
# if a daisy module is attached, wait to concatenate two samples (main board + daisy)
# before passing it to callback
if self.daisy:
# odd sample: daisy sample, save for later
if ~sample.sample_number % 2:
self.last_odd_sample = sample
# even sample: concatenate and send if last sample was the fist part, otherwise drop the packet
elif sample.sample_number - 1 == self.last_odd_sample.sample_number:
# the aux data will be the average between the two samples, as the channel
# samples themselves have been averaged by the board
daisy_sample = self.parser.make_daisy_sample_object_wifi(self.last_odd_sample, sample)
if self.callback is not None:
self.callback(daisy_sample)
else:
if self.callback is not None:
self.callback(sample)

else:
try:
Expand All @@ -516,11 +542,12 @@ def handle_read(self):

class WiFiShieldServer(asyncore.dispatcher):

def __init__(self, host, port, callback=None, gains=None, high_speed=True):
def __init__(self, host, port, callback=None, gains=None, high_speed=True, daisy=False):
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()
self.bind((host, port))
self.daisy = daisy
self.listen(5)
self.callback = None
self.handler = None
Expand All @@ -532,13 +559,19 @@ def handle_accept(self):
if pair is not None:
sock, addr = pair
print 'Incoming connection from %s' % repr(addr)
self.handler = WiFiShieldHandler(sock, self.callback, high_speed=self.high_speed, parser=self.parser)
self.handler = WiFiShieldHandler(sock, self.callback, high_speed=self.high_speed,
parser=self.parser, daisy=self.daisy)

def set_callback(self, callback):
self.callback = callback
if self.handler is not None:
self.handler.callback = callback

def set_daisy(self, daisy):
self.daisy = daisy
if self.handler is not None:
self.handler.daisy = daisy

def set_gains(self, gains):
self.parser.set_ads1299_scale_factors(gains)

Expand Down
3 changes: 2 additions & 1 deletion scripts/stream_data_wifi.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@


def printData(sample):
print(sample)
print(sample.sample_number)
print(sample.channel_data)


if __name__ == '__main__':
Expand Down
65 changes: 65 additions & 0 deletions tests/test_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import mock

from openbci.utils import (k,
OpenBCISample,
ParseRaw,
sample_packet,
sample_packet_standard_raw_aux,
Expand Down Expand Up @@ -245,6 +246,70 @@ def test_transform_raw_data_packets_to_sample(self):
for i in range(len(samples)):
self.assertEqual(samples[i].sample_number, i)

def test_make_daisy_sample_object_wifi(self):
parser = ParseRaw(gains=[24, 24, 24, 24, 24, 24, 24, 24])
# Make the lower sample(channels 1 - 8)
lower_sample_object = OpenBCISample(sample_number=1)
lower_sample_object.channel_data = [1, 2, 3, 4, 5, 6, 7, 8]
lower_sample_object.aux_data = [0, 1, 2]
lower_sample_object.timestamp = 4
lower_sample_object.accel_data = [0, 0, 0]
# Make the upper sample(channels 9 - 16)
upper_sample_object = OpenBCISample(sample_number=2)
upper_sample_object.channel_data = [9, 10, 11, 12, 13, 14, 15, 16]
upper_sample_object.accel_data = [0, 1, 2]
upper_sample_object.aux_data = [3, 4, 5]
upper_sample_object.timestamp = 8

daisy_sample_object = parser.make_daisy_sample_object_wifi(lower_sample_object, upper_sample_object);

# should have valid object true
self.assertTrue(daisy_sample_object.valid)

# should make a channelData array 16 elements long
self.assertEqual(len(daisy_sample_object.channel_data), k.NUMBER_OF_CHANNELS_DAISY)

# should make a channelData array with lower array in front of upper array
for i in range(16):
self.assertEqual(daisy_sample_object.channel_data[i], i + 1)

self.assertEqual(daisy_sample_object.id, daisy_sample_object.sample_number)
self.assertEqual(daisy_sample_object.sample_number, daisy_sample_object.sample_number)

# should put the aux packets in an object
self.assertIsNotNone(daisy_sample_object.aux_data['lower'])
self.assertIsNotNone(daisy_sample_object.aux_data['upper'])

# should put the aux packets in an object in the right order
for i in range(3):
self.assertEqual(daisy_sample_object.aux_data['lower'][i], i)
self.assertEqual(daisy_sample_object.aux_data['upper'][i], i + 3)

# should take the lower timestamp
self.assertEqual(daisy_sample_object.timestamp, lower_sample_object.timestamp)

# should take the lower stopByte
self.assertEqual(daisy_sample_object.stop_byte, lower_sample_object.stop_byte)

# should place the old timestamps in an object
self.assertEqual(daisy_sample_object._timestamps['lower'], lower_sample_object.timestamp)
self.assertEqual(daisy_sample_object._timestamps['upper'], upper_sample_object.timestamp)

# should store an accelerometer value if present
self.assertIsNotNone(daisy_sample_object.accel_data)
self.assertListEqual(daisy_sample_object.accel_data, [0, 1, 2])

lower_sample = OpenBCISample(sample_number=1)
lower_sample.accel_data = [0, 1, 2]
upper_sample = OpenBCISample(sample_number=2)
upper_sample.accel_data = [0, 0, 0]

# Call the function under test
daisy_sample = parser.make_daisy_sample_object_wifi(lower_sample, upper_sample)

self.assertIsNotNone(daisy_sample.accel_data)
self.assertListEqual(daisy_sample.accel_data, [0, 1, 2])


if __name__ == '__main__':
main()

0 comments on commit 8661390

Please sign in to comment.