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 this.state.data && ( 85 <Text> 86 {Object.entries(this.state.data) 87 .map(([key, value]) => `${key}: ${typeof value === 'number' ? round(value) : 0}`) 88 .join(' ')} 89 </Text> 90 ) 91 ); 92 } 93 94 render() { 95 if (this.state.isAvailable !== true) { 96 return null; 97 } 98 return ( 99 <View style={styles.sensor}> 100 <Text>{this.getName()}:</Text> 101 {this.renderData()} 102 <View style={styles.buttonContainer}> 103 <TouchableOpacity onPress={this._toggle} style={styles.button}> 104 <Text>Toggle</Text> 105 </TouchableOpacity> 106 <TouchableOpacity onPress={this._slow} style={[styles.button, styles.middleButton]}> 107 <Text>Slow</Text> 108 </TouchableOpacity> 109 <TouchableOpacity onPress={this._fast} style={styles.button}> 110 <Text>Fast</Text> 111 </TouchableOpacity> 112 </View> 113 </View> 114 ); 115 } 116} 117 118class GyroscopeSensor extends SensorBlock<Sensors.GyroscopeMeasurement> { 119 getName = () => 'Gyroscope'; 120 getSensor = () => Sensors.Gyroscope; 121} 122 123class AccelerometerSensor extends SensorBlock<Sensors.AccelerometerMeasurement> { 124 getName = () => 'Accelerometer'; 125 getSensor = () => Sensors.Accelerometer; 126} 127 128class MagnetometerSensor extends SensorBlock<Sensors.MagnetometerMeasurement> { 129 getName = () => 'Magnetometer'; 130 getSensor = () => Sensors.Magnetometer; 131} 132 133class MagnetometerUncalibratedSensor extends SensorBlock<Sensors.MagnetometerUncalibratedMeasurement> { 134 getName = () => 'Magnetometer (Uncalibrated)'; 135 getSensor = () => Sensors.MagnetometerUncalibrated; 136} 137 138class DeviceMotionSensor extends SensorBlock<Sensors.DeviceMotionMeasurement> { 139 getName = () => 'DeviceMotion'; 140 getSensor = () => Sensors.DeviceMotion; 141 renderXYZBlock = (name: string, event: null | { x?: number; y?: number; z?: number } = {}) => { 142 if (!event) return null; 143 const { x, y, z } = event; 144 return ( 145 <Text> 146 {name}: x: {round(x)} y: {round(y)} z: {round(z)} 147 </Text> 148 ); 149 }; 150 renderABGBlock = ( 151 name: string, 152 event: null | { alpha?: number; beta?: number; gamma?: number } = {} 153 ) => { 154 if (!event) return null; 155 156 const { alpha, beta, gamma } = event; 157 return ( 158 <Text> 159 {name}: α: {round(alpha)} β: {round(beta)} γ: {round(gamma)} 160 </Text> 161 ); 162 }; 163 renderData = () => ( 164 <View> 165 {this.renderXYZBlock('Acceleration', this.state.data.acceleration)} 166 {this.renderXYZBlock('Acceleration w/gravity', this.state.data.accelerationIncludingGravity)} 167 {this.renderABGBlock('Rotation', this.state.data.rotation)} 168 {this.renderABGBlock('Rotation rate', this.state.data.rotationRate)} 169 <Text>Orientation: {Sensors.DeviceMotionOrientation[this.state.data.orientation]}</Text> 170 </View> 171 ); 172} 173 174class BarometerSensor extends SensorBlock<Sensors.BarometerMeasurement> { 175 getName = () => 'Barometer'; 176 getSensor = () => Sensors.Barometer; 177 renderData = () => ( 178 <View> 179 <Text>Pressure: {this.state.data.pressure}</Text> 180 <Text>Relative Altitude: {this.state.data.relativeAltitude}</Text> 181 </View> 182 ); 183} 184 185class LightSensor extends SensorBlock<Sensors.LightSensorMeasurement> { 186 getName = () => 'LightSensor'; 187 getSensor = () => Sensors.LightSensor; 188 renderData = () => ( 189 <View> 190 <Text>Illuminance: {this.state.data.illuminance}</Text> 191 </View> 192 ); 193} 194 195function round(n?: number) { 196 return n ? Math.floor(n * 100) / 100 : 0; 197} 198 199const styles = StyleSheet.create({ 200 container: { 201 flex: 1, 202 marginBottom: 10, 203 }, 204 buttonContainer: { 205 flexDirection: 'row', 206 alignItems: 'stretch', 207 marginTop: 15, 208 }, 209 button: { 210 flex: 1, 211 justifyContent: 'center', 212 alignItems: 'center', 213 backgroundColor: '#eee', 214 padding: 10, 215 }, 216 middleButton: { 217 borderLeftWidth: 1, 218 borderRightWidth: 1, 219 borderColor: '#ccc', 220 }, 221 sensor: { 222 marginTop: 15, 223 paddingHorizontal: 10, 224 }, 225}); 226