From 65ed78fe5f2189a5db4e48f0643585f7b796b285 Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Tue, 14 Jun 2016 22:31:57 -0400 Subject: [PATCH] Initial Servo API support. --- examples/servo/index.js | 29 ++++++++ examples/servo/index.js.bak | 72 +++++++++++++++++++ flow-typed/jsx-intrinsics.js | 2 +- src/components/Servo.js | 39 ++++++++++ src/components/index.js | 1 + src/firmata/HardwareManager.js | 23 ++++-- src/firmata/__tests__/HardwareManager-test.js | 57 +++++++++++---- 7 files changed, 203 insertions(+), 20 deletions(-) create mode 100644 examples/servo/index.js create mode 100644 examples/servo/index.js.bak create mode 100644 src/components/Servo.js diff --git a/examples/servo/index.js b/examples/servo/index.js new file mode 100644 index 0000000..220be91 --- /dev/null +++ b/examples/servo/index.js @@ -0,0 +1,29 @@ +/** + * servo. + * This is a simple example of a servo animating open and closed. + */ + +import React from 'react'; +import {getPort} from '../port'; +import ReactHardware, {Servo} from '../../src'; + +class Application extends React.Component { + render() { + return ( + + ); + } +} + +ReactHardware.render( + , + getPort(), + (inst) => { + console.log('Rendered <%s />', 'Servo'); + } +); + diff --git a/examples/servo/index.js.bak b/examples/servo/index.js.bak new file mode 100644 index 0000000..094f483 --- /dev/null +++ b/examples/servo/index.js.bak @@ -0,0 +1,72 @@ +/** + * servo. + * This is a simple example of a servo animating open and closed. + */ + +import React from 'react'; +import {getPort} from '../port'; +import ReactHardware from '../../src'; +import tweenState from 'react-tween-state'; + +var SweepingServo = React.createClass({ + mixins: [tweenState.Mixin], + + getInitialState() { + return {value: this.props.duration[0]}; + }, + + getDefaultProps() { + return { + range: [0, 180], + duration: 66.6666 * 20, + }; + }, + + tween() { + this.tweenState('value', { + easing: tweenState.easingTypes.linear, + duration: this.props.duration, + endValue: this.state.value === this.props.range[0] ? + this.props.range[1] : + this.props.range[0], + onEnd: this.tween, + }); + }, + + componentDidMount() { + this.tween(); + }, + + render() { + const value = this.getTweeningValue('value'); + return ( + + ); + }, + +}); + +class Application extends React.Component { + render():ReactElement { + return ( + + ); + } +} + +ReactHardware.render( + , + getPort(), + (inst) => { + console.log('Rendered <%s />', 'SweepingServo'); + } +); + diff --git a/flow-typed/jsx-intrinsics.js b/flow-typed/jsx-intrinsics.js index 65dba57..36be12a 100644 --- a/flow-typed/jsx-intrinsics.js +++ b/flow-typed/jsx-intrinsics.js @@ -14,7 +14,7 @@ declare type $JSXIntrinsics = { mode: 'INPUT'|'OUTPUT'|'ANALOG'|'PWM'| 'SERVO'|'SHIFT'|'I2C'|'ONEWIRE'|'STEPPER'|'IGNORE'|'UNKNOWN'; pin: number|string; value?: number; - onRead: ?(...arg:any) => any; + onRead?: (...arg:any) => any; }>; container: JSXHelper<{}>; diff --git a/src/components/Servo.js b/src/components/Servo.js new file mode 100644 index 0000000..f31b812 --- /dev/null +++ b/src/components/Servo.js @@ -0,0 +1,39 @@ +/** + * ReactHardware component. + * + * console.log('Switch state changed to %s', value)} + * /> + * + * @flow + **/ + +import type {HardwareEvent} from '../types'; +import React, {Component} from 'react'; + +type P = { + pin: number; + min?: number; + max?: number; + value: number; +} + +class Servo extends Component { + props: P; + static defaultProps: {}; + + render() { + return ( + + ); + } +} + +export default Servo; + diff --git a/src/components/index.js b/src/components/index.js index cbfc184..8ac1c3d 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -4,4 +4,5 @@ export {default as Button} from './Button'; export {default as Led} from './Led'; export {default as Potentiometer} from './Potentiometer'; export {default as RGBLed} from './RGBLed'; +export {default as Servo} from './Servo'; export {default as Switch} from './Switch'; diff --git a/src/firmata/HardwareManager.js b/src/firmata/HardwareManager.js index bf74b74..cd815cb 100644 --- a/src/firmata/HardwareManager.js +++ b/src/firmata/HardwareManager.js @@ -212,11 +212,24 @@ export const setPayloadForPin = ( // console.log(`set pinMode of "%s" to "%s"`, payload.pin, payload.mode); const normalizedPin = analogToDigital(payload.pin); board.pinMode(normalizedPin, MODES[payload.mode]); - const communicationType = FIRMATA_COMMUNICATION_METHOD[MODES[payload.mode]]; - if (typeof payload.value !== 'undefined') { - // console.log(`${communicationType}Write to "%s" with "%s"`, payload.pin, payload.value); - /* $FlowFixMe computed property call */ - board[`${communicationType}Write`](payload.pin, +payload.value); + const communicationType = FIRMATA_COMMUNICATION_METHOD[MODES[payload.mode]].toLowerCase(); + + switch (communicationType) { + case 'servo': + board.servoConfig(normalizedPin, +payload.min, +payload.max); + board.servoWrite(normalizedPin, +payload.value); + break; + case 'digital': + board.digitalWrite(normalizedPin, +payload.value); + break; + case 'analog': + board.analogWrite(normalizedPin, +payload.value); + break; + // todo + // * i2c + // * debugString + // * serial + // * sysex } if (payload.onRead) { diff --git a/src/firmata/__tests__/HardwareManager-test.js b/src/firmata/__tests__/HardwareManager-test.js index c053d98..20632f3 100644 --- a/src/firmata/__tests__/HardwareManager-test.js +++ b/src/firmata/__tests__/HardwareManager-test.js @@ -71,11 +71,15 @@ describe('HardwareManager', () => { beforeEach(() => { // default Firmata pin mapping is an Uno hw = new Firmata(); + // mock-firmata uses board-io that does not call this.addListener(...) + hw.digitalRead = require('firmata').prototype.digitalRead.bind(hw); spyOn(hw, 'pinMode'); spyOn(hw, 'digitalWrite'); - spyOn(hw, 'digitalRead'); + spyOn(hw, 'digitalRead').and.callThrough(); spyOn(hw, 'analogWrite'); spyOn(hw, 'analogRead'); + spyOn(hw, 'servoWrite'); + spyOn(hw, 'servoConfig'); }); it('should handle an easy case', () => { @@ -119,10 +123,34 @@ describe('HardwareManager', () => { expect(connection.readers[0].call).toBe(payload.onRead); }); - // TODO: mock-firmata emit should work - xit('should handle changing read handlers', () => { - const before = jasmine.createSpy(); - const after = jasmine.createSpy(); + it('should handle servo write', () => { + const payload = { + pin: 0, + value: 120, + mode: 'SERVO', + }; + const connection = {board: hw, readers: []}; + setPayloadForPin(connection, payload); + expect(hw.servoWrite).toHaveBeenCalledWith(0, 120); + }); + + it('should map servo properties to servoConfig', () => { + const payload = { + pin: 0, + value: 120, + mode: 'SERVO', + min: 0, + max: 180, + }; + const connection = {board: hw, readers: []}; + setPayloadForPin(connection, payload); + expect(hw.servoConfig).toHaveBeenCalledWith(0, 0, 180); + expect(hw.servoWrite).toHaveBeenCalledWith(0, 120); + }); + + it('should handle changing read handlers', () => { + const before = jasmine.createSpy('before'); + const after = jasmine.createSpy('after'); const initialPayload = { pin: 0, mode: 'OUTPUT', @@ -134,18 +162,19 @@ describe('HardwareManager', () => { mode: 'OUTPUT', onRead: after, }; - - setPayloadForPin({board: hw, readers: []}, initialPayload); - + const connection = {board: hw, readers: []}; + setPayloadForPin(connection, initialPayload); expect(hw.pinMode).toHaveBeenCalled(); - - expect(hw.digitalRead).toHaveBeenCalledWith(initialPayload.pin, initialPayload.onRead); + expect(hw.digitalRead).toHaveBeenCalledTimes(1); hw.emit('digital-read-0', Infinity); - expect(before).toHaveBeenCalledWith(Infinity); + expect(initialPayload.onRead).toHaveBeenCalledWith(Infinity); - setPayloadForPin({board: hw}, updatePayload); - hw.emit('digital-read-1', 3.1415); - expect(before).toHaveBeenCalledWith(3.1415); + setPayloadForPin(connection, updatePayload); + hw.emit('digital-read-0', 3.1415); + expect(updatePayload.onRead).toHaveBeenCalledWith(3.1415); + expect(connection.readers[0].call).toBe(after); + expect(hw.digitalRead).toHaveBeenCalledTimes(1); }); }); }); +