Bsongis/x7d companion support (#4028)
[opentx.git] / radio / src / lua / api_general.cpp
blob9375b638950ef07d0a04586869db210d764bebc2
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #include <ctype.h>
22 #include <stdio.h>
23 #include "opentx.h"
24 #include "stamp.h"
25 #include "lua/lua_api.h"
26 #include "telemetry/frsky.h"
28 #if defined(PCBHORUS)
29 #include "lua/lua_exports_horus.inc" // this line must be after lua headers
30 #elif defined(PCBFLAMENCO)
31 #include "lua/lua_exports_flamenco.inc"
32 #elif defined(PCBX9E)
33 #include "lua/lua_exports_x9e.inc"
34 #elif defined(PCBX7D)
35 #include "lua/lua_exports_x7d.inc"
36 #elif defined(PCBTARANIS)
37 #include "lua/lua_exports_x9d.inc"
38 #endif
40 #if defined(SIMU)
41 #define RADIO_VERSION FLAVOUR "-simu"
42 #else
43 #define RADIO_VERSION FLAVOUR
44 #endif
46 #define FIND_FIELD_DESC 0x01
48 /*luadoc
49 @function getVersion()
51 Return OpenTX version
53 @retval string OpenTX version (ie "2.1.5")
55 @retval multiple (available since 2.1.7) returns 5 values:
56 * (string) OpenTX version (ie "2.1.5")
57 * (string) radio version: `taranisx9e`, `taranisplus` or `taranis`.
58 If running in simulator the "-simu" is added
59 * (number) major version (ie 2 if version 2.1.5)
60 * (number) minor version (ie 1 if version 2.1.5)
61 * (number) revison number (ie 5 if version 2.1.5)
63 @status current Introduced in 2.0.0, expanded in 2.1.7
65 ### Example
67 This example also runs in OpenTX versions where the function returned only one value:
69 ```lua
70 local function run(event)
71 local ver, radio, maj, minor, rev = getVersion()
72 print("version: "..ver)
73 if radio then print ("radio: "..radio) end
74 if maj then print ("maj: "..maj) end
75 if minor then print ("minor: "..minor) end
76 if rev then print ("rev: "..rev) end
77 return 1
78 end
80 return { run=run }
81 ```
82 Output of the above script in simulator:
83 ```
84 version: 2.1.7
85 radio: taranis-simu
86 maj: 2
87 minor: 1
88 rev: 7
89 ```
91 static int luaGetVersion(lua_State * L)
93 lua_pushstring(L, VERSION);
94 lua_pushstring(L, RADIO_VERSION);
95 lua_pushnumber(L, VERSION_MAJOR);
96 lua_pushnumber(L, VERSION_MINOR);
97 lua_pushnumber(L, VERSION_REVISION);
98 return 5;
101 /*luadoc
102 @function getTime()
104 Return the time since the radio was started in multiple of 10ms
106 @retval number Number of 10ms ticks since the radio was started Example:
107 run time: 12.54 seconds, return value: 1254
109 @status current Introduced in 2.0.0
111 static int luaGetTime(lua_State * L)
113 lua_pushunsigned(L, get_tmr10ms());
114 return 1;
117 static void luaPushDateTime(lua_State * L, uint32_t year, uint32_t mon, uint32_t day,
118 uint32_t hour, uint32_t min, uint32_t sec)
120 lua_createtable(L, 0, 6);
121 lua_pushtableinteger(L, "year", year);
122 lua_pushtableinteger(L, "mon", mon);
123 lua_pushtableinteger(L, "day", day);
124 lua_pushtableinteger(L, "hour", hour);
125 lua_pushtableinteger(L, "min", min);
126 lua_pushtableinteger(L, "sec", sec);
129 /*luadoc
130 @function getDateTime()
132 Return current system date and time that is kept by the RTC unit
134 @retval table current date and time, table elements:
135 * `year` (number) year
136 * `mon` (number) month
137 * `day` (number) day of month
138 * `hour` (number) hours
139 * `min` (number) minutes
140 * `sec` (number) seconds
142 static int luaGetDateTime(lua_State * L)
144 struct gtm utm;
145 gettime(&utm);
146 luaPushDateTime(L, utm.tm_year + TM_YEAR_BASE, utm.tm_mon + 1, utm.tm_mday, utm.tm_hour, utm.tm_min, utm.tm_sec);
147 return 1;
150 static void luaPushLatLon(TelemetrySensor & telemetrySensor, TelemetryItem & telemetryItem)
151 /* result is lua table containing members ["lat"] and ["lon"] as lua_Number (doubles) in decimal degrees */
153 lua_createtable(L, 0, 4);
154 lua_pushtablenumber(L, "lat", telemetryItem.gps.latitude / 1000000.0);
155 lua_pushtablenumber(L, "pilot-lat", telemetryItem.pilotLatitude / 1000000.0);
156 lua_pushtablenumber(L, "lon", telemetryItem.gps.longitude / 1000000.0);
157 lua_pushtablenumber(L, "pilot-lon", telemetryItem.pilotLongitude / 1000000.0);
160 static void luaPushTelemetryDateTime(TelemetrySensor & telemetrySensor, TelemetryItem & telemetryItem)
162 luaPushDateTime(L, telemetryItem.datetime.year, telemetryItem.datetime.month, telemetryItem.datetime.day,
163 telemetryItem.datetime.hour, telemetryItem.datetime.min, telemetryItem.datetime.sec);
166 static void luaPushCells(TelemetrySensor & telemetrySensor, TelemetryItem & telemetryItem)
168 if (telemetryItem.cells.count == 0)
169 lua_pushinteger(L, (int)0); // returns zero if no cells
170 else {
171 lua_createtable(L, telemetryItem.cells.count, 0);
172 for (int i = 0; i < telemetryItem.cells.count; i++) {
173 lua_pushnumber(L, i + 1);
174 lua_pushnumber(L, telemetryItem.cells.values[i].value / 100.0);
175 lua_settable(L, -3);
180 void luaGetValueAndPush(int src)
182 getvalue_t value = getValue(src); // ignored for GPS, DATETIME, and CELLS
184 if (src >= MIXSRC_FIRST_TELEM && src <= MIXSRC_LAST_TELEM) {
185 div_t qr = div(src-MIXSRC_FIRST_TELEM, 3);
186 // telemetry values
187 if (TELEMETRY_STREAMING() && telemetryItems[qr.quot].isAvailable()) {
188 TelemetrySensor & telemetrySensor = g_model.telemetrySensors[qr.quot];
189 switch (telemetrySensor.unit) {
190 case UNIT_GPS:
191 luaPushLatLon(telemetrySensor, telemetryItems[qr.quot]);
192 break;
193 case UNIT_DATETIME:
194 luaPushTelemetryDateTime(telemetrySensor, telemetryItems[qr.quot]);
195 break;
196 case UNIT_CELLS:
197 if (qr.rem == 0) {
198 luaPushCells(telemetrySensor, telemetryItems[qr.quot]);
199 break;
201 // deliberate no break here to properly return `Cels-` and `Cels+`
202 default:
203 if (telemetrySensor.prec > 0)
204 lua_pushnumber(L, float(value)/telemetrySensor.getPrecDivisor());
205 else
206 lua_pushinteger(L, value);
207 break;
210 else {
211 // telemetry not working, return zero for telemetry sources
212 lua_pushinteger(L, (int)0);
215 else if (src == MIXSRC_TX_VOLTAGE) {
216 lua_pushnumber(L, float(value)/10.0);
218 else {
219 lua_pushinteger(L, value);
224 Return field data for a given field name
226 bool luaFindFieldByName(const char * name, LuaField & field, unsigned int flags)
228 // TODO better search method (binary lookup)
229 for (unsigned int n=0; n<DIM(luaSingleFields); ++n) {
230 if (!strcmp(name, luaSingleFields[n].name)) {
231 field.id = luaSingleFields[n].id;
232 if (flags & FIND_FIELD_DESC) {
233 strncpy(field.desc, luaSingleFields[n].desc, sizeof(field.desc)-1);
234 field.desc[sizeof(field.desc)-1] = '\0';
236 else {
237 field.desc[0] = '\0';
239 return true;
243 // search in multiples
244 unsigned int len = strlen(name);
245 for (unsigned int n=0; n<DIM(luaMultipleFields); ++n) {
246 const char * fieldName = luaMultipleFields[n].name;
247 unsigned int fieldLen = strlen(fieldName);
248 if (!strncmp(name, fieldName, fieldLen)) {
249 unsigned int index;
250 if (len == fieldLen+1 && isdigit(name[fieldLen])) {
251 index = name[fieldLen] - '1';
253 else if (len == fieldLen+2 && isdigit(name[fieldLen]) && isdigit(name[fieldLen+1])) {
254 index = 10 * (name[fieldLen] - '0') + (name[fieldLen+1] - '1');
256 else {
257 continue;
259 if (index < luaMultipleFields[n].count) {
260 field.id = luaMultipleFields[n].id + index;
261 if (flags & FIND_FIELD_DESC) {
262 snprintf(field.desc, sizeof(field.desc)-1, luaMultipleFields[n].desc, index+1);
263 field.desc[sizeof(field.desc)-1] = '\0';
265 else {
266 field.desc[0] = '\0';
268 return true;
273 // search in telemetry
274 field.desc[0] = '\0';
275 for (int i=0; i<MAX_TELEMETRY_SENSORS; i++) {
276 if (isTelemetryFieldAvailable(i)) {
277 char sensorName[TELEM_LABEL_LEN+1];
278 int len = zchar2str(sensorName, g_model.telemetrySensors[i].label, TELEM_LABEL_LEN);
279 if (!strncmp(sensorName, name, len)) {
280 if (name[len] == '\0') {
281 field.id = MIXSRC_FIRST_TELEM + 3*i;
282 field.desc[0] = '\0';
283 return true;
285 else if (name[len] == '-' && name[len+1] == '\0') {
286 field.id = MIXSRC_FIRST_TELEM + 3*i + 1;
287 field.desc[0] = '\0';
288 return true;
290 else if (name[len] == '+' && name[len+1] == '\0') {
291 field.id = MIXSRC_FIRST_TELEM + 3*i + 2;
292 field.desc[0] = '\0';
293 return true;
299 return false; // not found
302 /*luadoc
303 @function sportTelemetryPop()
305 Pops a received SPORT packet from the queue. Please note that only packets using a data ID within 0x5000 to 0x50FF (frame ID == 0x10), as well as packets with a frame ID equal 0x32 (regardless of the data ID) will be passed to the LUA telemetry receive queue.
307 @retval SPORT paket as a quadruple:
308 * sensor ID (number)
309 * frame ID (number)
310 * data ID (number)
311 * value (number)
313 @status current Introduced in 2.2.0
315 static int luaSportTelemetryPop(lua_State * L)
317 if (!luaInputTelemetryFifo) {
318 luaInputTelemetryFifo = new Fifo<uint8_t, LUA_TELEMETRY_INPUT_FIFO_SIZE>();
319 if (!luaInputTelemetryFifo) {
320 return 0;
324 if (luaInputTelemetryFifo->size() >= sizeof(SportTelemetryPacket)) {
325 SportTelemetryPacket packet;
326 for (uint8_t i=0; i<sizeof(packet); i++) {
327 luaInputTelemetryFifo->pop(packet.raw[i]);
329 lua_pushnumber(L, packet.physicalId);
330 lua_pushnumber(L, packet.primId);
331 lua_pushnumber(L, packet.dataId);
332 lua_pushunsigned(L, packet.value);
333 return 4;
336 return 0;
339 #define BIT(x, index) (((x) >> index) & 0x01)
340 uint8_t getDataId(uint8_t physicalId)
342 uint8_t result = physicalId;
343 result += (BIT(physicalId, 0) ^ BIT(physicalId, 1) ^ BIT(physicalId, 2)) << 5;
344 result += (BIT(physicalId, 2) ^ BIT(physicalId, 3) ^ BIT(physicalId, 4)) << 6;
345 result += (BIT(physicalId, 0) ^ BIT(physicalId, 2) ^ BIT(physicalId, 4)) << 7;
346 return result;
349 /*luadoc
350 @function sportTelemetryPush()
352 This functions allows for sending SPORT telemetry data toward the receiver,
353 and more generally, to anything connected SPORT bus on the receiver or transmitter.
355 When called without parameters, it will only return the status of the ouput buffer without sending anything.
357 @param sensorId physical sensor ID
359 @param frameId frame ID
361 @param dataId data ID
363 @param value value
365 @retval boolean data queued in output buffer or not.
367 @status current Introduced in 2.2.0
369 static int luaSportTelemetryPush(lua_State * L)
371 if (lua_gettop(L) == 0) {
372 lua_pushboolean(L, isSportOutputBufferAvailable());
374 else if (isSportOutputBufferAvailable()) {
375 SportTelemetryPacket packet;
376 packet.physicalId = getDataId(luaL_checkunsigned(L, 1));
377 packet.primId = luaL_checkunsigned(L, 2);
378 packet.dataId = luaL_checkunsigned(L, 3);
379 packet.value = luaL_checkunsigned(L, 4);
380 sportOutputPushPacket(&packet);
381 lua_pushboolean(L, true);
383 else {
384 lua_pushboolean(L, false);
386 return 1;
389 /*luadoc
390 @function crossfireTelemetryPop()
392 @retval SPORT paket as a quadruple:
393 * sensor ID (number)
394 * frame ID (number)
395 * data ID (number)
396 * value (number)
398 @status current Introduced in 2.2.0
400 static int luaCrossfireTelemetryPop(lua_State * L)
402 if (!luaInputTelemetryFifo) {
403 luaInputTelemetryFifo = new Fifo<uint8_t, LUA_TELEMETRY_INPUT_FIFO_SIZE>();
404 if (!luaInputTelemetryFifo) {
405 return 0;
409 uint8_t length, data;
410 if (luaInputTelemetryFifo->probe(length) && luaInputTelemetryFifo->size() >= uint32_t(length)) {
411 // length value includes the length field
412 luaInputTelemetryFifo->pop(length);
413 luaInputTelemetryFifo->pop(data); // command
414 lua_pushnumber(L, data);
415 lua_newtable(L);
416 for (uint8_t i=1; i<length-1; i++) {
417 luaInputTelemetryFifo->pop(data);
418 lua_pushinteger(L, i);
419 lua_pushinteger(L, data);
420 lua_settable(L, -3);
422 return 2;
425 return 0;
428 /*luadoc
429 @function crossfireTelemetryPush()
431 This functions allows for sending telemetry data toward the TBS Crossfire link.
433 When called without parameters, it will only return the status of the ouput buffer without sending anything.
435 @param sensorId physical sensor ID
437 @param frameId frame ID
439 @param dataId data ID
441 @param value value
443 @retval boolean data queued in output buffer or not.
445 @status current Introduced in 2.2.0
447 static int luaCrossfireTelemetryPush(lua_State * L)
449 if (lua_gettop(L) == 0) {
450 lua_pushboolean(L, isCrossfireOutputBufferAvailable());
452 else if (isCrossfireOutputBufferAvailable()) {
453 uint8_t command = luaL_checkunsigned(L, 1);
454 luaL_checktype(L, 2, LUA_TTABLE);
455 uint8_t length = luaL_len(L, 2);
456 telemetryOutputPushByte(MODULE_ADDRESS);
457 telemetryOutputPushByte(2 + length); // 1(COMMAND) + data length + 1(CRC)
458 telemetryOutputPushByte(command); // COMMAND
459 for (int i=0; i<length; i++) {
460 lua_rawgeti(L, 2, i+1);
461 telemetryOutputPushByte(luaL_checkunsigned(L, -1));
463 telemetryOutputPushByte(crc8(outputTelemetryBuffer+2, 1 + length));
464 telemetryOutputSetTrigger(command);
465 lua_pushboolean(L, true);
467 else {
468 lua_pushboolean(L, false);
470 return 1;
473 /*luadoc
474 @function getFieldInfo(name)
476 Return detailed information about field (source)
478 The list of valid sources is available:
479 * for OpenTX 2.0.x at http://downloads-20.open-tx.org/firmware/lua_fields.txt
480 * for OpenTX 2.1.x at http://downloads-21.open-tx.org/firmware/lua_fields.txt (depreciated)
481 * for OpenTX 2.1.x Taranis and Taranis Plus at http://downloads-21.open-tx.org/firmware/lua_fields_taranis.txt
482 * for OpenTX 2.1.x Taranis X9E at http://downloads-21.open-tx.org/firmware/lua_fields_taranis_x9e.txt
484 @param name (string) name of the field
486 @retval table information about requested field, table elements:
487 * `id` (number) field identifier
488 * `name` (string) field name
489 * `desc` (string) field description
491 @retval nil the requested field was not found
493 @status current Introduced in 2.0.8
495 static int luaGetFieldInfo(lua_State * L)
497 const char * what = luaL_checkstring(L, 1);
498 LuaField field;
499 bool found = luaFindFieldByName(what, field, FIND_FIELD_DESC);
500 if (found) {
501 lua_newtable(L);
502 lua_pushtableinteger(L, "id", field.id);
503 lua_pushtablestring(L, "name", what);
504 lua_pushtablestring(L, "desc", field.desc);
505 return 1;
507 return 0;
510 /*luadoc
511 @function getValue(source)
513 Returns the value of a source.
515 The list of valid sources is available:
516 * for OpenTX 2.0.x at http://downloads-20.open-tx.org/firmware/lua_fields.txt
517 * for OpenTX 2.1.x at http://downloads-21.open-tx.org/firmware/lua_fields.txt
519 In OpenTX 2.1.x the telemetry sources no longer have a predefined name.
520 To get a telemetry value simply use it's sensor name. For example:
521 * Altitude sensor has a name "Alt"
522 * to get the current altitude use the source "Alt"
523 * to get the minimum altitude use the source "Alt-", to get the maximum use "Alt+"
525 @param source can be an identifier (number) (which was obtained by the getFieldInfo())
526 or a name (string) of the source.
528 @retval value current source value (number). Zero is returned for:
529 * non-existing sources
530 * for all telemetry source when the telemetry stream is not received
532 @retval table GPS position is returned in a table:
533 * `lat` (number) latitude, positive is North
534 * `lon` (number) longitude, positive is East
535 * `pilot-lat` (number) pilot latitude, positive is North
536 * `pilot-lon` (number) pilot longitude, positive is East
538 @retval table GPS date/time, see getDateTime()
540 @retval table Cells are returned in a table
541 (except where no cells were detected in which
542 case the returned value is 0):
543 * table has one item for each detected cell:
544 * key (number) cell number (1 to number of cells)
545 * value (number) current cell voltage
547 @status current Introduced in 2.0.0, changed in 2.1.0, `Cels+` and
548 `Cels-` added in 2.1.9
550 @notice Getting a value by its numerical identifier is faster then by its name.
551 While `Cels` sensor returns current values of all cells in a table, a `Cels+` or
552 `Cels-` will return a single value - the maximum or minimum Cels value.
554 static int luaGetValue(lua_State * L)
556 int src = 0;
557 if (lua_isnumber(L, 1)) {
558 src = luaL_checkinteger(L, 1);
560 else {
561 // convert from field name to its id
562 const char *name = luaL_checkstring(L, 1);
563 LuaField field;
564 bool found = luaFindFieldByName(name, field);
565 if (found) {
566 src = field.id;
569 luaGetValueAndPush(src);
570 return 1;
573 /*luadoc
574 @function getRAS()
576 Return the RAS value or nil if no valid hardware found
578 @retval number representing RAS value. Value bellow 0x33 (51 decimal) are all ok, value above 0x33 indicate a hardware antenna issue.
579 This is just a hardware pass/fail measure and does not represent the quality of the radio link
581 @status current Introduced in 2.2.0
583 static int luaGetRAS(lua_State * L)
585 if (IS_SWR_VALUE_VALID()) {
586 lua_pushinteger(L, telemetryData.swr.value);
588 else {
589 lua_pushnil(L);
591 return 1;
595 /*luadoc
596 @function getFlightMode(mode)
598 Return flight mode data.
600 @param mode (number) flight mode number to return (0 - 8). If mode parameter
601 is not specified (or contains invalid value), then the current flight mode data is returned.
603 @retval multiple returns 2 values:
604 * (number) (current) flight mode number (0 - 8)
605 * (string) (current) flight mode name
607 @status current Introduced in 2.1.7
609 static int luaGetFlightMode(lua_State * L)
611 int mode = luaL_optinteger(L, 1, -1);
612 if (mode < 0 || mode >= MAX_FLIGHT_MODES) {
613 mode = mixerCurrentFlightMode;
615 lua_pushnumber(L, mode);
616 char name[sizeof(g_model.flightModeData[0].name)+1];
617 zchar2str(name, g_model.flightModeData[mode].name, sizeof(g_model.flightModeData[0].name));
618 lua_pushstring(L, name);
619 return 2;
622 /*luadoc
623 @function playFile(name)
625 Play a file from the SD card
627 @param path (string) full path to wav file (i.e. “/SOUNDS/en/system/tada.wav”)
628 Introduced in 2.1.0: If you use a relative path, the current language is appended
629 to the path (example: for English language: `/SOUNDS/en` is appended)
631 @status current Introduced in 2.0.0, changed in 2.1.0
633 static int luaPlayFile(lua_State * L)
635 const char * filename = luaL_checkstring(L, 1);
636 if (filename[0] != '/') {
637 // relative sound file path - use current language dir for absolute path
638 char file[AUDIO_FILENAME_MAXLEN+1];
639 char * str = getAudioPath(file);
640 strncpy(str, filename, AUDIO_FILENAME_MAXLEN - (str-file));
641 file[AUDIO_FILENAME_MAXLEN] = 0;
642 PLAY_FILE(file, 0, 0);
644 else {
645 PLAY_FILE(filename, 0, 0);
647 return 0;
650 /*luadoc
651 @function playNumber(value, unit [, attributes])
653 Play a numerical value (text to speech)
655 @param value (number) number to play. Value is interpreted as integer.
657 @param unit (number) unit identifier (see table todo)
659 @param attributes (unsigned number) possible values:
660 * `0 or not present` plays integral part of the number (for a number 123 it plays 123)
661 * `PREC1` plays a number with one decimal place (for a number 123 it plays 12.3)
662 * `PREC2` plays a number with two decimal places (for a number 123 it plays 1.23)
664 @status current Introduced in 2.0.0
666 @notice 2.0 Only - automatic conversion of units for distance, speed, and temperature.
668 OpenTX 2.0:
670 | Unit | Sound | File (.wav) | Automatic conversion rules |
671 | --- | --- | --- | --- |
672 | 0 | --- | --- (no unit played) | |
673 | 1 | Volts | 116 | |
674 | 2 | Amps | 118 | |
675 | 3 | Meters per Second | 120 | |
676 | 4 | *missing file* | 122 | |
677 | 5 | Kilometers per Hour / Miles per Hour | 124 / 142 | Input value is KPH |
678 | 6 | Meters / Feet | 126 / 140 | Input value is meters |
679 | 7 | Degrees | 128 | Input value is celsius, converted to Fahrenheit for Imperial |
680 | 8 | Percent | 130 | |
681 | 9 | Milliamps | 132 | |
682 | 10 | Milliamp Hours | 134 | |
683 | 11 | Watts | 136 | |
684 | 12 | DB | 138 | |
685 | 13 | Feet | 140 | |
686 | 14 | Kilometers per Hour / Miles per Hour | 124 / 142 | Input value is in Knots, converted to KPH or MPH |
687 | 15 | Hours | 144 | |
688 | 16 | Minutes | 146 | |
689 | 17 | Seconds | 148 | |
690 | 18 | RPM | 150 | |
691 | 19 | Gee | 152 | |
692 | 20 | Degrees | 128 | |
695 OpenTX 2.1:
697 | 2.1 Unit | Sound | Sound File (.wav) |
698 | --- | --- | --- |
699 | 0 | --- | --- (no unit played) | |
700 | 1 | Volts | 116 |
701 | 2 | Amps | 118 |
702 | 3 | Milliamps | 120 |
703 | 4 | Knots | 122 |
704 | 5 | Meters per Second | 124 |
705 | 6 | Feet per Second | 126 |
706 | 7 | Kilometers per Hour | 128 |
707 | 8 | Miles per Hour | 130 |
708 | 9 | Meters | 132 |
709 | 10 | Feet | 134 |
710 | 11 | Degrees Celsius | 136 |
711 | 12 | Degrees Fahrenheit | 138 |
712 | 13 | Percent | 140 |
713 | 14 | Milliamp Hours | 142 |
714 | 15 | Watts | 144 |
715 | 16 | DB | 146 |
716 | 17 | RPM | 148 |
717 | 18 | Gee | 150 |
718 | 19 | Degrees | 152 |
719 | 20 | Milliliters | 154 |
720 | 21 | Fluid Ounces | 156 |
721 | 22 | Hours | 158 |
722 | 23 | Minutes | 160 |
723 | 24 | Seconds | 162 |
726 static int luaPlayNumber(lua_State * L)
728 int number = luaL_checkinteger(L, 1);
729 int unit = luaL_checkinteger(L, 2);
730 unsigned int att = luaL_optunsigned(L, 3, 0);
731 playNumber(number, unit, att, 0);
732 return 0;
735 /*luadoc
736 @function playDuration(duration [, hourFormat])
738 Play a time value (text to speech)
740 @param duration (number) number of seconds to play. Only integral part is used.
742 @param hourFormat (number):
743 * `0 or not present` play format: minutes and seconds.
744 * `!= 0` play format: hours, minutes and seconds.
746 @status current Introduced in 2.1.0
748 static int luaPlayDuration(lua_State * L)
750 int duration = luaL_checkinteger(L, 1);
751 bool playTime = (luaL_optinteger(L, 2, 0) != 0);
752 playDuration(duration, playTime ? PLAY_TIME : 0, 0);
753 return 0;
756 /*luadoc
757 @function playTone(frequency, duration, pause [, flags [, freqIncr]])
759 Play a tone
761 @param frequency (number) tone frequency in Hz (from 150 to 15000)
763 @param duration (number) length of the tone in milliseconds
765 @param pause (number) length of the silence after the tone in milliseconds
767 @param flags (number):
768 * `0 or not present` play with normal priority.
769 * `PLAY_BACKGROUND` play in background (built in vario function uses this context)
770 * `PLAY_NOW` play immediately
772 @param freqIncr (number) positive number increases the tone pitch (frequency with time),
773 negative number decreases it. The frequency changes every 10 milliseconds, the change is `freqIncr * 10Hz`.
774 The valid range is from -127 to 127.
776 @status current Introduced in 2.1.0
778 static int luaPlayTone(lua_State * L)
780 int frequency = luaL_checkinteger(L, 1);
781 int length = luaL_checkinteger(L, 2);
782 int pause = luaL_checkinteger(L, 3);
783 int flags = luaL_optinteger(L, 4, 0);
784 int freqIncr = luaL_optinteger(L, 5, 0);
785 audioQueue.playTone(frequency, length, pause, flags, freqIncr);
786 return 0;
789 /*luadoc
790 @function luaPlayHaptic(duration, pause [, flags])
792 Generate haptic feedback
794 @param duration (number) length of the haptic feedback in milliseconds
796 @param pause (number) length of the silence after haptic feedback in milliseconds
798 @param flags (number):
799 * `0 or not present` play with normal priority
800 * `PLAY_NOW` play immediately
802 @status current Introduced in 2.2.0
804 static int luaPlayHaptic(lua_State * L)
806 #if defined(HAPTIC)
807 int length = luaL_checkinteger(L, 1);
808 int pause = luaL_checkinteger(L, 2);
809 int flags = luaL_optinteger(L, 3, 0);
810 haptic.play(length, pause, flags);
811 #endif
812 return 0;
815 /*luadoc
816 @function killEvents(key)
818 Stops key state machine.
820 @param key (number) key to be killed, can also include event type (only key part is used)
822 @status current Introduced in 2.0.0
824 TODO table of events/masks
826 static int luaKillEvents(lua_State * L)
828 uint8_t key = EVT_KEY_MASK(luaL_checkinteger(L, 1));
829 // prevent killing maskable keys (only in telemetry scripts)
830 // TODO add which tpye of script is running before p_call()
831 if (IS_MASKABLE(key)) {
832 killEvents(key);
834 return 0;
837 #if LCD_DEPTH > 1 && !defined(COLORLCD)
838 /*luadoc
839 @function GREY()
841 Returns gray value which can be used in LCD functions
843 @retval (number) a value that represents amount of *greyness* (from 0 to 15)
846 static int luaGrey(lua_State * L)
848 int index = luaL_checkinteger(L, 1);
849 lua_pushunsigned(L, GREY(index));
850 return 1;
852 #endif
854 /*luadoc
855 @function getGeneralSettings()
857 Returns (some of) the general radio settings
859 @retval table with elements:
860 * `battMin` (number) radio battery range - minimum value
861 * `battMax` (number) radio battery range - maximum value
862 * `imperial` (number) set to a value different from 0 if the radio is set to the
863 IMPERIAL units
864 * `language` (string) radio language (used for menus)
865 * `voice` (string) voice language (used for speech)
867 @status current Introduced in 2.0.6, `imperial` added in TODO,
868 `language` and `voice` added int 2.2.0.
871 static int luaGetGeneralSettings(lua_State * L)
873 lua_newtable(L);
874 lua_pushtablenumber(L, "battMin", double(90+g_eeGeneral.vBatMin)/10);
875 lua_pushtablenumber(L, "battMax", double(120+g_eeGeneral.vBatMax)/10);
876 lua_pushtableinteger(L, "imperial", g_eeGeneral.imperial);
877 lua_pushtablestring(L, "language", TRANSLATIONS);
878 lua_pushtablestring(L, "voice", currentLanguagePack->id);
879 return 1;
882 /*luadoc
883 @function popupInput(title, event, input, min, max)
885 Raises a pop-up on screen that allows uses input
887 @param title (string) text to display
889 @param event (number) the event variable that is passed in from the
890 Run function (key pressed)
892 @param input (number) value that can be adjusted by the +/­- keys
894 @param min (number) min value that input can reach (by pressing the -­ key)
896 @param max (number) max value that input can reach
898 @retval number result of the input adjustment
900 @retval "OK" user pushed ENT key
902 @retval "CANCEL" user pushed EXIT key
904 @notice Use only from stand-alone and telemetry scripts.
906 @status current Introduced in 2.0.0
908 static int luaPopupInput(lua_State * L)
910 event_t event = luaL_checkinteger(L, 2);
911 warningInputValue = luaL_checkinteger(L, 3);
912 warningInputValueMin = luaL_checkinteger(L, 4);
913 warningInputValueMax = luaL_checkinteger(L, 5);
914 warningText = luaL_checkstring(L, 1);
915 warningType = WARNING_TYPE_INPUT;
916 runPopupWarning(event);
917 if (warningResult) {
918 warningResult = 0;
919 lua_pushstring(L, "OK");
921 else if (!warningText) {
922 lua_pushstring(L, "CANCEL");
924 else {
925 lua_pushinteger(L, warningInputValue);
927 warningText = NULL;
928 return 1;
931 /*luadoc
932 @function popupWarning(title, event)
934 Raises a pop-up on screen that shows a warning
936 @param title (string) text to display
938 @param event (number) the event variable that is passed in from the
939 Run function (key pressed)
941 @retval "CANCEL" user pushed EXIT key
943 @notice Use only from stand-alone and telemetry scripts.
945 @status current Introduced in 2.2.0
947 static int luaPopupWarning(lua_State * L)
949 event_t event = luaL_checkinteger(L, 2);
950 warningText = luaL_checkstring(L, 1);
951 warningType = WARNING_TYPE_ASTERISK;
952 runPopupWarning(event);
953 if (!warningText) {
954 lua_pushstring(L, "CANCEL");
956 else {
957 warningText = NULL;
958 lua_pushnil(L);
960 return 1;
963 /*luadoc
964 @function popupConfirmation(title, event)
966 Raises a pop-up on screen that asks for confirmation
968 @param title (string) text to display
970 @param event (number) the event variable that is passed in from the
971 Run function (key pressed)
973 @retval "CANCEL" user pushed EXIT key
975 @notice Use only from stand-alone and telemetry scripts.
977 @status current Introduced in 2.2.0
979 static int luaPopupConfirmation(lua_State * L)
981 event_t event = luaL_checkinteger(L, 2);
982 warningText = luaL_checkstring(L, 1);
983 warningType = WARNING_TYPE_CONFIRM;
984 runPopupWarning(event);
985 if (!warningText) {
986 lua_pushstring(L, warningResult ? "OK" : "CANCEL");
988 else {
989 warningText = NULL;
990 lua_pushnil(L);
992 return 1;
995 /*luadoc
996 @function defaultStick(channel)
998 Get stick that is assigned to a channel. See Default Channel Order in General Settings.
1000 @param channel (number) channel number (0 means CH1)
1002 @retval number Stick assigned to this channel (from 0 to 3)
1004 @status current Introduced in 2.0.0
1006 static int luaDefaultStick(lua_State * L)
1008 uint8_t channel = luaL_checkinteger(L, 1);
1009 lua_pushinteger(L, channel_order(channel+1)-1);
1010 return 1;
1013 /* luadoc
1014 @function setTelemetryValue(id, subID, instance, value [, unit] [, precision [, name])
1016 @param id Id of the sensor
1018 @param subID subID of the sensor, usually 0
1020 @param instance instance of the sensor (SensorID)
1022 @param value fed to the sensor
1024 @param unit unit of the sensor.
1025 * `0 or not present` UNIT_RAW.
1026 * `!= 0` Valid values are 1 (UNIT_VOLTS), 2 (UNIT_AMPS), 3 (UNIT_MILLIAMPS),
1027 4 (UNIT_KTS), 5 (UNIT_METERS_PER_SECOND), 6 (UNIT_FEET_PER_SECOND), 7 (UNIT_KMH), 8 (UNIT_MPH), 9 (UNIT_METERS),
1028 10 (UNIT_FEET), 11 (UNIT_CELSIUS), 12 (UNIT_FAHRENHEIT), 13 (UNIT_PERCENT), 14 (UNIT_MAH), 15 (UNIT_WATTS),
1029 16 (UNIT_MILLIWATTS), 17 (UNIT_DB), 18 (UNIT_RPMS), 19 (UNIT_G), 20 (UNIT_DEGREE), 21 (UNIT_RADIANS),
1030 22 (UNIT_MILLILITERS), 23 (UNIT_FLOZ), 24 (UNIT_HOURS), 25 (UNIT_MINUTES), 26 (UNIT_SECONDS), 27 (UNIT_CELLS),
1031 28 (UNIT_DATETIME), 29 (UNIT_GPS), 30 (UNIT_BITFIELD), 31 (UNIT_TEXT)
1033 @param precision the precision of the sensor
1034 * `0 or not present` no decimal precision.
1035 * `!= 0` value is divided by 10^precision, e.g. value=1000, prec=2 => 10.00.
1037 @param name (string) Name of the sensor if it does not yet exist (4 chars).
1038 * `not present` Name defaults to the Id.
1039 * `present` Sensor takes name of the argument. Argument must have name surrounded by quotes: e.g., "Name"
1041 @retval true, if the sensor was just added. In this case the value is ignored (subsequent call will set the value)
1043 static int luaSetTelemetryValue(lua_State * L)
1045 uint16_t id = luaL_checkinteger(L, 1);
1046 uint8_t subId = luaL_checkinteger(L, 2);
1047 uint8_t instance = luaL_checkinteger(L, 3);
1048 int32_t value = luaL_checkinteger(L, 4);
1049 uint32_t unit = luaL_optinteger(L, 5, 0);
1050 uint32_t prec = luaL_optinteger(L, 6, 0);
1052 char zname[4];
1053 const char* name = luaL_optstring(L, 7, NULL);
1054 if (name != NULL) {
1055 str2zchar(zname, name, 4);
1056 } else {
1057 zname[0] = hex2zchar((id & 0xf000) >> 12);
1058 zname[1] = hex2zchar((id & 0x0f00) >> 8);
1059 zname[2] = hex2zchar((id & 0x00f0) >> 4);
1060 zname[3] = hex2zchar((id & 0x000f) >> 0);
1063 int index = setTelemetryValue(TELEM_PROTO_LUA, id, subId, instance, value, unit, prec);
1064 if (index >= 0) {
1065 TelemetrySensor &telemetrySensor = g_model.telemetrySensors[index];
1066 telemetrySensor.id = id;
1067 telemetrySensor.subId = subId;
1068 telemetrySensor.instance = instance;
1069 telemetrySensor.init(zname, unit, prec);
1070 lua_pushboolean(L, true);
1071 } else {
1072 lua_pushboolean(L, false);
1074 return 1;
1077 /*luadoc
1078 @function defaultChannel(stick)
1080 Get channel assigned to stick. See Default Channel Order in General Settings
1082 @param stick (number) stick number (from 0 to 3)
1084 @retval number channel assigned to this stick (from 0 to 3)
1086 @retval nil stick not found
1088 @status current Introduced in 2.0.0
1090 static int luaDefaultChannel(lua_State * L)
1092 uint8_t stick = luaL_checkinteger(L, 1);
1093 for (int i=1; i<=4; i++) {
1094 int tmp = channel_order(i) - 1;
1095 if (tmp == stick) {
1096 lua_pushinteger(L, i-1);
1097 return 1;
1100 lua_pushnil(L);
1101 return 1;
1104 /*luadoc
1105 @function getRSSI()
1107 Get RSSI value as well as low and critical RSSI alarm levels (in dB)
1109 @retval rssi RSSI value (0 if no link)
1111 @retval alarm_low Configured low RSSI alarm level
1113 @retval alarm_crit Configured critical RSSI alarm level
1115 @status current Introduced in 2.2.0
1117 static int luaGetRSSI(lua_State * L)
1119 lua_pushunsigned(L, min((uint8_t)99, TELEMETRY_RSSI()));
1120 lua_pushunsigned(L, getRssiAlarmValue(0));
1121 lua_pushunsigned(L, getRssiAlarmValue(1));
1122 return 3;
1125 const luaL_Reg opentxLib[] = {
1126 { "getTime", luaGetTime },
1127 { "getDateTime", luaGetDateTime },
1128 { "getVersion", luaGetVersion },
1129 { "getGeneralSettings", luaGetGeneralSettings },
1130 { "getValue", luaGetValue },
1131 { "getRAS", luaGetRAS },
1132 { "getFieldInfo", luaGetFieldInfo },
1133 { "getFlightMode", luaGetFlightMode },
1134 { "playFile", luaPlayFile },
1135 { "playNumber", luaPlayNumber },
1136 { "playDuration", luaPlayDuration },
1137 { "playTone", luaPlayTone },
1138 { "playHaptic", luaPlayHaptic },
1139 { "popupInput", luaPopupInput },
1140 { "popupWarning", luaPopupWarning },
1141 { "popupConfirmation", luaPopupConfirmation },
1142 { "defaultStick", luaDefaultStick },
1143 { "defaultChannel", luaDefaultChannel },
1144 { "getRSSI", luaGetRSSI },
1145 { "killEvents", luaKillEvents },
1146 #if LCD_DEPTH > 1 && !defined(COLORLCD)
1147 { "GREY", luaGrey },
1148 #endif
1149 { "sportTelemetryPop", luaSportTelemetryPop },
1150 { "sportTelemetryPush", luaSportTelemetryPush },
1151 { "setTelemetryValue", luaSetTelemetryValue },
1152 #if defined(CROSSFIRE)
1153 { "crossfireTelemetryPop", luaCrossfireTelemetryPop },
1154 { "crossfireTelemetryPush", luaCrossfireTelemetryPush },
1155 #endif
1156 { NULL, NULL } /* sentinel */
1159 const luaR_value_entry opentxConstants[] = {
1160 { "FULLSCALE", RESX },
1161 { "XXLSIZE", XXLSIZE },
1162 { "DBLSIZE", DBLSIZE },
1163 { "MIDSIZE", MIDSIZE },
1164 { "SMLSIZE", SMLSIZE },
1165 { "INVERS", INVERS },
1166 { "BOLD", BOLD },
1167 { "BLINK", BLINK },
1168 { "RIGHT", RIGHT },
1169 { "LEFT", LEFT },
1170 { "PREC1", PREC1 },
1171 { "PREC2", PREC2 },
1172 { "VALUE", 0 }, // TODO reuse ZoneOption::Integer
1173 { "SOURCE", 1 }, // TODO reuse ZoneOption::Source
1174 { "REPLACE", MLTPX_REP },
1175 { "MIXSRC_FIRST_INPUT", MIXSRC_FIRST_INPUT },
1176 { "MIXSRC_Rud", MIXSRC_Rud },
1177 { "MIXSRC_Ele", MIXSRC_Ele },
1178 { "MIXSRC_Thr", MIXSRC_Thr },
1179 { "MIXSRC_Ail", MIXSRC_Ail },
1180 { "MIXSRC_SA", MIXSRC_SA },
1181 { "MIXSRC_SB", MIXSRC_SB },
1182 { "MIXSRC_SC", MIXSRC_SC },
1183 { "MIXSRC_SD", MIXSRC_SD },
1184 #if !defined(PCBX7D)
1185 { "MIXSRC_SE", MIXSRC_SE },
1186 { "MIXSRC_SG", MIXSRC_SG },
1187 #endif
1188 { "MIXSRC_SF", MIXSRC_SF },
1189 { "MIXSRC_SH", MIXSRC_SH },
1190 { "MIXSRC_CH1", MIXSRC_CH1 },
1191 { "SWSRC_LAST", SWSRC_LAST_LOGICAL_SWITCH },
1192 #if defined(COLORLCD)
1193 { "COLOR", ZoneOption::Color },
1194 { "CUSTOM_COLOR", CUSTOM_COLOR },
1195 { "TEXT_COLOR", TEXT_COLOR },
1196 { "TEXT_BGCOLOR", TEXT_BGCOLOR },
1197 { "TEXT_INVERTED_COLOR", TEXT_INVERTED_COLOR },
1198 { "TEXT_INVERTED_BGCOLOR", TEXT_INVERTED_BGCOLOR },
1199 { "LINE_COLOR", LINE_COLOR },
1200 { "SCROLLBOX_COLOR", SCROLLBOX_COLOR },
1201 { "MENU_TITLE_BGCOLOR", MENU_TITLE_BGCOLOR },
1202 { "MENU_TITLE_COLOR", MENU_TITLE_COLOR },
1203 { "MENU_TITLE_DISABLE_COLOR", MENU_TITLE_DISABLE_COLOR },
1204 { "ALARM_COLOR", ALARM_COLOR },
1205 { "WARNING_COLOR", WARNING_COLOR },
1206 { "TEXT_DISABLE_COLOR", TEXT_DISABLE_COLOR },
1207 { "HEADER_COLOR", HEADER_COLOR },
1208 { "CURVE_AXIS_COLOR", CURVE_AXIS_COLOR },
1209 { "CURVE_COLOR", CURVE_COLOR },
1210 { "CURVE_CURSOR_COLOR", CURVE_CURSOR_COLOR },
1211 { "TITLE_BGCOLOR", TITLE_BGCOLOR },
1212 { "TRIM_BGCOLOR", TRIM_BGCOLOR },
1213 { "TRIM_SHADOW_COLOR", TRIM_SHADOW_COLOR },
1214 { "MAINVIEW_PANES_COLOR", MAINVIEW_PANES_COLOR },
1215 { "MAINVIEW_GRAPHICS_COLOR", MAINVIEW_GRAPHICS_COLOR },
1216 { "HEADER_BGCOLOR", HEADER_BGCOLOR },
1217 { "HEADER_ICON_BGCOLOR", HEADER_ICON_BGCOLOR },
1218 { "HEADER_CURRENT_BGCOLOR", HEADER_CURRENT_BGCOLOR },
1219 { "OVERLAY_COLOR", OVERLAY_COLOR },
1220 { "MENU_HEADER_HEIGHT", MENU_HEADER_HEIGHT },
1221 { "WHITE", (double)WHITE },
1222 { "GREY", (double)GREY },
1223 { "DARKGREY", (double)DARKGREY },
1224 { "BLACK", (double)BLACK },
1225 { "YELLOW", (double)YELLOW },
1226 { "BLUE", (double)BLUE },
1227 { "LIGHTGREY", (double)LIGHTGREY },
1228 { "RED", (double)RED },
1229 { "DARKRED", (double)DARKRED },
1230 #else
1231 { "FIXEDWIDTH", FIXEDWIDTH },
1232 #endif
1233 #if defined(PCBHORUS)
1234 { "EVT_PAGEUP_FIRST", EVT_KEY_FIRST(KEY_PGUP) },
1235 { "EVT_PAGEDN_FIRST", EVT_KEY_FIRST(KEY_PGDN) },
1236 { "EVT_TELEM_FIRST", EVT_KEY_FIRST(KEY_TELEM) },
1237 { "EVT_MODEL_FIRST", EVT_KEY_FIRST(KEY_MODEL) },
1238 { "EVT_SYS_FIRST", EVT_KEY_FIRST(KEY_RADIO) },
1239 { "EVT_RTN_FIRST", EVT_KEY_FIRST(KEY_EXIT) },
1240 #elif defined(PCBTARANIS)
1241 { "EVT_MENU_BREAK", EVT_KEY_BREAK(KEY_MENU) },
1242 { "EVT_PAGE_BREAK", EVT_KEY_BREAK(KEY_PAGE) },
1243 { "EVT_PAGE_LONG", EVT_KEY_LONG(KEY_PAGE) },
1244 { "EVT_PLUS_BREAK", EVT_KEY_BREAK(KEY_PLUS) },
1245 { "EVT_MINUS_BREAK", EVT_KEY_BREAK(KEY_MINUS) },
1246 { "EVT_PLUS_FIRST", EVT_KEY_FIRST(KEY_PLUS) },
1247 { "EVT_MINUS_FIRST", EVT_KEY_FIRST(KEY_MINUS) },
1248 { "EVT_PLUS_REPT", EVT_KEY_REPT(KEY_PLUS) },
1249 { "EVT_MINUS_REPT", EVT_KEY_REPT(KEY_MINUS) },
1250 #if LCD_DEPTH > 1
1251 { "FILL_WHITE", FILL_WHITE },
1252 { "GREY_DEFAULT", GREY_DEFAULT },
1253 #endif
1254 { "FORCE", FORCE },
1255 { "ERASE", ERASE },
1256 { "ROUND", ROUND },
1257 #endif
1258 { "EVT_ENTER_BREAK", EVT_KEY_BREAK(KEY_ENTER) },
1259 { "EVT_ENTER_LONG", EVT_KEY_LONG(KEY_ENTER) },
1260 { "EVT_EXIT_BREAK", EVT_KEY_BREAK(KEY_EXIT) },
1261 #if defined(ROTARY_ENCODER_NAVIGATION)
1262 { "EVT_ROT_BREAK", EVT_KEY_BREAK(KEY_ENTER) },
1263 { "EVT_ROT_LONG", EVT_KEY_LONG(KEY_ENTER) },
1264 { "EVT_ROT_LEFT", EVT_ROTARY_LEFT },
1265 { "EVT_ROT_RIGHT", EVT_ROTARY_RIGHT },
1266 #endif
1267 { "SOLID", SOLID },
1268 { "DOTTED", DOTTED },
1269 { "LCD_W", LCD_W },
1270 { "LCD_H", LCD_H },
1271 { "PLAY_NOW", PLAY_NOW },
1272 { "PLAY_BACKGROUND", PLAY_BACKGROUND },
1273 { "TIMEHOUR", TIMEHOUR },
1275 #if defined(PCBHORUS)
1276 // Adding the unit consts for the set Telemetry function adds about 1k of flash usage
1277 {"UNIT_RAW", UNIT_RAW },
1278 {"UNIT_VOLTS", UNIT_VOLTS },
1279 {"UNIT_AMPS", UNIT_AMPS },
1280 {"UNIT_MILLIAMPS", UNIT_MILLIAMPS },
1281 {"UNIT_KTS", UNIT_KTS },
1282 {"UNIT_METERS_PER_SECOND", UNIT_METERS_PER_SECOND },
1283 {"UNIT_FEET_PER_SECOND", UNIT_FEET_PER_SECOND },
1284 {"UNIT_KMH", UNIT_KMH },
1285 {"UNIT_MPH", UNIT_MPH },
1286 {"UNIT_METERS", UNIT_METERS },
1287 {"UNIT_FEET", UNIT_FEET },
1288 {"UNIT_CELSIUS", UNIT_CELSIUS },
1289 {"UNIT_FAHRENHEIT", UNIT_FAHRENHEIT },
1290 {"UNIT_PERCENT", UNIT_PERCENT },
1291 {"UNIT_MAH", UNIT_MAH },
1292 {"UNIT_WATTS", UNIT_WATTS },
1293 {"UNIT_MILLIWATTS", UNIT_MILLIWATTS },
1294 {"UNIT_DB", UNIT_DB },
1295 {"UNIT_RPMS", UNIT_RPMS },
1296 {"UNIT_G", UNIT_G },
1297 {"UNIT_DEGREE", UNIT_DEGREE },
1298 {"UNIT_RADIANS", UNIT_RADIANS },
1299 {"UNIT_MILLILITERS", UNIT_MILLILITERS },
1300 {"UNIT_FLOZ", UNIT_FLOZ },
1301 {"UNIT_HOURS", UNIT_HOURS },
1302 {"UNIT_MINUTES", UNIT_MINUTES },
1303 {"UNIT_SECONDS", UNIT_SECONDS },
1304 {"UNIT_CELLS", UNIT_CELLS},
1305 {"UNIT_DATETIME", UNIT_DATETIME},
1306 {"UNIT_GPS", UNIT_GPS},
1307 {"UNIT_BITFIELD", UNIT_BITFIELD},
1308 {"UNIT_TEXT", UNIT_TEXT},
1309 #endif
1310 { NULL, 0 } /* sentinel */