1import { Subscription } from 'expo-modules-core'; 2import * as Sensors from 'expo-sensors'; 3import React from 'react'; 4import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; 5 6const FAST_INTERVAL = 16; 7const SLOW_INTERVAL = 1000; 8 9export default class SensorScreen extends React.Component { 10 static navigationOptions = { 11 title: 'Sensors', 12 }; 13 14 render() { 15 return ( 16 <ScrollView style={styles.container}> 17 <GyroscopeSensor /> 18 <AccelerometerSensor /> 19 <MagnetometerSensor /> 20 <MagnetometerUncalibratedSensor /> 21 <BarometerSensor /> 22 <LightSensor /> 23 <DeviceMotionSensor /> 24 </ScrollView> 25 ); 26 } 27} 28 29type State<Measurement> = { 30 data: Measurement; 31 isAvailable?: boolean; 32}; 33 34abstract class SensorBlock<Measurement> extends React.Component<object, State<Measurement>> { 35 readonly state: State<Measurement> = { data: {} as Measurement }; 36 37 _subscription?: Subscription; 38 39 componentDidMount() { 40 this.checkAvailability(); 41 } 42 43 checkAvailability = async () => { 44 const isAvailable = await this.getSensor().isAvailableAsync(); 45 this.setState({ isAvailable }); 46 }; 47 48 componentWillUnmount() { 49 this._unsubscribe(); 50 } 51 52 abstract getName: () => string; 53 abstract getSensor: () => Sensors.DeviceSensor<Measurement>; 54 55 _toggle = () => { 56 if (this._subscription) { 57 this._unsubscribe(); 58 } else { 59 this._subscribe(); 60 } 61 }; 62 63 _slow = () => { 64 this.getSensor().setUpdateInterval(SLOW_INTERVAL); 65 }; 66 67 _fast = () => { 68 this.getSensor().setUpdateInterval(FAST_INTERVAL); 69 }; 70 71 _subscribe = () => { 72 this._subscription = this.getSensor().addListener((data: Measurement) => { 73 this.setState({ data }); 74 }); 75 }; 76 77 _unsubscribe = () => { 78 this._subscription && this._subscription.remove(); 79 this._subscription = undefined; 80 }; 81 82 renderData() { 83 return ( 84 <Text> 85 {Object.entries(this.state.data) 86 .map(([key, value]) => `${key}: ${round(value)}`) 87 .join(' ')} 88 </Text> 89 ); 90 } 91 92 render() { 93 if (this.state.isAvailable !== true) { 94 return null; 95 } 96 return ( 97 <View style={styles.sensor}> 98 <Text>{this.getName()}:</Text> 99 {this.renderData()} 100 <View style={styles.buttonContainer}> 101 <TouchableOpacity onPress={this._toggle} style={styles.button}> 102 <Text>Toggle</Text> 103 </TouchableOpacity> 104 <TouchableOpacity onPress={this._slow} style={[styles.button, styles.middleButton]}> 105 <Text>Slow</Text> 106 </TouchableOpacity> 107 <TouchableOpacity onPress={this._fast} style={styles.button}> 108 <Text>Fast</Text> 109 </TouchableOpacity> 110 </View> 111 </View> 112 ); 113 } 114} 115 116class GyroscopeSensor extends SensorBlock<Sensors.GyroscopeMeasurement> { 117 getName = () => 'Gyroscope'; 118 getSensor = () => Sensors.Gyroscope; 119} 120 121class AccelerometerSensor extends SensorBlock<Sensors.AccelerometerMeasurement> { 122 getName = () => 'Accelerometer'; 123 getSensor = () => Sensors.Accelerometer; 124} 125 126class MagnetometerSensor extends SensorBlock<Sensors.MagnetometerMeasurement> { 127 getName = () => 'Magnetometer'; 128 getSensor = () => Sensors.Magnetometer; 129} 130 131class MagnetometerUncalibratedSensor extends SensorBlock<Sensors.MagnetometerUncalibratedMeasurement> { 132 getName = () => 'Magnetometer (Uncalibrated)'; 133 getSensor = () => Sensors.MagnetometerUncalibrated; 134} 135 136class DeviceMotionSensor extends SensorBlock<Sensors.DeviceMotionMeasurement> { 137 getName = () => 'DeviceMotion'; 138 getSensor = () => Sensors.DeviceMotion; 139 renderXYZBlock = (name: string, event: null | { x?: number; y?: number; z?: number } = {}) => { 140 if (!event) return null; 141 const { x, y, z } = event; 142 return ( 143 <Text> 144 {name}: x: {round(x)} y: {round(y)} z: {round(z)} 145 </Text> 146 ); 147 }; 148 renderABGBlock = ( 149 name: string, 150 event: null | { alpha?: number; beta?: number; gamma?: number } = {} 151 ) => { 152 if (!event) return null; 153 154 const { alpha, beta, gamma } = event; 155 return ( 156 <Text> 157 {name}: α: {round(alpha)} β: {round(beta)} γ: {round(gamma)} 158 </Text> 159 ); 160 }; 161 renderData = () => ( 162 <View> 163 {this.renderXYZBlock('Acceleration', this.state.data.acceleration)} 164 {this.renderXYZBlock('Acceleration w/gravity', this.state.data.accelerationIncludingGravity)} 165 {this.renderABGBlock('Rotation', this.state.data.rotation)} 166 {this.renderABGBlock('Rotation rate', this.state.data.rotationRate)} 167 <Text>Orientation: {Sensors.DeviceMotionOrientation[this.state.data.orientation]}</Text> 168 </View> 169 ); 170} 171 172class BarometerSensor extends SensorBlock<Sensors.BarometerMeasurement> { 173 getName = () => 'Barometer'; 174 getSensor = () => Sensors.Barometer; 175 renderData = () => ( 176 <View> 177 <Text>Pressure: {this.state.data.pressure}</Text> 178 <Text>Relative Altitude: {this.state.data.relativeAltitude}</Text> 179 </View> 180 ); 181} 182 183class LightSensor extends SensorBlock<Sensors.LightSensorMeasurement> { 184 getName = () => 'LightSensor'; 185 getSensor = () => Sensors.LightSensor; 186 renderData = () => ( 187 <View> 188 <Text>Illuminance: {this.state.data.illuminance}</Text> 189 </View> 190 ); 191} 192 193function round(n?: number) { 194 return n ? Math.floor(n * 100) / 100 : 0; 195} 196 197const styles = StyleSheet.create({ 198 container: { 199 flex: 1, 200 marginBottom: 10, 201 }, 202 buttonContainer: { 203 flexDirection: 'row', 204 alignItems: 'stretch', 205 marginTop: 15, 206 }, 207 button: { 208 flex: 1, 209 justifyContent: 'center', 210 alignItems: 'center', 211 backgroundColor: '#eee', 212 padding: 10, 213 }, 214 middleButton: { 215 borderLeftWidth: 1, 216 borderRightWidth: 1, 217 borderColor: '#ccc', 218 }, 219 sensor: { 220 marginTop: 15, 221 paddingHorizontal: 10, 222 }, 223}); 224