-
-
Save mayuki/fd2e3e71ec9a29973f1d8a17e33341f9 to your computer and use it in GitHub Desktop.
CC2650 SensorTagを使う
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
async Task Main() | |
{ | |
ReactivePropertyScheduler.SetDefault(new SynchronizationContextScheduler(new SynchronizationContext())); | |
var bleDevice = await BluetoothLEDevice.FromBluetoothAddressAsync(0); /* 何らかの方法で特定するかDeviceInformationで探し出す*/ | |
var sensor = new Cc2650ReactiveSensor(bleDevice); | |
var disposables = new CompositeDisposable(); | |
var temperature = new Util.ProgressBar("Temperature").Dump(); | |
var humidity = new Util.ProgressBar("Humidity").Dump(); | |
var barometricPressure = new Util.ProgressBar("BarometricPressure").Dump(); | |
sensor.Humidity | |
//.Sample(TimeSpan.FromSeconds(60 * 5)) | |
.Subscribe(async x => | |
{ | |
temperature.Caption = $"Temperature: {x.Item1}%"; | |
temperature.Percent = (int)x.Item1; | |
humidity.Caption = $"Humidity: {x.Item2}%"; | |
humidity.Percent = (int)x.Item2; | |
x.Dump(); | |
}, ex => ex.Dump()) | |
.AddTo(disposables); | |
sensor.BarometricPressure | |
//.Sample(TimeSpan.FromSeconds(60 * 5)) | |
.Subscribe(async x => | |
{ | |
barometricPressure.Caption = $"BarometricPressure: {x}hPa"; | |
barometricPressure.Percent = (int)x; | |
}, ex => ex.Dump()) | |
.AddTo(disposables); | |
Console.ReadLine(); | |
disposables.Dispose(); | |
} | |
class M2xClient | |
{ | |
private string _apiKey; | |
private string _deviceId; | |
private HttpClient _client; | |
private const string EndpointUrl = "https://api-m2x.att.com/v2/devices/{0}/streams/{1}/value"; | |
public M2xClient(string apiKey, string deviceId) | |
{ | |
this._apiKey = apiKey; | |
this._deviceId = deviceId; | |
this._client = new HttpClient(); | |
this._client.DefaultRequestHeaders.Add("X-M2X-KEY", this._apiKey); | |
} | |
public async Task SendAsync(string streamId, double value) | |
{ | |
await this._client.PutAsync( | |
new Uri(String.Format(EndpointUrl, this._deviceId, streamId)), | |
new HttpStringContent( | |
$"{{ \"value\": \"{value}\" }}", Windows.Storage.Streams.UnicodeEncoding.Utf8, "application/json" | |
)) | |
.AsTask() | |
.ConfigureAwait(false); | |
} | |
} | |
// http://processors.wiki.ti.com/index.php/CC2650_SensorTag_User's_Guide | |
class Cc2650ReactiveSensor | |
{ | |
private BluetoothLEDevice _device; | |
public IReadOnlyReactiveProperty<Tuple<double, double>> IrTemperature { get; } | |
public IReadOnlyReactiveProperty<Tuple<double, double>> Humidity { get; } | |
public IReadOnlyReactiveProperty<double> Optical { get; } | |
public IReadOnlyReactiveProperty<double> BarometricPressure { get; } | |
public IReadOnlyReactiveProperty<MovementData> Movement { get; } | |
public Cc2650ReactiveSensor(BluetoothLEDevice device) | |
{ | |
this._device = device; | |
this.Movement = CreateObservable( | |
new Guid("f000aa80-0451-4000-b000-000000000000"), | |
new Guid("f000aa81-0451-4000-b000-000000000000"), | |
new Guid("f000aa82-0451-4000-b000-000000000000"), | |
x => | |
{ | |
return MovementData.Create(x, MovementConfiguration.AllMovement); | |
}, | |
BitConverter.GetBytes((short)MovementConfiguration.AllMovement), | |
BitConverter.GetBytes((short)MovementConfiguration.None)) | |
.ToReadOnlyReactiveProperty(); | |
this.IrTemperature = CreateObservable( | |
new Guid("f000aa00-0451-4000-b000-000000000000"), | |
new Guid("f000aa01-0451-4000-b000-000000000000"), | |
new Guid("f000aa02-0451-4000-b000-000000000000"), | |
x => | |
{ | |
var ScaleLsb = 0.03125; | |
var targetTemperature = (BitConverter.ToUInt16(x, 0) >> 2) * ScaleLsb; | |
var ambientTemperature = (BitConverter.ToUInt16(x, 2) >> 2) * ScaleLsb; | |
return Tuple.Create(ambientTemperature, targetTemperature); | |
}) | |
.ToReadOnlyReactiveProperty(); | |
this.Humidity = CreateObservable( | |
new Guid("f000aa20-0451-4000-b000-000000000000"), | |
new Guid("f000aa21-0451-4000-b000-000000000000"), | |
new Guid("f000aa22-0451-4000-b000-000000000000"), | |
x => | |
{ | |
var rawTemp = BitConverter.ToUInt16(x, 0); | |
var rawHum = BitConverter.ToUInt16(x, 2); | |
var temp = ((double)rawTemp / 65536) * 165 - 40; | |
var hum = ((double)rawHum / 65536) * 100; | |
return Tuple.Create(temp, hum); | |
}) | |
.ToReadOnlyReactiveProperty(); | |
this.Optical = CreateObservable( | |
new Guid("f000aa70-0451-4000-b000-000000000000"), | |
new Guid("f000aa71-0451-4000-b000-000000000000"), | |
new Guid("f000aa72-0451-4000-b000-000000000000"), | |
x => | |
{ | |
var rawData = BitConverter.ToUInt16(x, 0); | |
var m = rawData & 0x0FFF; | |
var e = (rawData & 0xF000) >> 12; | |
return m * (0.01 * Math.Pow(2.0, e)); | |
}) | |
.ToReadOnlyReactiveProperty(); | |
this.BarometricPressure = CreateObservable( | |
new Guid("f000aa40-0451-4000-b000-000000000000"), | |
new Guid("f000aa41-0451-4000-b000-000000000000"), | |
new Guid("f000aa42-0451-4000-b000-000000000000"), | |
x => | |
{ | |
return BitConverter.ToUInt32(new byte[] { x[3], x[4], x[5], 0 }, 0) / 100d; | |
}) | |
.ToReadOnlyReactiveProperty(); | |
} | |
private IObservable<TResult> CreateObservable<TResult>(Guid service, Guid data, Guid config, Func<byte[], TResult> convert, byte[] configurationEnable = null, byte[] configurationDisable = null) | |
{ | |
return Observable.Defer(() => | |
{ | |
var gattService = _device.GetGattService(service); | |
var charaService = gattService.GetCharacteristics(data)[0]; | |
var charaConfig = gattService.GetCharacteristics(config)[0]; | |
return charaService | |
.ValueChangedAsObservable(charaConfig, configurationEnable ?? new byte[] { 1 }, configurationDisable ?? new byte[] { 0 }) | |
.Select(x => x.EventArgs.CharacteristicValue.ToArray()) | |
.Select(convert) | |
.Publish() | |
.RefCount(); | |
}); | |
} | |
} | |
[Flags] | |
public enum MovementConfiguration : short | |
{ | |
None = 0, | |
GyroscopeZ = 1 << 0, | |
GyroscopeY = 1 << 1, | |
GyroscopeX = 1 << 2, | |
AccelerometerZ = 1 << 3, | |
AccelerometerY = 1 << 4, | |
AccelerometerX = 1 << 5, | |
Magnetometer = 1 << 6, | |
AllMovement = GyroscopeX | GyroscopeY | GyroscopeZ | AccelerometerX | AccelerometerY | AccelerometerZ | Magnetometer, | |
WakeOnMotion = 1 << 7, | |
AccelerometerRange2G = 0 << 8, | |
AccelerometerRange4G = 1 << 8, | |
AccelerometerRange8G = 1 << 9, | |
AccelerometerRange16G = 1 << 8 & 1 << 9, | |
} | |
public struct MovementData | |
{ | |
public double GyroX { get; set; } | |
public double GyroY { get; set; } | |
public double GyroZ { get; set; } | |
public double AccX { get; set; } | |
public double AccY { get; set; } | |
public double AccZ { get; set; } | |
public double MagX { get; set; } | |
public double MagY { get; set; } | |
public double MagZ { get; set; } | |
public MovementData(double gyroX, double gyroY, double gyroZ, double accX, double accY, double accZ, double magX, double magY, double magZ) | |
{ | |
GyroX = gyroX; | |
GyroY = gyroY; | |
GyroZ = gyroZ; | |
AccX = accX; | |
AccY = accY; | |
AccZ = accZ; | |
MagX = magX; | |
MagY = magY; | |
MagZ = magZ; | |
} | |
public static MovementData Create(byte[] data, MovementConfiguration configuration) | |
{ | |
// Gyro | |
var gyroX = ToGyro(BitConverter.ToInt16(data, 0)); | |
var gyroY = ToGyro(BitConverter.ToInt16(data, 2)); | |
var gyroZ = ToGyro(BitConverter.ToInt16(data, 4)); | |
// Acc | |
var accX = ToAcc(BitConverter.ToInt16(data, 6), configuration); | |
var accY = ToAcc(BitConverter.ToInt16(data, 8), configuration); | |
var accZ = ToAcc(BitConverter.ToInt16(data, 10), configuration); | |
// Mag | |
var magX = ToMag(BitConverter.ToInt16(data, 12)); | |
var magY = ToMag(BitConverter.ToInt16(data, 14)); | |
var magZ = ToMag(BitConverter.ToInt16(data, 16)); | |
return new MovementData(gyroX, gyroY, gyroZ, accX, accY, accZ, magX, magY, magZ); | |
} | |
private static double ToGyro(short data) | |
{ | |
//-- calculate rotation, unit deg/s, range -250, +250 | |
return (data * 1.0) / (65536 / 500); | |
} | |
private static double ToAcc(short data, MovementConfiguration configuration) | |
{ | |
if (configuration.HasFlag(MovementConfiguration.AccelerometerRange16G)) | |
{ | |
//-- calculate acceleration, unit G, range -16, +16 | |
return (data * 1.0) / (32768 / 16); | |
} | |
else if (configuration.HasFlag(MovementConfiguration.AccelerometerRange8G)) | |
{ | |
//-- calculate acceleration, unit G, range -8, +8 | |
return (data * 1.0) / (32768 / 8); | |
} | |
else if (configuration.HasFlag(MovementConfiguration.AccelerometerRange4G)) | |
{ | |
//-- calculate acceleration, unit G, range -4, +4 | |
return (data * 1.0) / (32768 / 4); | |
} | |
else | |
{ | |
//-- calculate acceleration, unit G, range -2, +2 | |
return (data * 1.0) / (32768 / 2); | |
} | |
} | |
private static double ToMag(short data) | |
{ | |
//-- calculate magnetism, unit uT, range +-4900 | |
return data * 1.0; | |
} | |
} | |
static class GattCharacteristicExtension | |
{ | |
public static IObservable<EventPattern<GattValueChangedEventArgs>> ValueChangedAsObservable(this GattCharacteristic characteristic, GattCharacteristic charConfig, byte[] configurationEnable, byte[] configurationDisable) | |
{ | |
return Observable.FromEventPattern<TypedEventHandler<GattCharacteristic, GattValueChangedEventArgs>, GattValueChangedEventArgs>( | |
async h => | |
{ | |
await characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify); | |
await charConfig.WriteValueAsync(configurationEnable); | |
characteristic.ValueChanged += h; | |
}, | |
async h => | |
{ | |
characteristic.ValueChanged -= h; | |
await characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.None); | |
await charConfig.WriteValueAsync(configurationDisable); | |
} | |
); | |
} | |
public static IAsyncOperation<GattCommunicationStatus> WriteValueAsync(this GattCharacteristic characteristic, byte value) | |
{ | |
var dataWriter = new DataWriter(); | |
dataWriter.WriteByte(value); | |
var buffer = dataWriter.DetachBuffer(); | |
return characteristic.WriteValueAsync(buffer); | |
} | |
public static IAsyncOperation<GattCommunicationStatus> WriteValueAsync(this GattCharacteristic characteristic, byte[] value) | |
{ | |
var dataWriter = new DataWriter(); | |
dataWriter.WriteBytes(value); | |
var buffer = dataWriter.DetachBuffer(); | |
return characteristic.WriteValueAsync(buffer); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Query Kind="Program"> | |
<Reference><RuntimeDirectory>\System.Runtime.dll</Reference> | |
<Reference><ProgramFilesX86>\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5\System.Runtime.WindowsRuntime.dll</Reference> | |
<Reference><RuntimeDirectory>\System.Threading.Tasks.dll</Reference> | |
<Reference><ProgramFilesX86>\Windows Kits\10\References\Windows.Foundation.FoundationContract\2.0.0.0\Windows.Foundation.FoundationContract.winmd</Reference> | |
<Reference><ProgramFilesX86>\Windows Kits\10\References\Windows.Foundation.UniversalApiContract\2.0.0.0\Windows.Foundation.UniversalApiContract.winmd</Reference> | |
<Reference><ProgramFilesX86>\Windows Kits\10\UnionMetadata\Facade\Windows.WinMD</Reference> | |
<Reference><RuntimeDirectory>\System.Runtime.InteropServices.WindowsRuntime.dll</Reference> | |
<Reference><RuntimeDirectory>\WPF\WindowsBase.dll</Reference> | |
<Reference><RuntimeDirectory>\System.Xaml.dll</Reference> | |
<Reference><RuntimeDirectory>\Accessibility.dll</Reference> | |
<Reference><RuntimeDirectory>\System.Security.dll</Reference> | |
<Namespace>System.Runtime.InteropServices.WindowsRuntime</Namespace> | |
<Namespace>System.Threading.Tasks</Namespace> | |
<Namespace>Windows.Devices.Bluetooth</Namespace> | |
<Namespace>Windows.Devices.Bluetooth.Advertisement</Namespace> | |
<Namespace>Windows.Devices.Bluetooth.GenericAttributeProfile</Namespace> | |
<Namespace>Windows.Devices.Enumeration</Namespace> | |
<Namespace>Windows.Foundation</Namespace> | |
<Namespace>Windows.Storage.Streams</Namespace> | |
<Namespace>Windows.UI.Core</Namespace> | |
</Query> | |
async Task Main() | |
{ | |
// GATTのUUID (ここではHumidityセンサー) | |
// http://processors.wiki.ti.com/index.php/SensorTag_User_Guide#Gatt_Server | |
// http://processors.wiki.ti.com/images/a/a8/BLE_SensorTag_GATT_Server.pdf | |
var uuidHumidityService = new Guid("f000aa20-0451-4000-b000-000000000000"); | |
var uuidHumidityData = new Guid("f000aa21-0451-4000-b000-000000000000"); | |
var uuidHumidityConfig = new Guid("f000aa22-0451-4000-b000-000000000000"); | |
var uuidHumidityPeriod = new Guid("f000aa23-0451-4000-b000-000000000000"); | |
// サービスを持つデバイスを探す | |
var gattHumidityServices = await DeviceInformation.FindAllAsync( | |
GattDeviceService.GetDeviceSelectorFromUuid(uuidHumidityService), null); | |
// デバイスIDからGattDeviceServiceを取得する | |
var gattHumidityService = await GattDeviceService.FromIdAsync(gattHumidityServices[0].Id); | |
// データと設定のCharacteristicを取得する | |
var charHumidityData = gattHumidityService.GetCharacteristics(uuidHumidityData)[0]; | |
var charHumidityConfig = gattHumidityService.GetCharacteristics(uuidHumidityConfig)[0]; | |
// データの通知を受け取るイベントハンドラーを設定する | |
charHumidityData.ValueChanged += (sender, eventArgs) => | |
{ | |
// データの処理方法はWikiを参照のこと | |
// 例: hhttp://processors.wiki.ti.com/index.php/CC2650_SensorTag_User's_Guide#Data_3 | |
var data = eventArgs.CharacteristicValue.ToArray(); | |
var temperature = (BitConverter.ToUInt16(data, 0) / 65536d) * 165 - 40; | |
var humidity = (BitConverter.ToUInt16(data, 2) / 65536d) * 100; | |
Console.WriteLine($"Temperature={temperature:0.0}℃, Humidity={humidity:0.0}%"); | |
}; | |
// データの通知を有効にする | |
// CC2650に設定を書き込む(大抵の場合は1で有効、0で無効) | |
await charHumidityConfig.WriteValueAsync(new byte[] { 1 }); | |
// データ通知を有効にする | |
await charHumidityData.WriteClientCharacteristicConfigurationDescriptorAsync( | |
GattClientCharacteristicConfigurationDescriptorValue.Notify); | |
// 入力されるまで適当に待機 | |
Console.ReadLine(); | |
// データの通知を無効にする | |
// データ通知を無効にする | |
await charHumidityData.WriteClientCharacteristicConfigurationDescriptorAsync( | |
GattClientCharacteristicConfigurationDescriptorValue.None); | |
// CC2650に設定を書き込む(大抵の場合は1で有効、0で無効) | |
await charHumidityConfig.WriteValueAsync(new byte[] { 0 }); | |
} | |
static class GattCharacteristicExtension | |
{ | |
public static IAsyncOperation<GattCommunicationStatus> WriteValueAsync(this GattCharacteristic characteristic, byte[] value) | |
{ | |
var dataWriter = new DataWriter(); | |
dataWriter.WriteBytes(value); | |
var buffer = dataWriter.DetachBuffer(); | |
return characteristic.WriteValueAsync(buffer); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment