UNPKG

tpmkms

Version:

Reusable libraries for Entodicton, a natural language to json converter

602 lines (520 loc) 20.4 kB
const { knowledgeModule, where } = require('./runtime').theprogrammablemind const { defaultContextCheck, getValue, setValue } = require('./helpers') const droneV1_tests = require('./drone_v1.test.json') const instance = require('./drone_v1.instance.json') const hierarchy = require('./hierarchy') const ordinals = require('./ordinals') const nameable = require('./nameable') const rates = require('./rates') const help = require('./help') const { degreesToRadians, radiansToDegrees, cartesianToPolar } = require('./helpers/drone') /* todo VOSK https://alphacephei.com/vosk/ FreeNode Tank https://docs.freenove.com/projects/fnk0077/en/latest/fnk0077/codes/tutorial/3_Module_test_%28necessary%29.html https://www.amazon.ca/Freenove-Raspberry-Tracking-Avoidance-Ultrasonic/dp/B0BNDQFRP1/ref=sr_1_1_sspa?crid=1JT788RT84O8C&dib=eyJ2IjoiMSJ9.1W6XTWnHwPcqZTD8iRfmF7hHwiVycHjB02NHKEcqGfQSUKyJfN0OLyaaoCcypQug_C9CGah-7wLgfAtJRs_JKiwDsqYXqFfvvoU5ETBk_Le-S9Qt4kwh92r0w19bzA5my7aQpT52ssw8-f8Xpzjbqm1uFsLh82jF4V7P8xMKobjVHHILXalReEPuJz2OlF6y_ihwtUuVLDjMkuvNPoK-M7YLntLqKQy229XKjtDSUV4J0YT1L8uLVWHZ-ySs_MmG_w-oyZ9QFIe0a9hJEMuiu_BcaDmxFkwMeGBro2uczAU.NlqF_FH_6PvflZKozPylFlIyKuwx7mAB-jAggC1aPFk&dib_tag=se&keywords=Freenove+Tank+Robot+Kit&qid=1766258114&sprefix=freenove+drone+robot+kit%2Caps%2C130&sr=8-1-spons&sp_csd=d2lkZ2V0TmFtZT1zcF9hdGY&psc=1 send commands to arduino from another computer: https://www.npmjs.com/package/johnny-five KEYESTUDIO Mini Tank Robot V2 Smart Car Kit for Arduino https://www.amazon.ca/KEYESTUDIO-Infrared-Ultrasonic-Obstacle-Avoidance/dp/B07X4W7SZ5/ref=sr_1_10?crid=2A71NHZNTAION&dib=eyJ2IjoiMSJ9.W-I4I_tfyGdGt2UrNlNrlFeKnfIwppniNSX5FJndx77Ht944f9RylJD9me0PiqV5V_b185b17BsrPdKxmYYHnJ-Odb7hbdVzKs019mag1nCL-Wqe4aR0IYrEOzJkKTnR4YbXGYwriLd26OBYjhNvgaCFyE5uwsYkAK-qJXI2Xiui19oLiLYrmJvBz0bCHe4s7U6OdmaumYhhfxpVErk1E1zAwxE8kdq_YD7ZCMRjKS9Tr6cbayIh9GKDwMLuW-LCdzOW2eQx-dTB7yXV53rpV34IBAcCE1IgmwwNIIW7E6Y.fdaAuj4qvXq-67f5ktOq7Coo8lggrMiB_TFFtluqDtI&dib_tag=se&keywords=Adafruit+Mini+Round+Robot+Chassis+Kit&qid=1766256581&sprefix=adafruit+mini+round+robot+chassis+kit+%2Caps%2C123&sr=8-10 https://github.com/ericmend/mini-drone/blob/master/README.md vosk https://github.com/sunfounder/picar-x/tree/v2.0/gpt_examples https://github.com/sunfounder/picar-x/tree/v2.0/example VOSK: https://docs.sunfounder.com/projects/picar-x-v20/en/latest/ai_interaction/python_voice_control.html DONE why is 3 meters not marker: length its marker dimension DONE how to handle time in the testing repeat that/what/say again/say that again make it say the howToCalibrate right from the start. maybe have some prime it call?!?!?! convert from length to a some kind of standard number shut up/dont talk/be quiet -> stop saying responses use it to measure distances -> go forward. stop. how far was that call this point a move 5 feet call this point b moving between a and b is called patrol 1 do patrol 1 every 3 minutes what is patrol 1 calibrate distance stop go forward slowly stop that was 2 feet do a circle 2 feet in diameter every 2 minutes this is jeff say hi say your names car 1 do patrol one car 1 and 2 go to point a who car 1 just say the speed go forward 2 meters pan camera left slowly do the cylon every 30 seconds patrol one is forward 10 feet left 2 feet right 3 feet and back to the start go forward 10 feet then go back to the start go to the last point go back 2 positions call the first point start call the second point fred call the last point june call the next point albert pause for 4 seconds */ /* ^ y | 90 | -180/180 <--^--> 0 -- x -> | -90 */ class API { initialize({ objects }) { this._objects = objects this._objects.defaultTime = { hour: 9, minute: 0, second: 0, millisecond: 0 } this._objects.ordinal = 0 delete this.testDate objects.calibration = { startTime: undefined, // start time for calibration endTime: undefined, // end time for calibration duration: undefined, // end time - start time distance: undefined, // distance travelled during calibration in mm power: 0.1, speed: undefined, // meters per second } objects.current = { power: 0.1, angleInDegrees: 0 //easier to debug // direction: undefined, // direction to go if going // power: undefined, // power // ordinal // ordinal of the current point or the current point that the recent movement started at } objects.history = [] objects.isCalibrated = false } isCalibrated() { return this._objects.isCalibrated } nextOrdinal() { return this._objects.ordinal += 1 } async currentPoint() { if (!this._objects.current.endTime) { return null // in motion } const ordinal = this._objects.current.ordinal const lastPoint = await this.args.recall({ context: { marker: 'point' }, condition: (context) => context.ordinal == ordinal }) const durationInSeconds = (this._objects.current.endTime - this._objects.current.startTime) / 1000 const speedInMetersPerSecond = (this._objects.current.power / this._objects.calibration.power) * this._objects.calibration.speed const direction = this._objects.current.direction const distanceInMeters = speedInMetersPerSecond * durationInSeconds * (direction == 'forward' ? 1 : -1) const angleInRadians = degreesToRadians(this._objects.current.angleInDegrees) const yPrime = lastPoint.point.y + distanceInMeters * Math.sin(angleInRadians) const xPrime = lastPoint.point.x + distanceInMeters * Math.cos(angleInRadians) return { x: xPrime, y: yPrime } } async markCurrentPoint() { const ordinal = this.nextOrdinal() const point = await this.currentPoint() this.args.remember({ marker: 'point', ordinal, point }) this._objects.current.ordinal = ordinal this._objects.current.endTime = null this._objects.current.startTime = null } now() { if (this.args.isProcess || this.args.isTest) { if (!this.testDate) { this.testDate = new Date(2025, 5, 29, 14, 52, 0) } this.testDate = new Date(this.testDate.getTime() + 1000) return this.testDate } else { return new Date() } } // this is for testing pause(duration_in_seconds) { this._objects.history.push({ marker: 'history', pause: duration_in_seconds }) this.testDate = new Date(this.testDate.getTime() + (duration_in_seconds-1)*1000) } async sendCommand() { const stopAtDistance = async (distanceMeters) => { const speed_meters_per_second = this._objects.calibration.speed const duration_seconds = distanceMeters / speed_meters_per_second this.pause(duration_seconds) this.stop() await this.markCurrentPoint() } if (this._objects.current.destination) { const currentPoint = await this.args.recall({ context: { marker: 'point' } }) const polar = cartesianToPolar(currentPoint.point, this._objects.current.destination.point) const destinationAngleInDegrees = radiansToDegrees(polar.angle) let angleDelta = destinationAngleInDegrees - this._objects.current.angleInDegrees if (angleDelta > 180) { angleDelta -= 360 } else if (angleDelta < -180) { angleDelta += 360 } this.rotate(angleDelta) this.forward(this._objects.current.power) await stopAtDistance(polar.radius) return } const command = { power: this._objects.current.power, ...this._objects.current } switch (command.direction) { case 'forward': this.forward(command.power) break case 'backward': this.backward(command.power) break case 'right': this.rotate(-90) break case 'left': this.rotate(90) break case 'around': this.rotate(180) break } if (command.distance) { const distanceMeters = command.distance await stopAtDistance(distanceMeters) } } forward(power) { const time = this.forwardDrone(power) this._objects.current.startTime = time this._objects.current.endTime = null return time } backward(power) { const time = this.backwardDrone(power) this._objects.current.startTime = time this._objects.current.endTime = null return time } // TODO allow saying turn while its moving and make that one moves so you can go back wiggly? rotate(angleInDegrees) { this.rotateDrone(angleInDegrees) this._objects.current.angleInDegrees = (this._objects.current.angleInDegrees + angleInDegrees) % 360 } tiltAngle(angle) { tiltAngleDrone(angle) } panAngle(angle) { panAngleDrone(angle) } stop() { const time = this.stopDrone() this._objects.current.endTime = time return time } // subclass and override the remaining to call the drone forwardDrone(power) { const time = this.now() this._objects.history.push({ marker: 'history', direction: 'forward', power, time }) return time } backwardDrone(power) { const time = this.now() this._objects.history.push({ marker: 'history', direction: 'backward', power, time }) return time } // -angle is counterclockwise // +angle is clockwise rotateDrone(angle) { this._objects.history.push({ marker: 'history', turn: angle }) } tiltAngleDrone(angle) { } panAngleDrone(angle) { } stopDrone() { const time = this.now() this._objects.history.push({ marker: 'history', power: 0, time }) return time } } const howToCalibrate = "When you are ready say calibrate. The drone will drive forward at 10 percent power then say stop. Measure the distance and tell me that. Or you can say the speed of the drone at percentage of power." function askForProperty({ ask, propertyPath, contextPath=[], query, matchr, oneShot=false, }) { ask({ where: where(), oneShot, matchq: ({ api, context, objects }) => !getValue(propertyPath, objects) && context.marker == 'controlEnd', applyq: async ({ say, objects }) => { return query }, matchr, applyr: async ({objects, context}) => { setValue(propertyPath, objects, getValue(contextPath, context)) }, }) } function askForCalibrationDistance(args) { askForProperty({ ...args, propertyPath: ['calibration', 'distance'], query: "How far did the drone go?", matchr: ({context, objects}) => objects.calibration.endTime && context.marker == 'quantity' && context.unit.dimension == 'length', }) } function askForEndTime(args) { askForProperty({ ...args, propertyPath: ['calibration', 'endTime'], query: "Say stop when the drone has driven enough.", matchr: () => false, }) } function askForStartTime(args) { askForProperty({ ...args, propertyPath: ['calibration', 'startTime'], query: howToCalibrate, matchr: () => false, }) } // expectProperty function expectDirection(args) { args.config.addSemantic({ match: ({context, isA}) => isA(context.marker, 'direction'), apply: ({objects, context}) => { objects.runCommand = true objects.current.direction = context.marker } }) } // expectProperty function expectDistanceForCalibration(args) { args.config.addSemantic({ oneShot: true, match: ({context, isA, objects}) => isA(context.marker, 'quantity') && !isA(context.unit.marker, 'unitPerUnit') && objects.calibration.startTime, apply: async ({context, objects, fragments, e}) => { const instantiation = await fragments("quantity in meters", { quantity: context }) const result = await e(instantiation) objects.calibration.distance = result.evalue.amount.evalue.evalue } }) } function expectDistanceForMove(args) { // TODO save id for recalibration args.config.addSemantic({ match: ({context, isA}) => isA(context.marker, 'quantity') && !isA(context.unit.marker, 'unitPerUnit'), apply: async ({context, objects, fragments, e}) => { const instantiation = await fragments("quantity in meters", { quantity: context }) const result = await e(instantiation) objects.runCommand = true objects.current.distance = result.evalue.amount.evalue.evalue } }) } function expectCalibrationCompletion(args) { args.config.addSemantic({ oneShot: true, match: ({context, objects, isA}) => context.marker == 'controlEnd' && objects.calibration.distance && objects.calibration.duration && !objects.calibration.speed, apply: ({api, context, objects, _continue, say, remember}) => { objects.calibration.speed = objects.calibration.distance / objects.calibration.duration objects.isCalibrated = true say(`The drone is calibrated. The speed is ${objects.calibration.speed.toFixed(4)} meters per second at 10 percent power`) const ordinal = api.nextOrdinal() remember({ marker: 'point', ordinal, point: { x: objects.calibration.distance, y: 0 }, distance: objects.calibration.distance, description: "calibration stop" }) objects.current.ordinal = ordinal _continue() expectDistanceForMove(args) } }) } const template = { fragments: [ "quantity in meters", "quantity in meters per second", ], configs: [ "drone is a concept", //TODO "forward left, right, backward are directions", "around, forward, left, right, and backward are directions", "speed and power are properties", "point is a concept", // TODO fix/add this "position means point", "points are nameable orderable and memorable", (args) => { askForCalibrationDistance(args) askForEndTime(args) askForStartTime(args) expectDirection(args) expectDistanceForCalibration(args) expectCalibrationCompletion(args) args.config.addSemantic({ match: ({context, isA}) => isA(context.marker, 'quantity') && isA(context.unit.marker, 'unitPerUnit'), apply: async ({context, objects, api, fragments, e}) => { // send a command to the drone const instantiation = await fragments("quantity in meters per second", { quantity: context }) const result = await e(instantiation) const desired_speed = result.evalue.amount.evalue.evalue const desired_power = objects.current.power * (desired_speed / objects.calibration.speed) objects.runCommand = true objects.current.power = desired_power } }) args.config.addSemantic({ match: ({context, objects, isA}) => objects.current.direction && objects.isCalibrated && context.marker == 'controlStart', apply: ({context, objects, api}) => { objects.runCommand = false } }) args.config.addSemantic({ // match: ({context, objects, isA}) => objects.current.direction && objects.isCalibrated && (context.marker == 'controlEnd' || context.marker == 'controlBetween'), match: ({context, objects, isA}) => objects.current.direction && objects.isCalibrated && context.marker == 'controlEnd', apply: async ({context, objects, api}) => { // send a command to the drone if (objects.runCommand) { // debugger await api.sendCommand() } } }) }, { operators: [ "([calibrate])", "([turn] (direction))", "([pause] ([number]))", "([stop] ([drone|])?)", "([go])", "([toPoint|to] (point))", ], bridges: [ { id: "toPoint", isA: ['preposition'], bridge: "{ ...next(operator), operator: operator, point: after[0], interpolate: [{ property: 'operator' }, { property: 'point' }] }", semantic: async ({objects, api, e, context}) => { if (api.isCalibrated()) { objects.runCommand = true const point = await e(context.point) objects.current.destination = point.evalue } } }, { id: "go" }, { id: 'turn', isA: ['verb'], bridge: "{ ...next(operator), direction: after[0], interpolate: [{ context: operator }, { property: 'direction' }] }", semantic: ({context, objects, api}) => { objects.runCommand = true objects.current.direction = context.direction.marker }, // check: { marker: 'turn', exported: true, extra: ['direction'] } }, { id: 'calibrate', isA: ['verb'], bridge: "{ ...next(operator), interpolate: [{ context: operator }] }", semantic: ({context, objects, api, remember}) => { objects.current.direction = 'forward' const startTime = api.forward(objects.current.power) objects.calibration.startTime = startTime const ordinal = api.nextOrdinal() remember({ marker: 'point', ordinal, point: { x: 0, y: 0 }, description: "calibration start" }) objects.current.ordinal = ordinal } }, { id: 'pause', isA: ['verb'], bridge: "{ ...operator, time: after[0], interpolate: [{ context: operator }, { property: 'time' }] }", semantic: async ({context}) => { // why doesn't nodejs add a sleep function. I always have to look up how to do this because its not fucking memorable. // function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } // await sleep(context.time.value*1000) } }, { id: 'stop', isA: ['verb'], optional: { 1: "{ marker: 'drone' }", }, bridge: "{ ...next(operator), object: after[0], interpolate: [{ context: operator }, { property: 'object' }] }", semantic: async ({remember, context, objects, api, say}) => { if (!objects.calibration.startTime) { return // ignore } if (objects.calibration.speed) { /* const stopTime = api.stop() const ordinal = api.nextOrdinal() const point = api.currentPoint() remember({ marker: 'point', ordinal, point }) objects.current.ordinal = ordinal */ api.stop() await api.markCurrentPoint() } else { const stopTime = api.stop() objects.calibration.endTime = stopTime objects.calibration.duration = (objects.calibration.endTime - objects.calibration.startTime)/1000 } } }, ], generators: [ { match: ({context}) => context.marker == 'help' && !context.paraphrase && context.isResponse, apply: () => '' }, ], semantics: [ ], }, ], } knowledgeModule( { config: { name: 'drone_v1' }, includes: [nameable, ordinals, hierarchy, rates, help], api: () => new API(), module, description: 'controlling a drone. this is version one with a more complicated calibration. the drone has a distance sensor to I am going to write against that. I keeps this because I implemented new features for it and its acting like an integration test.', test: { name: './drone_v1.test.json', contents: droneV1_tests, checks: { context: [ defaultContextCheck({ marker: 'point', exported: true, extra: [ 'ordinal', { property: 'point', check: ['x', 'y'] }, 'description', { property: 'namespaced', check: [{ property: 'stm', check: ['id', 'names'] }] }, ] }), defaultContextCheck({ marker: 'turn', exported: true, extra: ['direction'] }), defaultContextCheck({ marker: 'history', exported: true, extra: ['pause', 'direction', 'power', 'turn', 'time'] }), defaultContextCheck(), ], objects: [ { km: 'stm' }, { path: ['isCalibrated'] }, { path: ['calibration'] }, { path: ['history'] }, { path: ['current'] }, { path: ['runCommand'] }, ], } }, instance, template, })