PageSourceSearch

https://docs.phpvms.net/assets/js/56b553c5.88b6e05f.js

js phpvms.net collected 2026-10-04 13:25:08 UTC 10,555 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkphpvms_docs=self.webpackChunkphpvms_docs||[]).push([[6689],{66740(e,n,s){s.r(n),s.d(n,{assets:()=>c,contentTitle:()=>l,default:()=>u,frontMatter:()=>i,metadata:()=>t,toc:()=>o});const t=JSON.parse('{"id":"acars/plugins/rules","title":"Custom Rules","description":"For users with Premium, you can create their own rules, in Javascript. Rules can","source":"@site/versioned_docs/version-v7.x/acars/plugins/rules.md","sourceDirName":"acars/plugins","slug":"/acars/plugins/rules","permalink":"/acars/plugins/rules","draft":false,"unlisted":false,"editUrl":"https://github.com/phpvms/docs/tree/master/versioned_docs/version-v7.x/acars/plugins/rules.md","tags":[],"version":"v7.x","frontMatter":{"id":"rules","title":"Custom Rules"},"sidebar":"docs","previous":{"title":"Aircraft Configs","permalink":"/acars/plugins/aircraftconfigs"},"next":{"title":"Callback Scripts","permalink":"/acars/plugins/callbacks"}}');var a=s(74848),r=s(28453);const i={id:"rules",title:"Custom Rules"},l=void 0,c={},o=[{value:"Rules Configuration",id:"rules-configuration",level:2},{value:"Looking at aircraft feature states",id:"looking-at-aircraft-feature-states",level:3},{value:"Returning a <code>RuleValue</code>",id:"returning-a-rulevalue",level:3},{value:"Helper Methods",id:"helper-methods",level:3},{value:"Testing",id:"testing",level:2}];function d(e){const n={a:"a",admonition:"admonition",code:"code",h2:"h2",h3:"h3",hr:"hr",li:"li",p:"p",pre:"pre",ul:"ul",...(0,r.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(n.p,{children:"For users with Premium, you can create their own rules, in Javascript. Rules can\nbe packaged alongside aircraft configs, sounds, and callbacks for distribution\nto your VA."}),"\n",(0,a.jsxs)(n.p,{children:["See the documentation at the\n",(0,a.jsx)(n.a,{href:"https://github.com/phpvms/acars-pdk",children:"acars-pdk repository"})," on how to create\naircraft configs, rules, and package them for distribution"]}),"\n",(0,a.jsx)(n.hr,{}),"\n",(0,a.jsx)(n.h2,{id:"rules-configuration",children:"Rules Configuration"}),"\n",(0,a.jsx)(n.p,{children:"A rule looks like this:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:"export default class ExampleRule implements Rule {\n  meta: Meta = {\n    id: 'ExampleRule',\n    name: 'An Example Rule',\n    enabled: true,\n    message: 'A example rule!',\n    states: [],\n    repeatable: false,\n    cooldown: 60,\n    max_count: 3,\n  };\n\n  violated(\n    pirep: Pirep,\n    data: Telemetry,\n    previousData?: Telemetry,\n  ): RuleValue {}\n}\n"})}),"\n",(0,a.jsx)(n.p,{children:"A rule also has several components:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["Needs to implement the ",(0,a.jsx)(n.code,{children:"Rule"})," interface"]}),"\n",(0,a.jsxs)(n.li,{children:["Has a ",(0,a.jsx)(n.code,{children:"meta"}),", section, hich gives some general information about the\nconfiguration:","\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"id"})," - A unique ID for this rule"]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"name"})," - a name for this rule, it's used as the reference"]}),"\n",(0,a.jsx)(n.li,{children:(0,a.jsx)(n.code,{children:"enabled"})}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"message"})," - a default message when the rule is violated"]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"states"})," - a list of ",(0,a.jsx)(n.code,{children:"PirepState"})," of when this rule is to be run"]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"repeatable"})," - if it can be violated multiple times"]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"cooldown"})," - The amount of time, in seconds, between violations"]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"max_count"})," - if it's repeatable, how many times it can maximally be\nvioalted"]}),"\n"]}),"\n"]}),"\n",(0,a.jsxs)(n.li,{children:["A ",(0,a.jsx)(n.code,{children:"violated()"})," method, which returns a ",(0,a.jsx)(n.code,{children:"RuleValue"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["Passed the ",(0,a.jsx)(n.code,{children:"pirep"})," and the ",(0,a.jsx)(n.code,{children:"data"})," (",(0,a.jsx)(n.code,{children:"Telemetry"})," type)"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,a.jsx)(n.h3,{id:"looking-at-aircraft-feature-states",children:"Looking at aircraft feature states"}),"\n",(0,a.jsxs)(n.p,{children:["To lookup the state of an aircraft feature, look at the ",(0,a.jsx)(n.code,{children:"data.Features"}),"\ndictionary. The following rule is evaluated during pushback, and checks that the\nbattery is on:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:"import {AircraftFeature, PirepState} from './defs';\n\nexport default class BatteryOnDuringPushback implements Rule {\n  meta: Meta = {\n    id: 'ExampleRule',\n    name: 'An Example Rule',\n    enabled: true,\n    message: 'A example rule!',\n    states: [PirepState.Pushback],\n    repeatable: false,\n    cooldown: 60,\n    max_count: 3,\n  };\n\n  violated(pirep: Pirep, data: Telemetry, previousData?: Telemetry): RuleValue {\n    // First check that the battery is declared as part of the aircraft's feature set\n    if (\n      AircraftFeature.Battery in data.features &&\n      // And then check its value to see if it's on or off\n      data.features[AircraftFeature.Battery] == false\n    ) {\n      return ['The battery must be on during pushback'];\n    }\n  }\n}\n"})}),"\n",(0,a.jsxs)(n.h3,{id:"returning-a-rulevalue",children:["Returning a ",(0,a.jsx)(n.code,{children:"RuleValue"})]}),"\n",(0,a.jsx)(n.p,{children:"The return value has multiple possible values, sending on"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:"export type RuleValue = undefined | boolean | [string?, number?];\n"})}),"\n",(0,a.jsx)(n.p,{children:"If a rule is passing/hasn't been violated:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:"return;\nreturn false;\n"})}),"\n",(0,a.jsx)(n.p,{children:"If a rule has been violated:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:"return true;\n"})}),"\n",(0,a.jsx)(n.p,{children:"Or, if you want to return a custom message:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:"return ['message'];\n"})}),"\n",(0,a.jsx)(n.p,{children:"Or, if you want to return a message and points:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:"return ['message', points];\n"})}),"\n",(0,a.jsx)(n.p,{children:"If you want to return just the points, you can return:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:"return ['', points];\n"})}),"\n",(0,a.jsxs)(n.p,{children:[(0,a.jsx)(n.code,{children:"points"})," and ",(0,a.jsx)(n.code,{children:"message"})," are optional - if omitted, they're pulled from the ",(0,a.jsx)(n.code,{children:"meta"}),"\nblock"]}),"\n",(0,a.jsx)(n.h3,{id:"helper-methods",children:"Helper Methods"}),"\n",(0,a.jsx)(n.h2,{id:"testing",children:"Testing"}),"\n",(0,a.jsx)(n.p,{children:"What I do is create a tests folder, so it looks like this:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{children:"rules/\n  tests/\n    lights.test.js  < will be created below\n  lights.js\n  package.json < will created below\nmanifest.json\n"})}),"\n",(0,a.jsxs)(n.p,{children:["Create a ",(0,a.jsx)(n.code,{children:"package.json"})," file in the root of the ",(0,a.jsx)(n.code,{children:"rules"})," folder, so it looks\nlike:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-json",children:'{\n  "name": "VA Rules",\n  "version": "1.0.0",\n  "author": "Your Name",\n  "type": "module",\n  "scripts": {\n    "tests": "tap"\n  }\n}\n'})}),"\n",(0,a.jsxs)(n.p,{children:["Next, grab the ",(0,a.jsx)(n.code,{children:"mocks.js"})," file from here. This is used to mock the methods that\nACARS provides, so that in the tests, there are faked versions available. If you\ncloned the sample repository, this file is already there."]}),"\n",(0,a.jsxs)(n.p,{children:["Then, install ",(0,a.jsx)(n.a,{href:"https://node-tap.org",children:"tap"})," using:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"npm install --save-dev tap\n"})}),"\n",(0,a.jsxs)(n.p,{children:["Next, create a ",(0,a.jsx)(n.code,{children:"tests/lights.test.js"}),". The example shown above is the ",(0,a.jsx)(n.code,{children:"Rule"}),"\nclass that's being assumed to be used. The important parts are commented:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-js",children:"import t from 'tap';\nimport * as Lights from '../lights.js';\nimport {createMocks} from './mocks.js';\n\nt.before(createMocks);\n\nt.test('Test the lights function', (t) => {\n  const rule = new Lights.Rule();\n  // You can pass the data that is only relevent to your rule here in the PIREP and Data objects\n  t.equal(rule.evaluate({}, {}), 0);\n  t.end();\n});\n"})}),"\n",(0,a.jsx)(n.admonition,{type:"note",children:(0,a.jsx)(n.p,{children:"You can use webpack and babel to create a smaller distribution, which you can\nthen create a packaged, minimized build, which then can be used by ACARS. So\nyou're welcome to create your rules using Typescript and transpile them."})}
1),"\n",(0,a.jsx)(n.p,{children:"Then you can finally run your tests:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"cd rules\nnpm run tests\n"})}),"\n",(0,a.jsx)(n.admonition,{type:"note",children:(0,a.jsxs)(n.p,{children:["You can use ",(0,a.jsx)(n.a,{href:"https://node-tap.org/plugins/clock",children:"the clock plugin"})," to fast\nforward time - call your rule, advance the clock, call it again and check for\nviolations"]})})]})}function u(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,a.jsx)(n,{...e,children:(0,a.jsx)(d,{...e})}):d(e)}},28453(e,n,s){s.d(n,{R:()=>i,x:()=>l});var t=s(96540);const a={},r=t.createContext(a);function i(e){const n=t.useContext(r);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:i(e.components),t.createElement(r.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.