Support for remote control buttons (#300)

* refactor beacon function inside IR sensor

* towards sim support

* channel labels

* reverting to singletons

* hiding unused apis

* lazy allocation of button instances

* tracking button state

* hook up the state
This commit is contained in:
Peli de Halleux 2018-02-02 09:48:27 -08:00 committed by GitHub
parent f36e14fe69
commit ba47fb0589
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 198 additions and 100 deletions

View File

@ -1,23 +1,31 @@
const enum IrSensorMode {
const enum InfraredSensorMode {
None = -1,
Proximity = 0,
Seek = 1,
RemoteControl = 2,
}
const enum IrRemoteChannel {
const enum InfraredRemoteChannel {
//% block="channel 0"
Ch0 = 0, // top
//% block="channel 1"
Ch1 = 1,
//% block="channel 2"
Ch2 = 2,
//% block="channel 3"
Ch3 = 3,
}
const enum IrRemoteButton {
None = 0x00,
const enum InfraredRemoteButton {
//% block="center beacon"
CenterBeacon = 0x01,
//% block="top left"
TopLeft = 0x02,
//% block="bottom left"
BottomLeft = 0x04,
//% block="top right"
TopRight = 0x08,
//% block="bottom right"
BottomRight = 0x10,
}
@ -31,56 +39,37 @@ const enum InfraredSensorEvent {
namespace sensors {
function mapButton(v: number) {
switch (v) {
case 0: return IrRemoteButton.None
case 1: return IrRemoteButton.TopLeft
case 2: return IrRemoteButton.BottomLeft
case 3: return IrRemoteButton.TopRight
case 4: return IrRemoteButton.TopRight | IrRemoteButton.BottomRight
case 5: return IrRemoteButton.TopLeft | IrRemoteButton.TopRight
case 6: return IrRemoteButton.TopLeft | IrRemoteButton.BottomRight
case 7: return IrRemoteButton.BottomLeft | IrRemoteButton.TopRight
case 8: return IrRemoteButton.BottomLeft | IrRemoteButton.BottomRight
case 9: return IrRemoteButton.CenterBeacon
case 10: return IrRemoteButton.BottomLeft | IrRemoteButton.TopLeft
case 11: return IrRemoteButton.TopRight | IrRemoteButton.BottomRight
default: return IrRemoteButton.None
case 1: return InfraredRemoteButton.TopLeft
case 2: return InfraredRemoteButton.BottomLeft
case 3: return InfraredRemoteButton.TopRight
case 4: return InfraredRemoteButton.TopRight | InfraredRemoteButton.BottomRight
case 5: return InfraredRemoteButton.TopLeft | InfraredRemoteButton.TopRight
case 6: return InfraredRemoteButton.TopLeft | InfraredRemoteButton.BottomRight
case 7: return InfraredRemoteButton.BottomLeft | InfraredRemoteButton.TopRight
case 8: return InfraredRemoteButton.BottomLeft | InfraredRemoteButton.BottomRight
case 9: return InfraredRemoteButton.CenterBeacon
case 10: return InfraredRemoteButton.BottomLeft | InfraredRemoteButton.TopLeft
case 11: return InfraredRemoteButton.TopRight | InfraredRemoteButton.BottomRight
default: return 0;
}
}
let buttons: RemoteInfraredBeaconButton[]
function create(ir: InfraredSensor) {
// it's created by referencing it
const __remoteButtons: RemoteInfraredBeaconButton[] = [];
function __irButton(id: InfraredRemoteButton): RemoteInfraredBeaconButton {
for(let i = 0; i < __remoteButtons.length; ++i) {
if (__remoteButtons[i].position == id)
return __remoteButtons[i];
}
export function irButton(id: IrRemoteButton): RemoteInfraredBeaconButton {
if (buttons == null) {
buttons = []
for (let i = 0; i < 5; ++i) {
buttons.push(new RemoteInfraredBeaconButton(new brick.Button()))
}
// this defeats our static allocation system
// make sure sensors are up
//create(infraredSensor1)
//create(infraredSensor2)
//create(infraredSensor3)
//create(infraredSensor4)
}
let num = -1
while (id) {
id >>= 1;
num++;
}
num = Math.clamp(0, buttons.length - 1, num)
return buttons[num]
const btn = new RemoteInfraredBeaconButton(id, new brick.Button());
__remoteButtons.push(btn);
return btn;
}
//% fixedInstances
export class RemoteInfraredBeaconButton extends control.Component {
position: InfraredRemoteButton;
private button: brick.Button;
constructor(button: brick.Button) {
constructor(position: InfraredRemoteButton, button: brick.Button) {
super();
this.button = button;
}
@ -134,40 +123,51 @@ namespace sensors {
onEvent(ev: ButtonEvent, body: () => void) {
this.button.onEvent(ev, body);
}
/**
* Pauses until the given event is raised
* @param ev the event to wait for
*/
//% help=input/remote-infrared-beacon/pause-until
//% blockId=remotebuttonEvent block="pause until %button|%event"
//% parts="remote"
//% blockNamespace=sensors
//% weight=99 blockGap=8
//% group="Remote Infrared Beacon"
pauseUntil(ev: ButtonEvent) {
this.button.pauseUntil(ev);
}
}
//% fixedInstances
export class InfraredSensor extends internal.UartSensor {
private channel: IrRemoteChannel;
private proximityThreshold: sensors.ThresholdDetector;
private _channel: InfraredRemoteChannel;
private _proximityThreshold: sensors.ThresholdDetector;
constructor(port: number) {
super(port)
this.channel = IrRemoteChannel.Ch0
this.proximityThreshold = new sensors.ThresholdDetector(this._id, 0, 100, 10, 90);
irButton(0) // make sure buttons array is initalized
// and set the mode, as otherwise button events won't work
this.mode = IrSensorMode.RemoteControl;
this._channel = InfraredRemoteChannel.Ch0
this._proximityThreshold = new sensors.ThresholdDetector(this._id, 0, 100, 10, 90);
this.setMode(InfraredSensorMode.Proximity);
}
_query() {
if (this.mode == IrSensorMode.RemoteControl)
return mapButton(this.getNumber(NumberFormat.UInt8LE, this.channel));
else if (this.mode == IrSensorMode.Proximity) {
if (this.mode == InfraredSensorMode.RemoteControl)
return mapButton(this.getNumber(NumberFormat.UInt8LE, this._channel));
else if (this.mode == InfraredSensorMode.Proximity) {
return this.getNumber(NumberFormat.UInt16LE, 0) & 0x0fff;
}
return 0
}
_update(prev: number, curr: number) {
if (this.mode == IrSensorMode.RemoteControl) {
for (let i = 0; i < buttons.length; ++i) {
if (this.mode == InfraredSensorMode.RemoteControl) {
for (let i = 0; i < __remoteButtons.length; ++i) {
let v = !!(curr & (1 << i))
buttons[i]._update(v)
__remoteButtons[i]._update(v)
}
} else if (this.mode == IrSensorMode.Proximity) {
this.proximityThreshold.setLevel(curr);
} else if (this.mode == InfraredSensorMode.Proximity) {
this._proximityThreshold.setLevel(curr);
}
}
@ -175,13 +175,7 @@ namespace sensors {
return DAL.DEVICE_TYPE_IR
}
setRemoteChannel(c: IrRemoteChannel) {
c = Math.clamp(0, 3, c | 0)
this.channel = c
this.setMode(IrSensorMode.RemoteControl)
}
setMode(m: IrSensorMode) {
setMode(m: InfraredSensorMode) {
this._setMode(m)
}
@ -197,7 +191,7 @@ namespace sensors {
//% weight=100 blockGap=8
//% group="Infrared Sensor"
onEvent(event: InfraredSensorEvent, handler: () => void) {
this._setMode(IrSensorMode.Proximity)
this._setMode(InfraredSensorMode.Proximity)
control.onEvent(this._id, event, handler);
}
@ -212,7 +206,7 @@ namespace sensors {
//% weight=99 blockGap=8
//% group="Infrared Sensor"
pauseUntil(event: InfraredSensorEvent) {
this._setMode(IrSensorMode.Proximity)
this._setMode(InfraredSensorMode.Proximity)
control.waitForEvent(this._id, event);
}
@ -228,33 +222,24 @@ namespace sensors {
//% weight=98 blockGap=8
//% group="Infrared Sensor"
proximity(): number {
this._setMode(IrSensorMode.Proximity)
this._setMode(InfraredSensorMode.Proximity)
return this.getNumber(NumberFormat.UInt8LE, 0)
}
/**
* Get the remote commandreceived the infrared sensor.
* @param sensor the infrared sensor
* Sets the remote channel to listen from
* @param channel the channel to listen
*/
//% help=input/infrared/remote-command
//% block="%sensor|remote command"
//% blockId=infraredGetRemoteCommand
//% parts="infrared"
//% blockNamespace=sensors
//% blockId=irSetRemoteChannel block="set %sensor|remote channel to %channel"
//% weight=65
//% group="Infrared Sensor"
remoteCommand(): number {
this._setMode(IrSensorMode.RemoteControl)
return this.getNumber(NumberFormat.UInt8LE, this.channel)
//% group="Remote Infrared Beacon"
setRemoteChannel(channel: InfraredRemoteChannel) {
this.setMode(InfraredSensorMode.RemoteControl)
channel = Math.clamp(0, 3, channel | 0)
this._channel = channel;
}
// TODO
getDirectionAndDistance() {
this._setMode(IrSensorMode.Seek)
return this.getNumber(NumberFormat.UInt16LE, this.channel * 2)
}
/**
* Sets a threshold value
* @param condition the dark or bright light condition
@ -263,11 +248,11 @@ namespace sensors {
//% blockId=irSetThreshold block="set %sensor|%condition|to %value"
//% group="Threshold" blockGap=8 weight=49
//% value.min=0 value.max=100
setThreshold(condition: InfraredSensorEvent, value: number) {
setPromixityThreshold(condition: InfraredSensorEvent, value: number) {
if (condition == InfraredSensorEvent.ObjectNear)
this.proximityThreshold.setLowThreshold(value)
this._proximityThreshold.setLowThreshold(value)
else
this.proximityThreshold.setHighThreshold(value);
this._proximityThreshold.setHighThreshold(value);
}
/**
@ -277,8 +262,14 @@ namespace sensors {
//% blockId=irGetThreshold block="%sensor|%condition"
//% group="Threshold" blockGap=8 weight=49
//% sensor.fieldEditor="ports"
threshold(condition: InfraredSensorEvent): number {
return this.proximityThreshold.threshold(<ThresholdState><number>LightCondition.Dark);
proximityThreshold(condition: InfraredSensorEvent): number {
return this._proximityThreshold.threshold(<ThresholdState><number>LightCondition.Dark);
}
// TODO
private getDirectionAndDistance() {
this._setMode(InfraredSensorMode.Seek)
return this.getNumber(NumberFormat.UInt16LE, this._channel * 2)
}
}
@ -298,29 +289,29 @@ namespace sensors {
* Remote beacon (center) button.
*/
//% whenUsed block="remote button center" weight=95 fixedInstance
export const remoteButtonCenter = irButton(IrRemoteButton.CenterBeacon)
export const remoteButtonCenter = __irButton(InfraredRemoteButton.CenterBeacon)
/**
* Remote top-left button.
*/
//% whenUsed block="remote button top-left" weight=95 fixedInstance
export const remoteButtonTopLeft = irButton(IrRemoteButton.TopLeft)
export const remoteButtonTopLeft = __irButton(InfraredRemoteButton.TopLeft)
/**
* Remote top-right button.
*/
//% whenUsed block="remote button top-right" weight=95 fixedInstance
export const remoteButtonTopRight = irButton(IrRemoteButton.TopRight)
export const remoteButtonTopRight = __irButton(InfraredRemoteButton.TopRight)
/**
* Remote bottom-left button.
*/
//% whenUsed block="remote button bottom-left" weight=95 fixedInstance
export const remoteButtonBottomLeft = irButton(IrRemoteButton.BottomLeft)
export const remoteButtonBottomLeft = __irButton(InfraredRemoteButton.BottomLeft)
/**
* Remote bottom-right button.
*/
//% whenUsed block="remote button bottom-right" weight=95 fixedInstance
export const remoteButtonBottomRight = irButton(IrRemoteButton.BottomRight)
export const remoteButtonBottomRight = __irButton(InfraredRemoteButton.BottomRight)
}

View File

@ -13,6 +13,7 @@ namespace pxsim {
motorState: EV3MotorState;
screenState: EV3ScreenState;
audioState: AudioState;
remoteState: RemoteState;
inputNodes: SensorNode[] = [];
brickNode: BrickNode;
@ -38,6 +39,7 @@ namespace pxsim {
this.motorState = new EV3MotorState();
this.screenState = new EV3ScreenState();
this.audioState = new AudioState();
this.remoteState = new RemoteState();
}
receiveMessage(msg: SimulatorMessage) {

View File

@ -1,6 +1,48 @@
/// <reference path="./sensor.ts"/>
namespace pxsim {
export enum InfraredRemoteButton {
//% block="center beacon"
CenterBeacon = 0x01,
//% block="top left"
TopLeft = 0x02,
//% block="bottom left"
BottomLeft = 0x04,
//% block="top right"
TopRight = 0x08,
//% block="bottom right"
BottomRight = 0x10,
}
export class RemoteState {
state: number = 0;
constructor() {
}
unmapButtons() {
switch(this.state) {
case InfraredRemoteButton.TopLeft: return 1;
case InfraredRemoteButton.BottomLeft: return 2;
case InfraredRemoteButton.TopLeft: return 3;
case InfraredRemoteButton.TopRight | InfraredRemoteButton.BottomRight: return 4;
case InfraredRemoteButton.TopLeft | InfraredRemoteButton.TopRight: return 5;
case InfraredRemoteButton.TopLeft | InfraredRemoteButton.BottomRight: return 6;
case InfraredRemoteButton.BottomLeft | InfraredRemoteButton.TopRight: return 7;
case InfraredRemoteButton.BottomLeft | InfraredRemoteButton.BottomRight: return 8;
case InfraredRemoteButton.CenterBeacon: return 9;
case InfraredRemoteButton.BottomLeft | InfraredRemoteButton.TopLeft: return 10;
case InfraredRemoteButton.TopRight | InfraredRemoteButton.BottomRight: return 11;
default: return 0;
}
}
setPressed(btns: InfraredRemoteButton, down: boolean) {
if (down) this.state = this.state | btns;
else this.state = ~(~this.state | btns);
}
}
export enum InfraredSensorMode {
None = -1,
Proximity = 0,
@ -29,7 +71,11 @@ namespace pxsim {
}
getValue() {
return this.proximity;
switch(this.mode) {
case InfraredSensorMode.Proximity: return this.proximity;
case InfraredSensorMode.RemoteControl: return ev3board().remoteState.unmapButtons();
default: return 0;
}
}
}
}

View File

@ -234,6 +234,8 @@ namespace pxsim.visuals {
const state = ev3board().getInputNodes()[0] as InfraredSensorNode;
if (state.getMode() == InfraredSensorMode.Proximity)
view = new ProximitySliderControl(this.element, this.defs, state, port);
else if (state.getMode() == InfraredSensorMode.RemoteControl)
view = new RemoteBeaconButtonsControl(this.element, this.defs, state, port);
break;
}
case NodeType.GyroSensor: {

View File

@ -0,0 +1,57 @@
namespace pxsim.visuals {
enum InfraredRemoteButton {
CenterBeacon = 0x01,
TopLeft = 0x02,
BottomLeft = 0x04,
TopRight = 0x08,
BottomRight = 0x10,
}
export class RemoteBeaconButtonsControl extends ControlView<InfraredSensorNode> {
private group: SVGGElement;
getInnerView() {
this.group = svg.elt("g") as SVGGElement;
this.group.setAttribute("transform", `translate(2, 2.5) scale(0.6)`)
const btnIds = [
InfraredRemoteButton.CenterBeacon,
InfraredRemoteButton.TopLeft,
InfraredRemoteButton.TopRight,
InfraredRemoteButton.BottomLeft,
InfraredRemoteButton.BottomRight];
const colors = ['#f12a21', '#ffd01b', '#006db3', '#00934b', '#6c2d00'];
let cy = -4;
btnIds.forEach((cid, c) => {
const cx = c % 2 == 0 ? 2.2 : 8.2;
if (c % 2 == 0) cy += 5;
if (btnIds[c]) {
const circle = pxsim.svg.child(this.group, "circle", { 'class': 'sim-color-grid-circle', 'cx': cx, 'cy': cy, 'r': '2', 'style': `fill: ${colors[c]}` });
pointerEvents.down.forEach(evid => circle.addEventListener(evid, ev => {
ev3board().remoteState.setPressed(cid, true);
}));
circle.addEventListener(pointerEvents.leave, ev => {
ev3board().remoteState.setPressed(cid, false);
});
circle.addEventListener(pointerEvents.up, ev => {
ev3board().remoteState.setPressed(cid, true);
});
}
})
return this.group;
}
getInnerWidth() {
return 10.2;
}
getInnerHeight() {
return 15;
}
}
}