Skip to content

Instantly share code, notes, and snippets.

@mayuki
Last active July 5, 2023 13:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mayuki/fd2e3e71ec9a29973f1d8a17e33341f9 to your computer and use it in GitHub Desktop.
Save mayuki/fd2e3e71ec9a29973f1d8a17e33341f9 to your computer and use it in GitHub Desktop.
CC2650 SensorTagを使う
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);
}
}
<Query Kind="Program">
<Reference>&lt;RuntimeDirectory&gt;\System.Runtime.dll</Reference>
<Reference>&lt;ProgramFilesX86&gt;\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5\System.Runtime.WindowsRuntime.dll</Reference>
<Reference>&lt;RuntimeDirectory&gt;\System.Threading.Tasks.dll</Reference>
<Reference>&lt;ProgramFilesX86&gt;\Windows Kits\10\References\Windows.Foundation.FoundationContract\2.0.0.0\Windows.Foundation.FoundationContract.winmd</Reference>
<Reference>&lt;ProgramFilesX86&gt;\Windows Kits\10\References\Windows.Foundation.UniversalApiContract\2.0.0.0\Windows.Foundation.UniversalApiContract.winmd</Reference>
<Reference>&lt;ProgramFilesX86&gt;\Windows Kits\10\UnionMetadata\Facade\Windows.WinMD</Reference>
<Reference>&lt;RuntimeDirectory&gt;\System.Runtime.InteropServices.WindowsRuntime.dll</Reference>
<Reference>&lt;RuntimeDirectory&gt;\WPF\WindowsBase.dll</Reference>
<Reference>&lt;RuntimeDirectory&gt;\System.Xaml.dll</Reference>
<Reference>&lt;RuntimeDirectory&gt;\Accessibility.dll</Reference>
<Reference>&lt;RuntimeDirectory&gt;\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