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