diff --git a/libs/core/dal.d.ts b/libs/core/dal.d.ts index 4507f801..837de14d 100644 --- a/libs/core/dal.d.ts +++ b/libs/core/dal.d.ts @@ -309,6 +309,8 @@ declare const enum DAL { DEVICE_TYPE_NXT_LIGHT = 2, DEVICE_TYPE_NXT_SOUND = 3, DEVICE_TYPE_NXT_COLOR = 4, + DEVICE_TYPE_NXT_ULTRASONIC = 5, + DEVICE_TYPE_NXT_TEMPERATURE = 6, DEVICE_TYPE_TACHO = 7, DEVICE_TYPE_MINITACHO = 8, DEVICE_TYPE_NEWTACHO = 9, diff --git a/libs/core/ev3const.h b/libs/core/ev3const.h index 6c222ff3..53881bf6 100644 --- a/libs/core/ev3const.h +++ b/libs/core/ev3const.h @@ -11,6 +11,8 @@ #define DEVICE_TYPE_NXT_LIGHT 2 #define DEVICE_TYPE_NXT_SOUND 3 #define DEVICE_TYPE_NXT_COLOR 4 +#define DEVICE_TYPE_NXT_ULTRASONIC 5 +#define DEVICE_TYPE_NXT_TEMPERATURE 6 #define DEVICE_TYPE_TACHO 7 #define DEVICE_TYPE_MINITACHO 8 #define DEVICE_TYPE_NEWTACHO 9 diff --git a/libs/core/input.ts b/libs/core/input.ts index a56cfde7..b25abe34 100644 --- a/libs/core/input.ts +++ b/libs/core/input.ts @@ -235,10 +235,11 @@ namespace sensors.internal { function detectDevices() { control.dmesg(`DETECT DEVICES (hash ${hashDevices()})`); - const conns = analogMM.slice(AnalogOff.InConn, DAL.NUM_INPUTS); + const inDcm = analogMM.slice(AnalogOff.InDcm, DAL.NUM_INPUTS); + const inConn = analogMM.slice(AnalogOff.InConn, DAL.NUM_INPUTS); for (const sensorInfo of sensorInfos) { - const newConn = conns[sensorInfo.port]; + const newConn = inConn[sensorInfo.port]; if (newConn == sensorInfo.connType && sensorInfo.sensor && sensorInfo.sensor.isActive()) { continue; } @@ -252,9 +253,13 @@ namespace sensors.internal { sensorInfo.devType = DAL.DEVICE_TYPE_IIC_UNKNOWN; sensorInfo.iicid = readIICID(sensorInfo.port); control.dmesg(`new IIC connection at port ${sensorInfo.port} with ID ${sensorInfo.iicid.length}`); + } else if (newConn == DAL.CONN_NXT_DUMB) { + sensorInfo.devType = inDcm[sensorInfo.port]; + control.dmesg(`new NXT DUMB connection at port ${sensorInfo.port} dev type ${sensorInfo.devType}`); } else if (newConn == DAL.CONN_INPUT_DUMB) { - control.dmesg(`new DUMB connection at port ${sensorInfo.port}`); - sensorInfo.devType = DAL.DEVICE_TYPE_TOUCH; // TODO? for now assume touch sensor + //sensorInfo.devType = inDcm[sensorInfo.port]; // We get the result DEVICE_TYPE_UNKNOWN + sensorInfo.devType = DAL.DEVICE_TYPE_TOUCH; // TODO? for now assume touch + control.dmesg(`new DUMB connection at ${sensorInfo.port} dev type ${sensorInfo.devType}`); } else if (newConn == DAL.CONN_NONE || newConn == 0) { control.dmesg(`disconnect at port ${sensorInfo.port}`); } else { @@ -351,13 +356,44 @@ namespace sensors.internal { } export class AnalogSensor extends Sensor { + + protected mode: number; // the mode user asked for + protected realmode: number; + constructor(port: number) { - super(port) + super(port); + this.mode = 0; + this.realmode = 0; + } + + _activated() { + this.realmode = 0; + this._setMode(this.mode); + } + + protected _setMode(m: number) { + let v = m | 0; + this.mode = v; + if (!this.isActive()) return; + if (this.realmode != this.mode) { + control.dmesg(`_setMode p=${this._port} m: ${this.realmode} -> ${v}`); + this.realmode = v; + setAnalogMode(this._port, this._deviceType(), this.mode); + } + } + + _readPin1() { + if (!this.isActive()) return 0; + return analogMM.getNumber(NumberFormat.Int16LE, AnalogOff.InPin1 + 2 * this._port); } _readPin6() { - if (!this.isActive()) return 0 - return analogMM.getNumber(NumberFormat.Int16LE, AnalogOff.InPin6 + 2 * this._port) + if (!this.isActive()) return 0; + return analogMM.getNumber(NumberFormat.Int16LE, AnalogOff.InPin6 + 2 * this._port); + } + + _deviceType() { + return DAL.DEVICE_TYPE_UNKNOWN; } } @@ -366,14 +402,14 @@ namespace sensors.internal { protected realmode: number // the mode the hardware is in constructor(port: number) { - super(port) - this.mode = 0 - this.realmode = 0 + super(port); + this.mode = 0; + this.realmode = 0; } _activated() { - this.realmode = 0 - this._setMode(this.mode) + this.realmode = 0; + this._setMode(this.mode); } getStatus() { @@ -381,13 +417,14 @@ namespace sensors.internal { } protected _setMode(m: number) { - //control.dmesg(`_setMode p=${this.port} m: ${this.realmode} -> ${m}`) - let v = m | 0 - this.mode = v - if (!this.isActive()) return + //control.dmesg(`_setMode p=${this.port} m: ${this.realmode} -> ${m}`); + let v = m | 0; + this.mode = v; + if (!this.isActive()) return; if (this.realmode != this.mode) { - this.realmode = v - setUartMode(this._port, v) + control.dmesg(`_setMode p=${this._port} m: ${this.realmode} -> ${v}`); + this.realmode = v; + setUartMode(this._port, v); } } @@ -575,19 +612,29 @@ namespace sensors.internal { DAL.MAX_DEVICE_DATALENGTH) } - function getUartNumber(fmt: NumberFormat, off: number, port: number) { + function getUartNumber(fmt: NumberFormat, off: number, port: number): number { if (port < 0) return 0 - let index = uartMM.getNumber(NumberFormat.UInt16LE, UartOff.Actual + port * 2) + const index = uartMM.getNumber(NumberFormat.UInt16LE, UartOff.Actual + port * 2) return uartMM.getNumber(fmt, UartOff.Raw + DAL.MAX_DEVICE_DATALENGTH * 300 * port + DAL.MAX_DEVICE_DATALENGTH * index + off) } + function setAnalogMode(port: number, type: number, mode: number) { + if (port < 0) return; + control.dmesg(`analog set type ${type} mode ${mode} at port ${port}`); + devcon.setNumber(NumberFormat.Int8LE, DevConOff.Connection + port, DAL.CONN_NXT_DUMB); + devcon.setNumber(NumberFormat.Int8LE, DevConOff.Type + port, type); + devcon.setNumber(NumberFormat.Int8LE, DevConOff.Mode + port, mode); + analogMM.ioctl(0, devcon); + } + export function setIICMode(port: number, type: number, mode: number) { if (port < 0) return; - devcon.setNumber(NumberFormat.Int8LE, DevConOff.Connection + port, DAL.CONN_NXT_IIC) - devcon.setNumber(NumberFormat.Int8LE, DevConOff.Type + port, type) - devcon.setNumber(NumberFormat.Int8LE, DevConOff.Mode + port, mode) - IICMM.ioctl(IO.IIC_SET_CONN, devcon) + control.dmesg(`iic set type ${type} mode ${mode} at port ${port}`); + devcon.setNumber(NumberFormat.Int8LE, DevConOff.Connection + port, DAL.CONN_NXT_IIC); + devcon.setNumber(NumberFormat.Int8LE, DevConOff.Type + port, type); + devcon.setNumber(NumberFormat.Int8LE, DevConOff.Mode + port, mode); + IICMM.ioctl(IO.IIC_SET_CONN, devcon); } export function transactionIIC(port: number, deviceAddress: number, writeBuf: number[], readLen: number) { diff --git a/libs/ev3/console.ts b/libs/ev3/console.ts index 8c95a185..ef28f8b4 100644 --- a/libs/ev3/console.ts +++ b/libs/ev3/console.ts @@ -19,8 +19,8 @@ namespace console._screen { if (!lines) { lines = []; console.addListener(log); - brick.buttonUp.onEvent(ButtonEvent.Bumped, () => scroll(-3)) - brick.buttonDown.onEvent(ButtonEvent.Bumped, () => scroll(3)) + brick.buttonUp.onEvent(ButtonEvent.Bumped, () => scroll(-3)); + brick.buttonDown.onEvent(ButtonEvent.Bumped, () => scroll(3)); } } diff --git a/libs/ev3/ns.ts b/libs/ev3/ns.ts index bc1a46f1..9235dae5 100644 --- a/libs/ev3/ns.ts +++ b/libs/ev3/ns.ts @@ -6,7 +6,7 @@ namespace brick { //% color="#C8509B" weight=95 icon="\uf10f" //% labelLineWidth=100 -//% groups='["Touch Sensor", "Color Sensor", "Ultrasonic Sensor", "Gyro Sensor", "Infrared Sensor", "Remote Infrared Beacon"]' +//% groups='["Touch Sensor", "Color Sensor", "Ultrasonic Sensor", "Gyro Sensor", "Infrared Sensor", "Remote Infrared Beacon", "Light Sensor"]' //% subcategories='["NXT", "HiTechnic"]' namespace sensors { } diff --git a/libs/nxt-light-sensor/README.md b/libs/nxt-light-sensor/README.md new file mode 100644 index 00000000..d0b65957 --- /dev/null +++ b/libs/nxt-light-sensor/README.md @@ -0,0 +1,3 @@ +# NXT Light sensor + +The library to interact with the NXT Light Sensor. \ No newline at end of file diff --git a/libs/nxt-light-sensor/docs/reference/sensors/nxt-light-sensor.md b/libs/nxt-light-sensor/docs/reference/sensors/nxt-light-sensor.md new file mode 100644 index 00000000..9f5fb2f1 --- /dev/null +++ b/libs/nxt-light-sensor/docs/reference/sensors/nxt-light-sensor.md @@ -0,0 +1,10 @@ +# NXT light sensor + +```cards +sensors.nxtLight1.light(NXTLightIntensityMode.Reflected) +sensors.nxtLight1.light(NXTLightIntensityMode.ReflectedRaw) +``` + +## See slso + +[light](/reference/sensors/nxt-light-sensor/light) diff --git a/libs/nxt-light-sensor/docs/reference/sensors/nxt-light-sensor/light.md b/libs/nxt-light-sensor/docs/reference/sensors/nxt-light-sensor/light.md new file mode 100644 index 00000000..a714da2d --- /dev/null +++ b/libs/nxt-light-sensor/docs/reference/sensors/nxt-light-sensor/light.md @@ -0,0 +1,31 @@ +# light + +Get the amount of ambient or reflected light measured by the sensor. + +```sig +sensors.nxtLight1.light(NXTLightIntensityMode.Reflected) +``` + +The light sensor adjusts itself to more accurately measure light depending on the source of the light. You decide if you want to measure _ambient_ light (light all around or direct light) or if you want to know how much light is reflected from a surface. The amount of light measured is in the range of `0` (darkest) to `100` (brightest). + +## Parameters + +* **mode**: the type of measurement for light. This is either ``ambient`` or ``reflected`` light. + +## Returns + +* a number that is the amount of light measured. No light (darkness) is `0` and the brightest light is `100`. + +## Example + +Make the status light show ``green`` if the ambient light is greater than `20`. + +```blocks +forever(function () { + if (sensors.nxtLight1.light(NXTLightIntensityMode.Reflected) > 20) { + brick.setStatusLight(StatusLight.Green) + } else { + brick.setStatusLight(StatusLight.Orange) + } +}) +``` diff --git a/libs/nxt-light-sensor/light.ts b/libs/nxt-light-sensor/light.ts new file mode 100644 index 00000000..6f177ac8 --- /dev/null +++ b/libs/nxt-light-sensor/light.ts @@ -0,0 +1,220 @@ +const enum NXTLightSensorMode { + //% block="reflected light (raw)" + ReflectedLightRaw = 0, + //% block="reflected light" + ReflectedLight = 1, + //% block="ambient light (raw)" + AmbientLightRaw = 2, + //% block="ambient light" + AmbientLight = 3, +} + +enum NXTLightIntensityMode { + //% block="reflected light (raw)" + ReflectedRaw = NXTLightSensorMode.ReflectedLightRaw, + //% block="reflected light" + Reflected = NXTLightSensorMode.ReflectedLight, + //% block="ambient light (raw)" + //% blockHidden=true + AmbientRaw = NXTLightSensorMode.AmbientLightRaw, + //% block="ambient light" + //% blockHidden=true + Ambient = NXTLightSensorMode.AmbientLight +} + +namespace sensors { + + //% fixedInstances + export class NXTLightSensor extends internal.AnalogSensor { + + // https://github.com/mindboards/ev3sources-xtended/blob/master/ev3sources/lms2012/lms2012/Linux_AM1808/sys/settings/typedata.rcf + + private thresholdDetector: sensors.ThresholdDetector; + private darkReflectedLight: number = 3372; + private brightReflectedLight: number = 445; + private darkAmbientLight: number = 3411; + private brightAmbientLight: number = 633; + + constructor(port: number) { + super(port); + this.thresholdDetector = new sensors.ThresholdDetector(this.id()); + } + + _query() { + if (this.mode == NXTLightSensorMode.ReflectedLight) { + return [this.reflectetLight()]; + } else if (this.mode == NXTLightSensorMode.AmbientLight) { + return [this.ambientLight()]; + } else if (this.mode == NXTLightSensorMode.ReflectedLightRaw) { + return [this.reflectetLightRaw()]; + } else if (this.mode == NXTLightSensorMode.AmbientLightRaw) { + return [this.ambientLightRaw()]; + } + return [0]; + } + + _info() { + if (this.mode == NXTLightSensorMode.ReflectedLight || this.mode == NXTLightSensorMode.AmbientLight) { + return [`${this._query()[0].toString()}%`]; + } else { + return [this._query()[0].toString()]; + } + } + + _update(prev: number, curr: number) { + return this.readValue(); + } + + _deviceType() { + return DAL.DEVICE_TYPE_NXT_LIGHT; + } + + setMode(m: number) { + this._setMode(m); + } + + /** + * Gets the current light mode + */ + lightMode() { + return this.mode; + } + + // This pin is not used by the NXT Analog Sensor + _readPin6() { + return 0; + } + + /** + * Measure the ambient or reflected light value from 0 (darkest) to 100 (brightest). For raw reflection values, the range can be from 0 to 4095. + * @param sensor the color sensor port + */ + //% help=sensors/nxt-light-sensor/light + //% block="**nxt light sensor** $this|$mode" + //% blockId=nxtLight + //% parts="nxtlightsensor" + //% blockNamespace=sensors + //% this.fieldEditor="ports" + //% weight=99 blockGap=8 + //% subcategory="NXT" + //% group="Light Sensor" + light(mode: NXTLightIntensityMode) { + this.setMode(mode); + this.poke(); + switch (mode) { + case NXTLightIntensityMode.ReflectedRaw: + return this.reflectetLightRaw(); + case NXTLightIntensityMode.Reflected: + return this.reflectetLight(); + case NXTLightIntensityMode.AmbientRaw: + return this.ambientLightRaw(); + case NXTLightIntensityMode.Ambient: + return this.ambientLight(); + default: + return 0; + } + } + + /** + * Set the range of values for determining dark and light in light reflection mode. This must be done so that the reflection mode defines a value in the range from 0 to 100 percent. + * @param sensor the color sensor port + * @param dark the value of dark, eg: 0 + * @param bright the value of bright, eg: 4095 + */ + //% help=sensors/nxt-light-sensor/set-reflected-range + //% block="**nxt light sensor** $this|set reflected range dark $dark|bright $bright" + //% blockId=setReflectedLightRange + //% parts="nxtlightsensor" + //% blockNamespace=sensors + //% this.fieldEditor="ports" + //% weight=89 blockGap=8 + //% subcategory="NXT" + //% group="Light Sensor" + setReflectedLightRange(dark: number, bright: number) { + if (dark <= bright) return; + this.darkReflectedLight = Math.constrain(dark, 0, 4095); + this.brightReflectedLight = Math.constrain(bright, 0, 4095); + } + + /** + * Set the value range for dark and light detection in ambient light mode. This must be done so that the ambient light mode determines the value in the range from 0 to 100 percent. + * @param sensor the color sensor port + * @param dark the value of dark, eg: 0 + * @param bright the value of bright, eg: 4095 + */ + //% help=sensors/nxt-light-sensor/set-ambient-range + //% block="**nxt light sensor** $this|set ambient range dark $dark|bright $bright" + //% blockId=setAmbientLightRange + //% parts="nxtlightsensor" + //% blockNamespace=sensors + //% this.fieldEditor="ports" + //% weight=88 blockGap=8 + //% subcategory="NXT" + //% group="Light Sensor" + //% blockHidden=true + setAmbientLightRange(dark: number, bright: number) { + if (dark <= bright) return; + this.darkAmbientLight = Math.constrain(dark, 0, 4095); + this.brightAmbientLight = Math.constrain(bright, 0, 4095); + } + + /** + * Gets the raw light value. + */ + //% + private readValue() { + return this._readPin1(); + } + + /** + * Gets the raw reflection light value. + */ + //% + reflectetLightRaw() { + // ToDo: the red LED should be turned off in ambient lighting mode + return this.readValue(); + } + + /** + * Gets the raw ambient light value. + */ + //% + ambientLightRaw() { + // ToDo: the red LED should be turned off in ambient lighting mode + return this.readValue(); + } + + /** + * Gets the normalize reflection light value. + */ + //% + reflectetLight() { + let reflectedVal = Math.map(this.readValue(), this.darkReflectedLight, this.brightReflectedLight, 0, 100); + reflectedVal = Math.round(Math.constrain(reflectedVal, 0, 100)); + return reflectedVal; + } + + /** + * Gets the normalize ambient light value. + */ + //% + ambientLight() { + let ambientVal = Math.map(this.readValue(), this.darkAmbientLight, this.brightAmbientLight, 0, 100); + ambientVal = Math.round(Math.constrain(ambientVal, 0, 100)); + return ambientVal; + } + + } + + //% whenUsed block="1" weight=95 fixedInstance jres=icons.port1 + export const nxtLight1: NXTLightSensor = new NXTLightSensor(1); + + //% whenUsed block="2" weight=90 fixedInstance jres=icons.port2 + export const nxtLight2: NXTLightSensor = new NXTLightSensor(2); + + //% whenUsed block="3" weight=90 fixedInstance jres=icons.port3 + export const nxtLight3: NXTLightSensor = new NXTLightSensor(3); + + //% whenUsed block="4" weight=90 fixedInstance jres=icons.port4 + export const nxtLight4: NXTLightSensor = new NXTLightSensor(4); +} diff --git a/libs/nxt-light-sensor/pxt.json b/libs/nxt-light-sensor/pxt.json new file mode 100644 index 00000000..4068bd03 --- /dev/null +++ b/libs/nxt-light-sensor/pxt.json @@ -0,0 +1,15 @@ +{ + "name": "nxt-light-sensor", + "description": "NXT Light Sensor support - beta", + "files": [ + "README.md", + "light.ts" + ], + "testFiles": [ + "test.ts" + ], + "public": true, + "dependencies": { + "core": "file:../core" + } +} \ No newline at end of file diff --git a/libs/nxt-light-sensor/test.ts b/libs/nxt-light-sensor/test.ts new file mode 100644 index 00000000..27e42245 --- /dev/null +++ b/libs/nxt-light-sensor/test.ts @@ -0,0 +1,19 @@ +sensors.nxtLight1.setReflectedLightRange(2600, 1600); + +forever(function () { + control.timer1.reset(); + let nls1 = sensors.nxtLight1.reflectetLightRaw(); + let nls2 = sensors.nxtLight1.light(NXTLightIntensityMode.ReflectedRaw); + let rrlcs = sensors.color2.light(LightIntensityMode.ReflectedRaw); + let rrrcs = sensors.color3.light(LightIntensityMode.ReflectedRaw); + let t = sensors.touch4.isPressed() ? 1 : 0; + brick.clearScreen(); + brick.printValue("nls1", nls1, 1); + brick.printValue("nls2", nls2, 2); + brick.printValue("rrlcs", rrlcs, 3); + brick.printValue("rrrcs", rrrcs, 4); + brick.printValue("t", t, 5); + brick.printValue("active1", sensors.nxtLight1.isActive() ? 1 : 0, 11); + brick.printValue("active4", sensors.touch4.isActive() ? 1 : 0, 12); + control.timer1.pauseUntil(10); +}) \ No newline at end of file diff --git a/libs/touch-sensor/touch.ts b/libs/touch-sensor/touch.ts index d0fa040d..53958e8f 100644 --- a/libs/touch-sensor/touch.ts +++ b/libs/touch-sensor/touch.ts @@ -12,11 +12,11 @@ namespace sensors { } _query() { - return this._readPin6() > 2500 ? [1] : [0]; + return [this._readPin6() > 2500 ? 1 : 0]; } _info() { - return this._query() ? ["pres"] : ["rel"]; + return [this._query()[0] ? "pres" : "rel"]; } _update(prev: number, curr: number) { diff --git a/package.json b/package.json index daa1c029..3ea2a01e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pxt-ev3", - "version": "1.4.37", + "version": "1.4.38", "description": "LEGO MINDSTORMS EV3 for Microsoft MakeCode", "private": false, "keywords": [ diff --git a/pxtarget.json b/pxtarget.json index 8592b1e8..e78b80a6 100644 --- a/pxtarget.json +++ b/pxtarget.json @@ -15,6 +15,7 @@ "libs/ultrasonic-sensor", "libs/infrared-sensor", "libs/gyro-sensor", + "libs/nxt-light-sensor", "libs/screen", "libs/ev3", "libs/storage", diff --git a/sim/dalboard.ts b/sim/dalboard.ts index c778866c..de79d221 100644 --- a/sim/dalboard.ts +++ b/sim/dalboard.ts @@ -159,6 +159,7 @@ namespace pxsim { case DAL.DEVICE_TYPE_TOUCH: this.inputNodes[port] = new TouchSensorNode(port); break; case DAL.DEVICE_TYPE_ULTRASONIC: this.inputNodes[port] = new UltrasonicSensorNode(port); break; case DAL.DEVICE_TYPE_IR: this.inputNodes[port] = new InfraredSensorNode(port); break; + case DAL.DEVICE_TYPE_NXT_LIGHT: this.inputNodes[port] = new NXTLightSensorNode(port); break; } } return this.inputNodes[port]; diff --git a/sim/state/analog.ts b/sim/state/analog.ts index 7122b4f1..529312ef 100644 --- a/sim/state/analog.ts +++ b/sim/state/analog.ts @@ -1,6 +1,6 @@ namespace pxsim { - enum AnalogOff { + export enum AnalogOff { InPin1 = 0, // int16[4] InPin6 = 8, // int16[4] OutPin5 = 16, // int16[4] @@ -25,7 +25,7 @@ namespace pxsim { export class EV3AnalogState { constructor() { - let data = new Uint8Array(5172) + let data = new Uint8Array(5172); MMapMethods.register("/dev/lms_analog", { data, beforeMemRead: () => { @@ -36,24 +36,34 @@ namespace pxsim { for (let port = 0; port < DAL.NUM_INPUTS; port++) { const node = inputNodes[port]; if (node) { - data[AnalogOff.InConn + port] = node.isUart() ? DAL.CONN_INPUT_UART : DAL.CONN_INPUT_DUMB; + if (node.isAnalog()) data[AnalogOff.InDcm + port] = node.getDeviceType(); + data[AnalogOff.InConn + port] = node.isUart() ? DAL.CONN_INPUT_UART : (!node.isNXT() ? DAL.CONN_INPUT_DUMB : DAL.CONN_NXT_DUMB); if (node.isAnalog() && node.hasData()) { //data[AnalogOff.InPin6 + 2 * port] = node.getValue(); - util.map16Bit(data, AnalogOff.InPin6 + 2 * port, Math.floor(node.getValue())); + util.map16Bit(data, node.getAnalogReadPin() + 2 * port, Math.floor(node.getValue())); } } } }, read: buf => { - let v = "vSIM" + let v = "vSIM"; for (let i = 0; i < buf.data.length; ++i) - buf.data[i] = v.charCodeAt(i) || 0 - return buf.data.length + buf.data[i] = v.charCodeAt(i) || 0; + return buf.data.length; }, write: buf => { - return 2 + return 2; }, ioctl: (id, buf) => { + //console.log("ioctl: " + id); + for (let port = 0; port < DAL.NUM_INPUTS; port++) { + const connection = buf.data[DevConOff.Connection + port]; + const type = buf.data[DevConOff.Type + port]; + const mode = buf.data[DevConOff.Mode + port]; + //console.log(`${port}, mode: ${mode}`); + const node = ev3board().getInputNodes()[port]; + if (node) node.setMode(mode); + } return 2; } }) diff --git a/sim/state/nodeTypes.ts b/sim/state/nodeTypes.ts index 53d4c9c7..255353cd 100644 --- a/sim/state/nodeTypes.ts +++ b/sim/state/nodeTypes.ts @@ -8,7 +8,8 @@ namespace pxsim { GyroSensor = 5, ColorSensor = 6, UltrasonicSensor = 7, - InfraredSensor = 8 + InfraredSensor = 8, + NXTLightSensor = 9 } export interface Node { diff --git a/sim/state/nxtlight.ts b/sim/state/nxtlight.ts new file mode 100644 index 00000000..fce1d9d7 --- /dev/null +++ b/sim/state/nxtlight.ts @@ -0,0 +1,58 @@ +/// + +namespace pxsim { + + export enum NXTLightSensorMode { + None = -1, + ReflectedLightRaw = 0, + ReflectedLight = 1, + AmbientLightRaw = 2, + AmbientLight = 3, + } + + export class NXTLightSensorNode extends AnalogSensorNode { + + id = NodeType.NXTLightSensor; + + private value: number = 0; + public darkReflectedLight: number = 3372; + public brightReflectedLight: number = 445; + public darkAmbientLight: number = 3411; + public brightAmbientLight: number = 633; + + constructor(port: number) { + super(port); + this.mode = -1; + } + + getDeviceType() { + return DAL.DEVICE_TYPE_NXT_LIGHT; + } + + setValue(value: number) { + this.value = value; + this.setChangedState(); + } + + getValue() { + return this.value; + } + + setMode(mode: number) { + this.mode = mode; + if (this.mode == NXTLightSensorMode.ReflectedLight) this.value = 1908; + else if (this.mode == NXTLightSensorMode.AmbientLight) this.value = 2022; + else this.value = 2048; + this.changed = true; + this.modeChanged = true; + } + + getAnalogReadPin() { + return AnalogOff.InPin1; + } + + isNXT() { + return true; + } + } +} \ No newline at end of file diff --git a/sim/state/sensor.ts b/sim/state/sensor.ts index 42e2df01..3aa75659 100644 --- a/sim/state/sensor.ts +++ b/sim/state/sensor.ts @@ -1,4 +1,3 @@ - namespace pxsim { export class SensorNode extends BaseNode { @@ -20,6 +19,10 @@ namespace pxsim { return false; } + public isNXT() { + return false; + } + public isModeReturnArr() { return this.modeReturnArr; } @@ -32,6 +35,10 @@ namespace pxsim { return [0]; } + public getAnalogReadPin() { + return AnalogOff.InPin6; // Defl for ev3 sensor + } + setMode(mode: number) { this.mode = mode; this.changed = true; diff --git a/sim/state/uart.ts b/sim/state/uart.ts index 820097a8..c9a99683 100644 --- a/sim/state/uart.ts +++ b/sim/state/uart.ts @@ -39,7 +39,7 @@ namespace pxsim { } - enum DevConOff { + export enum DevConOff { Connection = 0, // int8[4] Type = 4, // int8[4] Mode = 8, // int8[4] @@ -106,13 +106,13 @@ namespace pxsim { } }, read: buf => { - let v = "vSIM" + let v = "vSIM"; // for (let i = 0; i < buf.data.length; ++i) // buf.data[i] = v.charCodeAt(i) || 0 - return buf.data.length + return buf.data.length; }, write: buf => { - return 2 + return 2; }, ioctl: (id, buf) => { switch (id) { diff --git a/sim/visuals/assets/NXT Light Sensor.svg b/sim/visuals/assets/NXT Light Sensor.svg new file mode 100644 index 00000000..f39772bd --- /dev/null +++ b/sim/visuals/assets/NXT Light Sensor.svg @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sim/visuals/assets/NXT Light Sensor.ts b/sim/visuals/assets/NXT Light Sensor.ts new file mode 100644 index 00000000..610c2ab2 --- /dev/null +++ b/sim/visuals/assets/NXT Light Sensor.ts @@ -0,0 +1,150 @@ +namespace pxsim { + export const NXT_LIGHT_SENSOR_SVG = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `; +} \ No newline at end of file diff --git a/sim/visuals/board.ts b/sim/visuals/board.ts index 69d3e952..541bd25a 100644 --- a/sim/visuals/board.ts +++ b/sim/visuals/board.ts @@ -268,6 +268,13 @@ namespace pxsim.visuals { view = new RotationSliderControl(this.element, this.defs, state, port); break; } + case NodeType.NXTLightSensor: { + const state = ev3board().getInputNodes()[port] as NXTLightSensorNode; + if (state.getMode() != NXTLightSensorMode.None) { + view = new LightWheelControl(this.element, this.defs, state, port); + } + break; + } case NodeType.MediumMotor: case NodeType.LargeMotor: { const state = ev3board().getMotors()[port]; @@ -306,6 +313,8 @@ namespace pxsim.visuals { view = new UltrasonicSensorView(port); break; case NodeType.InfraredSensor: view = new InfraredView(port); break; + case NodeType.NXTLightSensor: + view = new NXTLightSensorView(port); break; case NodeType.Brick: //return new BrickView(0); view = this.layoutView.getBrick(); break; diff --git a/sim/visuals/controls/lightWheel.ts b/sim/visuals/controls/lightWheel.ts new file mode 100644 index 00000000..a2c3585e --- /dev/null +++ b/sim/visuals/controls/lightWheel.ts @@ -0,0 +1,129 @@ +namespace pxsim.visuals { + + export class LightWheelControl extends ControlView { + + private group: SVGGElement; + private colorGradient: SVGLinearGradientElement; + private reporter: SVGTextElement; + private rect: SVGElement; + + getInnerWidth() { + return 111; + } + + getInnerHeight() { + return 192; + } + + private getReporterHeight() { + return 38; + } + + private getSliderWidth() { + return 62; + } + + private getSliderHeight() { + return 131; + } + + private getMinValue(state: NXTLightSensorNode) { + if (state.getMode() == NXTLightSensorMode.ReflectedLight) return state.brightReflectedLight; + else if (state.getMode() == NXTLightSensorMode.AmbientLight) return state.brightAmbientLight; + return 0; + } + + private getMaxValue(state: NXTLightSensorNode) { + if (state.getMode() == NXTLightSensorMode.ReflectedLightRaw || state.getMode() == NXTLightSensorMode.AmbientLightRaw) { + return 4095; + } else if (state.getMode() == NXTLightSensorMode.ReflectedLight) { + return state.darkReflectedLight; + } else if (state.getMode() == NXTLightSensorMode.AmbientLight) { + return state.darkAmbientLight; + } + return 100; + } + + private mapValue(x: number, inMin: number, inMax: number, outMin: number, outMax: number) { + return (x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; + } + + updateState() { + if (!this.visible) { + return; + } + const node = this.state; + const value = node.getValue(); + let inverseValue = this.getMaxValue(node) - value + this.getMinValue(node); + if (node.getMode() == NXTLightSensorMode.ReflectedLightRaw || node.getMode() == NXTLightSensorMode.AmbientLightRaw) { + inverseValue = this.mapValue(inverseValue, 0, 4095, 0, 100); + } else if (node.getMode() == NXTLightSensorMode.ReflectedLight) { + inverseValue = this.mapValue(inverseValue, node.darkReflectedLight, node.brightReflectedLight, 0, 100); + } else if (node.getMode() == NXTLightSensorMode.AmbientLight) { + inverseValue = this.mapValue(inverseValue, node.darkAmbientLight, node.brightAmbientLight, 0, 100); + } + svg.setGradientValue(this.colorGradient, inverseValue + "%"); + if (node.getMode() == NXTLightSensorMode.ReflectedLightRaw || node.getMode() == NXTLightSensorMode.AmbientLightRaw) { + this.reporter.textContent = `${Math.floor(parseFloat(value.toString()))}`; + } else { + this.reporter.textContent = `${Math.floor(this.mapValue(parseFloat(value.toString()), this.getMaxValue(node), this.getMinValue(node), 0, 100))}%`; + } + } + + updateColorLevel(pt: SVGPoint, parent: SVGSVGElement, ev: MouseEvent) { + const state = this.state; + let cur = svg.cursorPoint(pt, parent, ev); + const bBox = this.rect.getBoundingClientRect(); + const height = bBox.height; + let t = Math.max(0, Math.min(1, (height + bBox.top / this.scaleFactor - cur.y / this.scaleFactor) / height)); + if (state.getMode() == NXTLightSensorMode.ReflectedLight || state.getMode() == NXTLightSensorMode.AmbientLight) t = 1 - t; + state.setValue(this.getMinValue(state) + t * (this.getMaxValue(state) - this.getMinValue(state))); + } + + getInnerView(parent: SVGSVGElement, globalDefs: SVGDefsElement) { + this.group = svg.elt("g") as SVGGElement; + + let gc = "gradient-color-" + this.getPort(); + const prevColorGradient = globalDefs.querySelector(`#${gc}`) as SVGLinearGradientElement; + this.colorGradient = prevColorGradient ? prevColorGradient : svg.linearGradient(globalDefs, gc, false); + svg.setGradientValue(this.colorGradient, "50%"); + svg.setGradientColors(this.colorGradient, "black", "yellow"); + + const reporterGroup = pxsim.svg.child(this.group, "g"); + reporterGroup.setAttribute("transform", `translate(${this.getWidth() / 2}, 20)`); + this.reporter = pxsim.svg.child(reporterGroup, "text", { 'text-anchor': 'middle', 'x': 0, 'y': '0', 'class': 'sim-text number large inverted' }) as SVGTextElement; + + const sliderGroup = pxsim.svg.child(this.group, "g"); + sliderGroup.setAttribute("transform", `translate(${this.getWidth() / 2 - this.getSliderWidth() / 2}, ${this.getReporterHeight()})`); + + const rect = pxsim.svg.child(sliderGroup, "rect", + { + "width": this.getSliderWidth(), + "height": this.getSliderHeight(), + "style": `fill: url(#${gc})` + } + ) + this.rect = rect; + + let pt = parent.createSVGPoint(); + let captured = false; + touchEvents(rect, ev => { + if (captured && (ev as MouseEvent).clientY) { + ev.preventDefault(); + this.updateColorLevel(pt, parent, ev as MouseEvent); + } + }, ev => { + captured = true; + if ((ev as MouseEvent).clientY) { + rect.setAttribute('cursor', '-webkit-grabbing'); + this.updateColorLevel(pt, parent, ev as MouseEvent); + } + }, () => { + captured = false; + rect.setAttribute('cursor', '-webkit-grab'); + }) + + return this.group; + } + } +} \ No newline at end of file diff --git a/sim/visuals/nodes/nxtLightSensorView.ts b/sim/visuals/nodes/nxtLightSensorView.ts new file mode 100644 index 00000000..890815f0 --- /dev/null +++ b/sim/visuals/nodes/nxtLightSensorView.ts @@ -0,0 +1,42 @@ +/// + +namespace pxsim.visuals { + export class NXTLightSensorView extends SensorView implements LayoutElement { + + private control: LightWheelControl; + + private static sensor_hole_id = 'led'; + + constructor(port: number) { + super(NXT_LIGHT_SENSOR_SVG, "color", NodeType.NXTLightSensor, port); + } + + protected optimizeForLightMode() { + (this.content.getElementById(this.normalizeId('box')) as SVGElement).style.fill = '#a8aaa8'; + } + + public getPaddingRatio() { + return 1 / 4; + } + + public updateState() { + super.updateState(); + + const lightState = ev3board().getInputNodes()[this.port]; + if (!lightState) return; + const mode = lightState.getMode(); + + if (mode == NXTLightSensorMode.ReflectedLightRaw || mode == NXTLightSensorMode.ReflectedLight) { + this.updateSensorLightVisual('#eb0c0c'); + } + } + + private updateSensorLightVisual(color: string) { + const sensorHole = this.content.getElementById(this.normalizeId(NXTLightSensorView.sensor_hole_id)) as SVGCircleElement; + sensorHole.style.stroke = color; + if (color != '#ffffff') { + sensorHole.style.strokeWidth = '2px'; + } + } + } +} \ No newline at end of file