Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ShimmerBluetooth/ShimmerBluetooth.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
3AD7C6032BD23238000E5455 /* SensorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AD7C6022BD23238000E5455 /* SensorTest.swift */; };
3AEB12F92BCE6F7E00B5F6F1 /* Shimmer3SpeedTestProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AEB12F82BCE6F7E00B5F6F1 /* Shimmer3SpeedTestProtocol.swift */; };
3AED20692B0AF79E0066A0F8 /* TimeSensor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AED20682B0AF79E0066A0F8 /* TimeSensor.swift */; };
DE10230B2C0100000000001B /* TimestampUnwrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE10230A2C0100000000001A /* TimestampUnwrap.swift */; };
DE10230D2C0100000000001D /* TimestampUnwrapVectorsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE10230C2C0100000000001C /* TimestampUnwrapVectorsTest.swift */; };
3AED206B2B0AFCD10066A0F8 /* ShimmerUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AED206A2B0AFCD10066A0F8 /* ShimmerUtilities.swift */; };
3AED206D2B0B10960066A0F8 /* ShimmerUtilitiesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AED206C2B0B10960066A0F8 /* ShimmerUtilitiesTest.swift */; };
3AED20712B0C4BA60066A0F8 /* MagSensor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AED20702B0C4BA60066A0F8 /* MagSensor.swift */; };
Expand Down Expand Up @@ -83,6 +85,8 @@
3AD7C6022BD23238000E5455 /* SensorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorTest.swift; sourceTree = "<group>"; };
3AEB12F82BCE6F7E00B5F6F1 /* Shimmer3SpeedTestProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shimmer3SpeedTestProtocol.swift; sourceTree = "<group>"; };
3AED20682B0AF79E0066A0F8 /* TimeSensor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeSensor.swift; sourceTree = "<group>"; };
DE10230A2C0100000000001A /* TimestampUnwrap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimestampUnwrap.swift; sourceTree = "<group>"; };
DE10230C2C0100000000001C /* TimestampUnwrapVectorsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimestampUnwrapVectorsTest.swift; sourceTree = "<group>"; };
3AED206A2B0AFCD10066A0F8 /* ShimmerUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShimmerUtilities.swift; sourceTree = "<group>"; };
3AED206C2B0B10960066A0F8 /* ShimmerUtilitiesTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShimmerUtilitiesTest.swift; sourceTree = "<group>"; };
3AED20702B0C4BA60066A0F8 /* MagSensor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MagSensor.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -163,6 +167,7 @@
3AED20702B0C4BA60066A0F8 /* MagSensor.swift */,
3AB320E92B07092C003D94F8 /* WRAccelSensor.swift */,
3AED20682B0AF79E0066A0F8 /* TimeSensor.swift */,
DE10230A2C0100000000001A /* TimestampUnwrap.swift */,
3AB320EB2B0709DA003D94F8 /* ObjectCluster.swift */,
3AED206A2B0AFCD10066A0F8 /* ShimmerUtilities.swift */,
92B106792B7DAFBC00AB9952 /* EXGSensor.swift */,
Expand All @@ -178,6 +183,7 @@
3A780C942AE12BEB00EAF050 /* ShimmerBluetoothTests.swift */,
3AED206C2B0B10960066A0F8 /* ShimmerUtilitiesTest.swift */,
3AD7C6022BD23238000E5455 /* SensorTest.swift */,
DE10230C2C0100000000001C /* TimestampUnwrapVectorsTest.swift */,
);
path = ShimmerBluetoothTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -314,6 +320,7 @@
3A77128D2AE177A4006213A8 /* ShimmerDevice.swift in Sources */,
3AA359152AF37943008AD334 /* ConfigByteLayoutShimmer3.swift in Sources */,
3AED20692B0AF79E0066A0F8 /* TimeSensor.swift in Sources */,
DE10230B2C0100000000001B /* TimestampUnwrap.swift in Sources */,
9299E50E2B85054E001EEFE0 /* PressureTempSensor.swift in Sources */,
3A0B4B652B0DDE0000786295 /* GSRSensor.swift in Sources */,
3AB320EC2B0709DA003D94F8 /* ObjectCluster.swift in Sources */,
Expand All @@ -339,6 +346,7 @@
3AED206D2B0B10960066A0F8 /* ShimmerUtilitiesTest.swift in Sources */,
3A780C952AE12BEB00EAF050 /* ShimmerBluetoothTests.swift in Sources */,
3AD7C6032BD23238000E5455 /* SensorTest.swift in Sources */,
DE10230D2C0100000000001D /* TimestampUnwrapVectorsTest.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
5 changes: 5 additions & 0 deletions ShimmerBluetooth/ShimmerBluetooth/ObjectCluster.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public class ObjectCluster {
public var SignalData : [Double] = []
let Seperator = "_"
public var PacketReceptionRate = -1
/// False when the packet carried no usable timestamp - see
/// `TimestampUnwrap`. The sensor values on this cluster are real; only its
/// time is missing, and the Time Stamp signals repeat the previous
/// packet's. Drop the cluster if you need a true time axis.
public var timestampValid = true
public func addData(sensorName:String,formatName:String,unitName:String,value:Double){
let newName = [sensorName,Seperator,formatName,Seperator,unitName].joined()
SignalNames.append(newName)
Expand Down
10 changes: 10 additions & 0 deletions ShimmerBluetooth/ShimmerBluetooth/Shimmer3Protocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,16 @@ public class Shimmer3Protocol : NSObject, ShimmerProtocol {
packetSize = TimeStampPacketByteSize // Time stamp
timeSensor.packetIndexTimeStamp = 0
timeSensor.sensorEnabled = true
/* Both callers set CurrentSamplingRate from the inquiry response just
above, and the rate is what sizes the reorder window: eight sample
periods is what separates a pair of packets delivered out of order
from a dropout that happens to span the counter's roll-over point.
A rate that arrives as a division by zero is +Infinity here, and
reorderWindowTicks answers 0 for it - the branch off is the only
safe reading of an unknown rate. */
timeSensor.reorderWindowTicks = TimestampUnwrap.reorderWindowTicks(
samplingRateHz: CurrentSamplingRate,
maxTicks: timeSensor.TimeStampPacketRawMaxTicks)
//}

