tpmkms
Version:
Reusable libraries for Entodicton, a natural language to json converter
1,400 lines (1,234 loc) • 56.1 kB
JavaScript
const { knowledgeModule, where, debug } = require('./runtime').theprogrammablemind
const { conjugateVerb } = require('./english_helpers')
const { OverrideCheck, defaultContextCheckProperties, defaultContextCheck, getValue, setValue } = require('./helpers')
const drone_tests = require('./drone.test.json')
const instance = require('./drone.instance.json')
const hierarchy = require('./hierarchy')
const ordinals = require('./ordinals')
const nameable = require('./nameable')
const compass = require('./compass')
const actions = require('./actions')
const angle = require('./angle')
const rates = require('./rates')
const help = require('./help')
const { rotateDelta, degreesToRadians, radiansToDegrees, cartesianToPolar, smallestRotate } = require('./helpers/drone')
/*
NEED TO CHECK ON ACTUAL DRONE
again 2 times
2 more times
do that again 3 times
do route 1 skipping point 2
base <number> is a pattern for base names
2 feet north and 1 foot east is called base 1
2 feet north 1 foot east is called base 1
2 feet north 1 foot east is base 1
DONE delete route 2 / delete that route / delete that
DONE maybe have status as the drone moves liek "patrolling route 1" etc
patrol back and forth 2 times
DONE fix DO so it can do all the stuff partol can do <<<<<<<<<<<<<<<<<<<
start again. start a new path
the last 3 points are called path 1
go forward 1 meter and then turn north
go forward 1 meter and turn north
stopping 2 seconds at each point
patrols x do that again
DONE go to the end of the patrol
DONE patrol x three times
patrol x continuously
patrol x for 5 minutes
go to the start
DONE node drone -q 'north 1 meter\neast 1 meter\ncall that route 2\nwhat is the second point of route 2' -g -
go to the start along the path / following the path
pausing 1 second at the first point and 5 at the last
DONE go to the second point of route 1
DONE do route 1 pausing 10 seconds at each point
what is the drone's position
DONE go back
go back another point
go back again
go back to the start
go back 2 points along route 1
go to the start of route 2
go forward 1 foot\nturn left\ngo forward again\n
do it again
TODO should there be two hierarchy one as a concept car is a vehicle and one as a word car is a noun
turn left\nturn back
DONE do route 1 pausing 10 seconds at each point
do route 1 pausing 1 second at point 1 and 2 seconds for the rest
forward 1 foot\nwest 1 foot\ngo back to the start <<<<<<<< turn the longer way not he shorter way
forward 1 foot\nwest 1 foot\ncall the path route 1\ngo to the start of route 1\npatrol route 1\npatrol route 1 <<<<< does the patrol more than once
call that route 1
what are the paths
list the paths
?? elipses of the verb go or some kind of conjunction?!?!?
go forward 1 meter turn right forward 2 meters stop
the patrol called patrol 1 is forward 1 meter pause 10 second west 1 meter pause again then back to the start
do a 5 second pause at each point / add a 10 second pause to each point / pause of 5 seconds
start again / start here / starting here / restart
again
go back to the start => follows path | go to the start => does not follow the path | go to the start following the path
go back 2 points/turns
go back and forth to the start!?!?
call the first point the start
180 degree turns not working
go 20 percent faster
lower and raise crane
round to 2 digits
DONE turn left go forward 1 foot <- the turn is lost
DONE call this patrol 1
DONE call this path patrol 1
DONE call that patrol 1
DONE call that path patrol 1
DONE the start of patrol 1
DONE the end of patrol 1
DONE turn right 45 degrees
DONE turn right 2 times
DONE turn right 2 times\nturn around <- no reset of times
DONE lower/raise the claw
DONE go to the start/end of patrol 1 not working
go north 1 foot east 1 foot south 1 foot west 1 foot. call this square 2. do square 2 two times
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
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
use it to measure distances -> go forward. stop. how far was that
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
this way is north
turn west
go three meters
turn south
go 1 foot
forward for 4 seconds
you are facing north. patrol between here and 100 feet to the west
this way is south
go forward for 1 second\nbackward 2 meters (implicit stop)
go back
go back and forth 3 times
go back and forth 2 meters
go back and forth 1 second
what is the default speed
increase that by 2 times/20 percent/2 inches per second
amke the default speed 2 times faster
double the default speed
# what is the speed
use inches per second and repeat that
*/
function expectDirection(args) {
args.config.addSemantic({
match: ({context, isA}) => isA(context.marker, 'direction') && !context.evaluate,
apply: ({objects, context}) => {
objects.runCommand = true
objects.current.direction = context.marker
}
})
}
// it's lazy day
const compassToRadians = {
'north': Math.PI / 2, // 90° → north
'northeast': Math.PI / 4, // 45° → northeast
'east': 0, // 0° → east
'southeast': -Math.PI / 4, // -45° → southeast
'south': -Math.PI / 2, // -90° → south
'southwest': -3 * Math.PI / 4, // -135° → southwest
'west': Math.PI, // 180° → west (or -Math.PI, same angle)
'northwest': 3 * Math.PI / 4 // 135° → northwest
};
function roundTo(num, decimals = 2) {
const factor = 10 ** decimals; // or Math.pow(10, decimals)
return Math.round(num * factor) / factor;
}
async function handleDistance(args, distance) {
const {objects, fragments, e, say, gp} = args
if (!distance) {
distance = args.context
}
const instantiation = await fragments("quantity in meters", { quantity: distance })
try {
const result = await e(instantiation)
objects.runCommand = true
objects.current.distance = result.evalue.amount.evalue.evalue
} catch (e) {
say(`Don't know how to interpret ${await gp(distance)} in meters`)
}
}
function expectDistanceForMove(args) {
args.config.addSemantic({
match: ({context, isA}) => isA(context.marker, 'quantity') && context.unit && !isA(context.unit.marker, 'unitPerUnit'),
match: ({context, isA}) => isA(context.marker, 'quantity') && context.unit && isA(context.unit.dimension, 'length'),
apply: async (args) => {
await handleDistance(args)
}
})
}
/*
90R means 90 degrees in radians
N
^
y
|
90R
|
W -180R/180R <--^--> 0R -- x -> E
|
-90R
S
*/
/*
L = track separation width (distance between the centers of the two tracks, measured side-to-side, in meters or whatever unit you like)
v = ground speed of each track (in m/s) — assume same magnitude but opposite directionsleft track forward at +v
right track backward at -v (or vice versa for the other direction)
θ = desired turn angle in radians (convert degrees to radians with θ_rad = θ_deg × π / 180)
The angular velocity ω (how fast the tank rotates, in rad/s) is:
ω = 2v / L
The time t needed to turn by angle θ is:
t = θ / ω = (θ × L) / (2v)
*/
class API {
constructor() {
const overrideMethods = Object.getOwnPropertyNames(API.prototype).filter(key => typeof API.prototype[key] === 'function' && key.endsWith('Drone'));
this.overrideCheck = new OverrideCheck(API, overrideMethods)
this.overriden = this.constructor !== API
this.startPoint = { x: 0, y: 0 }
this.startAngle = Math.PI/2
this.startCompass = 'north'
}
setStartPoint(point) {
this.startPoint = point
}
setStartAngle(angleInRadians, compass) {
this.startAngle = angleInRadians
this.startCompass = compass
}
setSpeed(metersPerSecond) {
this._objects.current.speed = metersPerSecond
}
initialize({ objects }) {
if (this.overriden) {
this.overrideCheck.check(this)
}
if (this.minimumSpeedDrone() == null) {
throw new Error(`minimumSpeedDrone is not returning a positive number. Its returning ${this.minimumSpeedDrone()}`)
}
if (this.maximumSpeedDrone() == null) {
throw new Error(`maximumSpeedDrone is not returning a positive number. Its returning ${this.maximumSpeedDrone()}`)
}
this._objects = objects
this._objects.defaultTime = { hour: 9, minute: 0, second: 0, millisecond: 0 }
delete this.testDate
objects.current = {
angleInRadians: this.startAngle,
path: [],
speed: this.minimumSpeedDrone(),
ordinal: 0, // ordinal of the current point or the current point that the recent movement started at
compass: this.startCompass,
direction: 'forward',
}
objects.history = []
objects.sonicTest = 5
this.args.remember({ marker: 'point', ordinal: this.nextOrdinal(), point: this.startPoint, description: "start" })
}
currentOrdinal() {
return this._objects.current.ordinal
}
nextOrdinal() {
return this._objects.current.ordinal += 1
}
async currentPoint() {
const current = this._objects.current
if (current.durationInSeconds) {
// okay
} else if (!current.startTime || !current.endTime) {
return null // in motion
}
const ordinal = this.currentOrdinal()
const lastPoint = await this.args.recall({ context: { marker: 'point' }, condition: (context) => context.ordinal == ordinal })
if (!current.startTime && !current.endTime && !current.durationInSeconds) {
return lastPoint // did not move
}
let durationInSeconds
if (current.durationInSeconds) {
durationInSeconds = current.durationInSeconds
} else {
durationInSeconds = (current.endTime - current.startTime) / 1000
}
const speedInMetersPerSecond = current.speed
const direction = current.direction
const distanceInMeters = Math.abs(speedInMetersPerSecond * durationInSeconds * (direction == 'forward' ? 1 : -1))
const angleInRadians = current.angleInRadians
const yPrime = roundTo(lastPoint.point.y + distanceInMeters * Math.sin(angleInRadians), 2)
const xPrime = roundTo(lastPoint.point.x + distanceInMeters * Math.cos(angleInRadians), 2)
return { x: xPrime, y: yPrime }
}
async markCurrentPoint() {
const point = await this.currentPoint()
if (!point) {
return
}
const ordinal = this.nextOrdinal()
this.args.remember({ marker: 'point', ordinal, point })
this._objects.current.endTime = null
this._objects.current.startTime = null
}
async sendCommand() {
if (!this._objects.runCommand) {
return
}
delete this._objects.runCommand
const objects = this._objects
const { fragments, e, say, gr } = this.args
async function quantityInMeters(from, to) {
const fi = await fragments("quantity in meters")
return await fi.instantiate([
{
match: ({path, pathEquals}) => pathEquals(path, ['0', 'from']),
apply: ({context}) => Object.assign(context, from),
},
{
match: ({path, pathEquals}) => pathEquals(path, ['0', 'to']),
apply: ({context}) => Object.assign(context, to),
},
])
}
// TODO account for forward vs backward speed
const minimumSpeed = this.minimumSpeedDrone()
if (objects.current.speed < minimumSpeed) {
const unitsOfUser = objects.current.speedUnitsOfUser
const minimumValueInDroneUnits = await fragments("number meters per second", { number: { marker: 'integer', value: minimumSpeed } })
// const valueInUsersUnits = await fragments("quantity in meters", { quantity: minimumValueInDroneUnits, meter: unitsOfUser })
if (unitsOfUser) {
const valueInUsersUnits = await quantityInMeters(minimumValueInDroneUnits, unitsOfUser )
const evaluated = await e(valueInUsersUnits)
say(`The drone cannot go that slow. The minimum speed is ${await gr(evaluated.evalue)}`)
} else {
say(`The drone cannot go that fast. The minimum speed is ${await gr(minimumValueInDroneUnits)}`)
}
objects.runCommand = false
objects.current.speed = minimumSpeed
return
}
// TODO account for forward vs backward speed
const maximumSpeed = this.maximumSpeedDrone()
if (objects.current.speed > maximumSpeed) {
const unitsOfUser = objects.current.speedUnitsOfUser
const maximumValueInDroneUnits = await fragments("number meters per second", { number: { marker: 'integer', value: maximumSpeed } })
// const valueInUsersUnits = await fragments("quantity in meters", { quantity: maximumValueInDroneUnits, meter: unitsOfUser })
if (unitsOfUser) {
const valueInUsersUnits = await quantityInMeters(maximumValueInDroneUnits, unitsOfUser)
const evaluated = await e(valueInUsersUnits)
say(`The drone cannot go that fast. The maximum speed is ${await gr(evaluated.evalue)}`)
} else {
say(`The drone cannot go that fast. The maximum speed is ${await gr(maximumValueInDroneUnits)}`)
}
objects.runCommand = false
objects.current.speed = minimumSpeed
return
}
const stopAtDistance = async (direction, distanceMeters) => {
const speed_meters_per_second = objects.current.speed
objects.current.durationInSeconds = distanceMeters / speed_meters_per_second
await this.pause(objects.current.durationInSeconds, { batched: true })
await this.stop({ batched: true })
await this.markCurrentPoint()
}
if (objects.current.path.length > 0) {
let currentPoint = (await this.args.recall({ context: { marker: 'point' } })).point
this._objects.history.push({ marker: 'history', debug: 'doing path' })
for (const [pathIndex, pathComponent] of objects.current.path.entries()) {
if (pathComponent.message) {
this.messageDrone(pathComponent.message)
} else if (pathComponent.repeatStart) {
if (objects.current.timeRepeats) {
this.startRepeats(objects.current.timeRepeats)
}
} else if (pathComponent.marker == 'pause') {
this.pause(pathComponent.pauseSeconds, { batched: true })
} else {
const points = this.args.toArray(pathComponent)
// const destinationPoint = pathComponent.point
const destinationPoint = points[0].point || points[0]
if (currentPoint.x == destinationPoint.x && currentPoint.y == destinationPoint.y) {
// already there
} else {
const polar = cartesianToPolar(currentPoint, destinationPoint)
const destinationAngleInRadians = polar.angle
// const angleDelta = (destinationAngleInRadians - objects.current.angleInRadians)
const angleDelta = rotateDelta(objects.current.angleInRadians, destinationAngleInRadians)
await this.rotate(angleDelta, { batched: true })
if (!pathComponent.aimOnly) {
await this.forward(objects.current.speed, { batched: true })
await stopAtDistance("forward", polar.radius)
currentPoint = destinationPoint
}
}
}
}
if (objects.current.timeRepeats) {
await this.endRepeats()
}
await this.sendBatch()
objects.current.path = []
objects.current.timeRepeats = 0
objects.current.durationInSeconds = 0
return
}
const command = { speed: objects.current.speed, ...objects.current }
if (this.args.hierarchy.isA(command.direction, 'compass_direction') || ['left', 'right', 'around'].includes(command.direction)) {
let delta = 0
if (command.direction == 'right') {
delta = -objects.current.turnAngle || -Math.PI/2
} else if (command.direction == 'left') {
delta = objects.current.turnAngle || Math.PI/2
} else if (command.direction == 'around') {
delta = Math.PI
} else {
delta = rotateDelta(objects.current.angleInRadians, compassToRadians[command.direction])
}
const repeats = objects.current.timeRepeats || 1
for (let i = 0; i < repeats; ++i) {
await this.rotate(delta, { batched: true })
}
if (!objects.current.justTurn) {
await this.forward(command.speed, { batched: command.distance })
} else {
await this.sendBatch()
}
delete objects.current.justTurn
delete objects.current.turnAngle
delete objects.current.timeRepeats
} else {
switch (command.direction) {
case 'forward':
case undefined:
await this.forward(command.speed, { batched: command.distance })
break
case 'backward':
await this.backward(command.speed, { batched: command.distance })
break
case 'right':
await this.rotate(-Math.PI/2)
break
case 'left':
await this.rotate(Math.PI/2)
break
case 'around':
await this.rotate(Math.PI)
break
}
}
if (command.distance > 0) {
const distanceMeters = command.distance
await stopAtDistance(command.direction, distanceMeters)
await this.sendBatch()
objects.current.durationInSeconds = 0
objects.current.distance = 0
}
}
async startRepeats(n) {
await this.startRepeatsDrone(n)
}
async endRepeats(n) {
await this.endRepeatsDrone()
}
async sendBatch() {
await this.sendBatchDrone()
}
async armAction(action) {
return this.armActionDrone(action)
}
async clawAction(action) {
return this.clawActionDrone(action)
}
async forward(speed, options) {
await this.forwardDrone(speed, options)
const time = this.now()
this._objects.current.startTime = time
this._objects.current.endTime = null
return time
}
async backward(speed, options) {
await this.backwardDrone(speed, options)
const time = this.now()
this._objects.current.startTime = time
this._objects.current.endTime = null
return time
}
async back(points) {
const objects = this._objects
let ordinal
if (points?.quantity?.value) {
ordinal = this.currentOrdinal() - points.quantity?.value
} else {
ordinal = this.currentOrdinal() - 1
}
const lastPoint = await this.args.recall({ context: { marker: 'point' }, condition: (context) => context.ordinal == ordinal })
if (!lastPoint) {
this.args.say(`There is no previous point to go back to`)
return
}
objects.current.path.push(lastPoint)
objects.runCommand = true
}
async backAndForth() {
const objects = this._objects
const current = objects.current
current.backAndForth = true
const ordinal = this.currentOrdinal()
const currentPoint = await this.args.recall({ context: { marker: 'point' }, condition: (context) => context.ordinal == ordinal })
const lastPoint = await this.args.recall({ context: { marker: 'point' }, condition: (context) => context.ordinal == ordinal-1 })
current.path.push({ repeatStart: true })
current.path.push(lastPoint)
current.path.push(currentPoint)
objects.runCommand = true
}
async sonic() {
return await this.sonicDrone()
}
// TODO allow saying turn while its moving and make that one moves so you can go back wiggly?
async rotate(angleInRadians, options) {
let shortestRotate = angleInRadians
if (shortestRotate > Math.PI) {
shortestRotate = shortestRotate - Math.PI*2
} else if (shortestRotate < -Math.PI) {
shortestRotate = shortestRotate + Math.PI*2
}
await this.rotateDrone(angleInRadians, options)
this._objects.current.angleInRadians = smallestRotate(this._objects.current.angleInRadians + angleInRadians)
}
async tiltAngle(angle) {
await tiltAngleDrone(angle)
}
async panAngle(angle) {
await panAngleDrone(angle)
}
async stop(options) {
await this.stopDrone(options)
const time = this.now()
if (this._objects.current.startTime) {
this._objects.current.endTime = time
}
return time
}
async pause(durationInSeconds, options) {
await this.pauseDrone(durationInSeconds, options)
}
async messageDrone(message) {
this._objects.history.push({ marker: 'message', message })
}
// subclass and override the remaining to call the drone
async armActionDrone(action) {
this._objects.history.push({ marker: 'history', armAction: action })
}
async clawActionDrone(action) {
this._objects.history.push({ marker: 'history', clawAction: action })
}
async startRepeatsDrone(n) {
this._objects.history.push({ marker: 'history', repeats: n })
}
async endRepeatsDrone(n) {
this._objects.history.push({ marker: 'endRepeats', })
}
async sendBatchDrone(durationInSeconds, options) {
this._objects.history.push({ marker: 'sendBatch', pause: durationInSeconds, ...options })
}
async pauseDrone(durationInSeconds, options) {
this._objects.history.push({ marker: 'history', pause: durationInSeconds, time: this.now(true, durationInSeconds*1000), ...options })
if (false) {
this.testDate = new Date(this.testDate.getTime() + (durationInSeconds-1)*1000)
} else {
this.testDate = new Date(this.now(false, 0).getTime() + (durationInSeconds-1)*1000)
}
}
// meters per second
minimumSpeedDrone() {
return 0.25
}
// meters per second
maximumSpeedDrone() {
return 1.2
}
now(lookahead = false, pause = 1000) {
if (this.args.isProcess || this.args.isTest) {
if (!this.testDate) {
this.testDate = new Date(2025, 5, 29, 14, 52, 0)
}
if (lookahead) {
return new Date(this.testDate.getTime() + pause)
} else {
this.testDate = new Date(this.testDate.getTime() + pause)
return this.testDate
}
} else {
return new Date()
}
}
// CMD_MOTOR#1000#1000#
async forwardDrone(speed, options) {
this._objects.sonicTest -= 10 // make the speed about the same as the actual drone
this._objects.history.push({ marker: 'history', direction: 'forward', speed, time: this.now(true), ...options })
}
async backwardDrone(speed, options) {
this._objects.sonicTest += 10 // make the speed about the same as the actual drone
this._objects.history.push({ marker: 'history', direction: 'backward', speed, time: this.now(true), ...options })
}
// -angle is counterclockwise
// +angle is clockwise
async rotateDrone(angleInRadians, options) {
this._objects.history.push({ marker: 'history', time: this.now(true), turn: angleInRadians, ...options })
}
// distance in cm
async sonicDrone() {
this._objects.history.push({ marker: 'history', sonic: this._objects.sonicTest })
return this._objects.sonicTest
}
async tiltAngleDrone(angle) {
}
async panAngleDrone(angle) {
}
async stopDrone(options) {
this._objects.history.push({ marker: 'history', speed: 0, time: this.now(true), ...options })
}
}
const howToCalibrate = "Put an object in front of the drone. When you are ready say calibrate."
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))
},
})
}
// 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
}
})
}
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
}
})
}
*/
const template = {
fragments: [
"quantity in meters",
"quantity in seconds",
"quantity in meters per second",
"number meters per second",
"quantity in units",
"number radians",
"40 degrees in radians",
"path",
"forward",
],
configs: [
"arm, claw and drone are concepts",
//TODO "forward left, right, backward are directions",
"around, forward, left, right, back, forth and backward are directions",
"paths are nameable and memorable",
"start and end are properties of path",
({apis}) => {
apis('properties').addHierarchyWatcher({
match: ({childId}) => childId == 'point',
apply: ({config, childId}) => {
config.updateBridge(childId, ({ bridge }) => {
if (!bridge.init) {
bridge.init = {}
}
bridge.init['notConjunctableWith'] = ['quantity']
})
}
})
},
"start and end are points",
"rest and remaining are concepts",
{
hierarchy: [
['point', 'distributable'],
['thisitthat', 'path'],
['path', 'action'],
],
},
"speed and power are properties",
"speed and power are comparable",
"speed is a quantity",
"point is a concept",
// TODO fix/add this "position means point",
"points are nameable orderable countable and memorable",
"drone modifies direction",
(args) => {
expectDirection(args)
expectDistanceForMove(args)
args.config.addSemantic({
match: ({context, isA}) => isA(context.marker, 'quantity') && isA(context.unit?.marker, 'unitPerUnit'),
apply: async ({context, objects, api, gr, fragments, e, say}) => {
// 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
objects.runCommand = true
objects.current.speed = desired_speed
objects.current.speedUnitsOfUser = context.unit
}
})
args.config.addSemantic({
match: ({context, objects, isA}) => objects.current.direction && context.marker == 'controlStart',
apply: ({context, objects, api}) => {
objects.runCommand = false
}
})
args.config.addSemantic({
match: ({context, objects, isA}) => objects.current.direction && context.marker == 'controlEnd',
apply: async ({context, objects, api}) => {
// send a command to the drone
if (objects.runCommand) {
await api.sendCommand()
}
}
})
},
{
operators: [
// "([do] (path))",
"([patrol] (path))",
"([lift|lift,raise] (@<= arm || @<= claw))",
"([lower] (@<= arm || @<=claw))",
"([open] (claw))",
"([close] (claw))",
"([startPath|start] (path))",
"([pathComponent])",
"([skipPoint|] (point))",
"(<another> (point))",
// "([turn] (direction))",
// "([pause] ([number]))",
"([stop] ([drone|])?)",
"([toPoint|to] (point))",
"([atPoint|at] (point))",
// "([forRest|for] (rest/* || remaining/*))",
"((context.unit.dimension == 'time') [timeAtPoint|] (atPoint))",
// "((context.unit.dimension == 'time') [timeForRest|] (forRest))",
],
bridges: [
{
id: 'skipPoint',
words: ['skip', 'skipping'],
isA: ['verb'],
bridge: "{ ...next(operator), points: after[0], operator: operator, interpolate: [{ property: 'operator' }, { property: 'points' }] }",
check: defaultContextCheckProperties(['points']),
},
{
id: 'skipPoint',
level: 1,
bridge: `{
...repeatable,
skip: operator,
checks: append(repeatable.checks, ['skip']),
repeatable: repeatable,
interpolate: [{ property: 'repeatable' }, { property: 'skip', byPosition: true }]
}`,
selector: {
loose: "repeatable",
arguments: {
repeatable: "(@<= 'repeatable')",
},
},
check: defaultContextCheckProperties(['repeatable', 'repeats'])
},
{
id: 'startPath',
isA: ['verb'],
bridge: "{ ...next(operator), path: after[0], operator: operator, interpolate: [{ property: 'operator' }, { property: 'path' }] }",
check: defaultContextCheckProperties(['path']),
semantic: async ({context, remember, recall, verbatim, g}) => {
const point = await recall({ context: { marker: 'point' } })
remember(context)
remember(point) // put the last point before the startPath so when the new path is generated this is the start point
verbatim(`New path started from ${await g(point)}`)
},
},
{
id: 'timeAtPoint',
before: ['verb'],
after: ['preposition'],
convolution: true,
bridge: "{ ...next(operator), time: before[0], point: after[0], operator: operator, interpolate: [{ property: 'time' }, { property: 'point' }] }",
check: defaultContextCheckProperties(['time', 'point']),
},
{
id: 'atPoint',
isA: ['preposition'],
bridge: "{ ...next(operator), point: after[0], operator: operator, interpolate: [{ property: 'operator' }, { property: 'point' }] }",
check: defaultContextCheckProperties(['point']),
},
{
id: 'another',
bridge: `{
...next(after[0]),
another: operator,
after: after[0],
interpolate: [ { property: 'another' }, { property: 'after' } ]
}`,
check: defaultContextCheckProperties(['another', 'after']),
},
{
id: 'pathComponent',
children: ['point', 'pause'],
},
{
id: 'patrol',
isA: ['verb', 'repeatable', 'action'],
bridge: `{
...next(operator),
operator: operator,
path: after[0],
interpolate: append(default(operator.interpolate, [{ property: 'operator'}]), [{ property: 'path' }])
}`,
check: defaultContextCheckProperties(['path']),
semantic: async ({context, g, e, toArray, fragments, toEValue, toFinalValue, recall, objects, verbatim}) => {
const paths = toArray(context.path)
for (const context_path of paths) {
const evaluated = await(e(context_path))
const path = toEValue(evaluated)
if (path.marker == 'answerNotKnown') {
verbatim(`${await g(context_path)} is not a known path`)
continue
}
objects.current.path.push({ message: await g(context) })
// TODO put this in a common place for use by do+patrol
// ordinal to pause time in seconds
const pauseTimeInSeconds = {}
if (context.pause) {
const timeAtPoints = toArray(context.pause.timeAtPoint)
for (const timeAtPoint of timeAtPoints) {
const instantiation = await fragments("quantity in seconds", { quantity: timeAtPoint.time })
const result = await e(instantiation)
const seconds = toFinalValue(toFinalValue(result).amount)
const points = await recall({ context: timeAtPoint.point.point, frameOfReference: path })
for (const point of toArray(points)) {
pauseTimeInSeconds[point.ordinal] = seconds
}
}
}
let skipPoints = []
if (context.skip) {
skipPoints = toArray(await recall({ context: context.skip.points, frameOfReference: path }))
}
// get to the start of the patrol
objects.current.path.push(path.points[0])
objects.current.path.push({ aimOnly: true, ...path.points[1] })
objects.current.path.push({ repeatStart: true })
for (const point of path.points) {
if (skipPoints.find((skipPoint) => skipPoint.ordinal == point.ordinal)) {
continue
}
objects.current.path.push(point)
if (pauseTimeInSeconds[point.ordinal]) {
objects.current.path.push({ marker: 'pause', pauseSeconds: pauseTimeInSeconds[point.ordinal] })
}
}
if (context.repeats) {
objects.current.timeRepeats = toFinalValue(context.repeats.repeats)
}
// if the patrol does not start and end at the same spot then
// go back to the start along the same path
// if the start is not the end of the patrol then go backwards along the patrol
if (JSON.stringify(path.points[0].point) !== JSON.stringify(path.points[path.points.length-1].point)) {
for (const point of [...path.points].reverse().slice(1)) {
if (skipPoints.find((skipPoint) => skipPoint.ordinal == point.ordinal)) {
continue
}
objects.current.path.push(point)
if (pauseTimeInSeconds[point.ordinal]) {
objects.current.path.push({ marker: 'pause', pauseSeconds: pauseTimeInSeconds[point.ordinal] })
}
}
const secondPoint = path.points[1]
objects.current.path.push({ ...secondPoint, aimOnly: true })
}
}
objects.runCommand = true
}
},
/*
{
id: 'do',
isA: ['verb'],
bridge: `{
...next(operator), operator: operator, path: after[0], interpolate: [{ property: 'operator'}, { property: 'path' }]
}`,
semantic: async ({context, e, toEValue, objects}) => {
const evaluated = await(e(context.path))
const path = toEValue(evaluated)
for (const point of path.points) {
objects.current.path.push(point)
}
objects.runCommand = true
}
},
*/
{
id: 'lift',
isA: ['verb'],
bridge: `{
...next(operator), operator: operator, object: after[0], interpolate: [{ property: 'operator'}, { property: 'object' }]
}`,
check: defaultContextCheckProperties(['object']),
semantic: ({api}) => {
api.armAction('up')
}
},
{
id: 'lower',
isA: ['verb'],
bridge: `{
...next(operator), operator: operator, object: after[0], interpolate: [{ property: 'operator'}, { property: 'object' }]
}`,
check: defaultContextCheckProperties(['object']),
semantic: ({api}) => {
api.armAction('down')
}
},
{
id: 'open',
isA: ['verb'],
bridge: `{
...next(operator), operator: operator, object: after[0], interpolate: [{ property: 'operator'}, { property: 'object' }]
}`,
check: defaultContextCheckProperties(['object']),
semantic: ({api}) => {
api.clawAction('open')
}
},
{
id: 'close',
isA: ['verb'],
bridge: `{
...next(operator), operator: operator, object: after[0], interpolate: [{ property: 'operator'}, { property: 'object' }]
}`,
check: defaultContextCheckProperties(['object']),
semantic: ({api}) => {
api.clawAction('close')
}
},
{
id: 'back',
isA: ['noun'],
semantic: async ({objects, api, e, context, say}) => {
await api.back()
}
},
{
id: "forth",
isA: ['noun'],
},
{
id: "toPoint",
isA: ['preposition'],
after: [['propertyOf', 1]],
bridge: "{ ...next(operator), operator: operator, point: after[0], interpolate: [{ property: 'operator' }, { property: 'point' }] }",
check: defaultContextCheckProperties(['point']),
semantic: async ({objects, api, e, context}) => {
objects.runCommand = true
const point = await e(context.point)
objects.current.path.push(point.evalue)
}
},
{
id: "go",
level: 0,
// convolution: true,
isA: ['verb', 'repeatable', 'doAction'],
words: [
...conjugateVerb('go'),
],
check: defaultContextCheckProperties(['direction', 'distance', 'to', 'points']),
bridge: `{
...next(operator),
distance: distance?,
direction: direction?,
points: points?,
to: to?,
operator: operator,
interpolate: append(default(operator.interpolate, [{ property: 'operator' }]), [{ property: 'direction' }, { property: 'points' }, { property: 'to' }, { property: 'distance' }])
}`,
selector: {
arguments: {
distance: "(@<= 'quantity' && context.unit.dimension == 'length')",
direction: "(@<= 'direction')",
to: "(@<= 'toPoint')",
points: "(@<= 'point')",
},
},
semantic: async (args) => {
const {api, toFinalValue, context, objects, e, toArray, toEValue} = args
if (context.distance) {
await handleDistance(args, context.distance)
}
if (context.direction) {
const array = toArray(context.direction)
if (array.length == 2 && array[0].marker == 'back' && array[1].marker == 'forth') {
await args.api.backAndForth()
} else if (context.direction.marker == 'back') {
await args.api.back(context.points)
} else {
objects.current.direction = context.direction.marker
}
}
if (context.repeats) {
objects.current.timeRepeats = toFinalValue(context.repeats.repeats)
}
if (context.to) {
const evaluation = await e(context.to.point)
const point = toEValue(evaluation)
objects.current.path.push(point)
}
objects.runCommand = true
await api.sendCommand()
},
},
{
id: 'turn',
isA: ['verb', 'action', 'repeatable'],
before: ['thenAction'],
words: ['turn'],
bridge: `{
...next(operator),
direction: direction,
operator: operator,
angle: angle?,
interpolate: append(default(operator.interpolate, [{ property: 'operator'}]), [{ property: 'direction' }, { property: 'angle' }])
}
`,
check: defaultContextCheckProperties(['direction', 'angle']),
selector: {
arguments: {
direction: "(@<= 'direction')",
repeats: "(@<= 'timeRepeats')",
angle: "(@<= 'quantity' && context.unit.dimension == 'angle')",
},
},
semantic: async ({context, fragments, objects, e, s, api, say, toFinalValue}) => {
const current = objects.current
current.direction = context.direction.marker
if (context.repeats) {
current.timeRepeats = toFinalValue(context.repeats.repeats)
}
async function toRadians(angle) {
const fi = await fragments("40 degrees in radians")
return await fi.instantiate([
{
match: ({path, pathEquals}) => pathEquals(path, ['0', 'from']),
apply: ({context}) => {
Object.assign(context, angle)
}
}
])
}
if (context.angle) {
const instantiation = await toRadians(context.angle)
try {
const result = await e(instantiation)
current.turnAngle = toFinalValue(result.evalue.amount)
objects.runCommand = true
} catch (e) {
say(`Don't know how to interpret ${await gp(context.angle)} in radians`)
return
}
}
if (context.repeats) {
objects.current.timeRepeats = toFinalValue(context.repeats.repeats)
}
current.justTurn = true
objects.runCommand = true
await api.sendCommand()
},
// check: { marker: 'turn', exported: true, extra: ['direction'] }
},
{
id: 'pause',
isA: ['verb'],
words: [
...conjugateVerb('pause'),
],
bridge: "{ ...next(operator), timeAtPoint: timeAtPoint?, time: or(time?, forTime?), operator: operator, atPoint: atPoint?, interpolate: [{ property: 'operator' }, { property: 'time' }, { property: 'forTime' }, { property: 'atPoint' }, { property: 'timeAtPoint' } ] }",
check: defaultContextCheckProperties(['timeAtPoint']),
selector: {
arguments: {
forTime: "(@<= 'forQuantity' && context.quantity.unit.dimension == 'time')",
time: "(@<= 'quantity' && context.unit.dimension == 'time')",
atPoint: "(@<= 'atPoint')",
timeAtPoint: "(@<= 'timeAtPoint')",
},
},
semantic: async ({context, remember, api, e, fragments, toFinalValue}) => {
let time = context.time
if (!time) {
return
}
if (time.marker == 'forQuantity') {
time = time.quantity
}
const instantiation = await fragments("quantity in seconds", { quantity: time})
const result = await e(instantiation)
const seconds = toFinalValue(toFinalValue(result).amount)
context.pauseSeconds = seconds
remember(context)
api.pause(seconds)
}
},
{
id: 'pause',
level: 1,
bridge: "{ ...repeatable, pause: operator, checks: append(repeatable.checks, ['pause']), repeatable: repeatable, interpolate: [{ property: 'repeatable' }, { property: 'pause', byPosition: true }] }",
selector: {
loose: "repeatable",
arguments: {
repeatable: "(@<= 'repeatable')",
},
},
check: defaultContextCheckProperties(['repeatable', 'repeats'])
},
{
id: 'stop',
isA: ['verb'],
optional: {
1: "{ marker: 'drone' }",
},
bridge: "{ ...next(operator), object: after[0], interpolate: [{ context: operator }, { property: 'object' }] }",
semantic: async ({context, objects, api, say}) => {
await api.stop()
await api.markCurrentPoint()
}
},
],
generators: [
{
match: ({context}) => context.marker == 'help' && !context.paraphrase && context.isResponse,
apply: () => ''
},
{
match: ({context}) => context.marker == 'point' && context.point,
apply: ({context}) => `(${context.point.x.toFixed(2)}, ${context.point.y.toFixed(2)})`
},
],
semantics: [
{
match: ({context}) => context.marker == 'doAction',
apply: async ({context, fragments, e, s, toEValue, toFinalValue, objects}) => {
await s({ ...context, marker: 'patrol', path: context.action})
}
},
{
match: ({context, contextHierarchy}) => {
if (!context.pullFromContext || !context.evaluate || contextHierarchy.under(['doAction', 'evaluate', 'patrol']) || context.instance) {
return false
}
if (context.marker == 'path' || context.marker == 'this' || context.marker == 'that') {
return true
}
},
apply: async ({context, frameOfReference, toArray, fragments, objects, remember, recall, resolveEvaluate, _continue, contextHierarchy}) => {
const pathComponents = toArray(await recall({
context: { marker: 'pathComponent' },
all: true,
stopCondition: (context) => context.marker == 'startPath',
}))
const path = (await fragments('path')).contexts()[0]
delete path.value
path.instance = true
path.points = pathComponents.reverse()
frameOfReference(path, { mentioned: 'points', reversed: true })
await remember(path)
_continue() // let the call pick the object out from the stm
},
},
{
match: ({context, contextHierarchy}) => {
if (!context.pullFromContext || !context.evaluate || !contextHierarchy.under('call') || context.notUnderCall) {
return false
}
if (context.marker == 'point' || context.marker == 'ordinal') {
return true
}
},
apply: async ({frameOfReference, context, e, fragments, stm, toEValue, toArray, objects, remember, recall, resolveEvaluate, _continue, contextHierarchy}) => {
const evaluated = await e({...context, notUnderCall: true})
const pointsContext = toEValue(evaluated)
const pathComponents = toArray(pointsContext)
const path = (await fragments('path')).contexts()[0]
delete path.value
path.instance = true
path.points = [...pathComponents]
frameOfReference(path, { mentioned: 'points', reversed: true })