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