var enabledSensors = Int(0)
Expand Down
60 changes: 53 additions & 7 deletions ShimmerBluetooth/ShimmerBluetooth/TimeSensor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,70 @@ public class TimeSensor : Sensor , SensorProcessing{
public func processData(sensorPacket: [UInt8], objectCluster: ObjectCluster) -> ObjectCluster {
let desiredRange = Array(sensorPacket[packetIndexTimeStamp..<packetIndexTimeStamp+3])
let rawData = ShimmerUtilities.parseSensorData(sensorData: desiredRange, dataType: SensorDataType.u24)
/* The unwrap runs whether or not calibration is on. A record the
firmware never stamped has no time either way, so `timestampValid`
has to mean something in both modes - and a timeline that only
advanced while someone was watching would jump by however much was
missed the moment calibration was switched on mid-stream. */
let calData = calibrateTimeStamp(timeStamp: Double(rawData!))
if (calibrationEnabled){
var calData = calibrateTimeStamp(timeStamp: Double(rawData!))
objectCluster.addData(sensorName: TimeSensor.TimeStamp, formatName: SensorFormats.Calibrated.rawValue, unitName: SensorUnits.milliSeconds.rawValue, value: calData)
print("TimeStamp (mS) : \(calData)")
}
/* A record the firmware never stamped keeps its sensor values and
loses its time. Both Time Stamp signals are still added - the raw
one is what the packet said, and the calibrated one repeats the
previous packet - so a consumer reading by signal name is
unaffected, and one that needs a true time axis has
`timestampValid` to filter on. */
objectCluster.timestampValid = !lastRecordRejected
objectCluster.addData(sensorName: TimeSensor.TimeStamp, formatName: SensorFormats.Raw.rawValue, unitName: SensorUnits.noUnit.rawValue, value: Double(rawData!))
return objectCluster
}

var LastReceivedTimeStamp:Double = 0
var TimeStampPacketRawMaxValue:Double = 16777216;
var TimeStampPacketRawMaxTicks:Int = TimestampUnwrap.ticksMax3Byte
var CurrentTimeStampCycle:Double = 0

/// False only before the first sample of a stream. The pair above cannot say
/// it on their own: `(0, 0)` is the reset state and also a state the unwrap
/// can reach, when a reordered packet lands exactly on the counter's origin.
/// A fresh `TimeSensor` is what starts a stream here - `Shimmer3Protocol`
/// builds one in `createSensors()` - so there is no separate reset to keep in
/// step with this.
var hasPreviousTimeStamp = false

/// How far behind its predecessor a value may be and still be read as a
/// reordered packet, in ticks. `0` disables the branch, which is what an
/// unknown sampling rate has to mean; `Shimmer3Protocol` sets it from the
/// inquiry response. See `TimestampUnwrap.reorderWindowTicks`.
public var reorderWindowTicks: Double = 0.0

/// True when the packet just processed carried no usable timestamp.
public private(set) var lastRecordRejected = false

/// Unwrap one packet's counter value and convert it to milliseconds.
///
/// The rule is `TimestampUnwrap`, shared with every other Shimmer host API
/// and checked against the same conformance vectors. What used to be here
/// was "the value went down, so it wrapped", which charges a whole modulo -
/// 512 seconds, permanently - for a reordered packet, a duplicated packet
/// or a record the firmware never stamped.
///
/// A rejected record leaves every piece of state where it is, so the next
/// real sample is compared against the last value the firmware actually
/// stamped and reads as the ordinary step forward it is.
func calibrateTimeStamp(timeStamp: Double) -> Double {
if (LastReceivedTimeStamp > (timeStamp + (TimeStampPacketRawMaxValue * CurrentTimeStampCycle)))
{
CurrentTimeStampCycle = CurrentTimeStampCycle + 1;
}
LastReceivedTimeStamp = (timeStamp + (TimeStampPacketRawMaxValue * CurrentTimeStampCycle));
let result = TimestampUnwrap.unwrap(rawTicks: timeStamp,
lastUnwrapped: LastReceivedTimeStamp,
cycle: CurrentTimeStampCycle,
maxTicks: TimeStampPacketRawMaxTicks,
reorderWindowTicks: reorderWindowTicks,
hasPreviousSample: hasPreviousTimeStamp)
hasPreviousTimeStamp = true
lastRecordRejected = result.rejected
LastReceivedTimeStamp = result.unwrappedTicks
CurrentTimeStampCycle = result.cycle

let clockConstant:Double = 32768;
let calibratedTimeStamp = LastReceivedTimeStamp / clockConstant * 1000; // to convert into mS
Expand Down
Loading
Loading