optimise mavlink SS packet size (#3029)
[ExpressLRS.git] / src / lib / LUA / lua.cpp
blobdd5ab721690a82478afe517bab286d996981dd64
1 #include "lua.h"
2 #include "common.h"
3 #include "CRSF.h"
4 #include "logging.h"
6 #ifdef TARGET_RX
7 #include "telemetry.h"
9 extern Telemetry telemetry;
10 #else
11 #include "CRSFHandset.h"
12 #endif
14 //LUA VARIABLES//
16 #ifdef TARGET_TX
17 static uint8_t luaWarningFlags = 0b00000000; //8 flag, 1 bit for each flag. set the bit to 1 to show specific warning. 3 MSB is for critical flag
18 static void (*devicePingCallback)() = nullptr;
19 #endif
21 #define LUA_MAX_PARAMS 64
22 static uint8_t parameterType;
23 static uint8_t parameterIndex;
24 static uint8_t parameterArg;
25 static volatile bool UpdateParamReq = false;
27 static struct luaPropertiesCommon *paramDefinitions[LUA_MAX_PARAMS] = {0}; // array of luaItem_*
28 static luaCallback paramCallbacks[LUA_MAX_PARAMS] = {0};
29 static uint8_t lastLuaField = 0;
30 static uint8_t nextStatusChunk = 0;
32 static uint8_t luaSelectionOptionMax(const char *strOptions)
34 // Returns the max index of the semicolon-delimited option string
35 // e.g. A;B;C;D = 3
36 uint8_t retVal = 0;
37 while (true)
39 char c = *strOptions++;
40 if (c == ';')
41 ++retVal;
42 else if (c == '\0')
43 return retVal;
47 uint8_t getLabelLength(char *text, char separator){
48 char *c = (char*)text;
49 //get label length up to null or lua separator ;
50 while(*c != separator && *c != '\0'){
51 c++;
53 return c-text;
56 uint8_t findLuaSelectionLabel(const void *luaStruct, char *outarray, uint8_t value)
58 const struct luaItem_selection *p1 = (const struct luaItem_selection *)luaStruct;
59 char *c = (char *)p1->options;
60 uint8_t count = 0;
61 while (*c != '\0'){
62 //if count is equal to the parameter value, print out the label to the array
63 if(count == value){
64 uint8_t labelLength = getLabelLength(c,';');
65 //write label to destination array
66 strlcpy(outarray, c, labelLength+1);
67 strlcpy(outarray + labelLength, p1->units, strlen(p1->units)+1);
68 return strlen(outarray);
70 //increment the count until value is found
71 if(*c == ';'){
72 count++;
74 c++;
76 return 0;
79 static uint8_t *luaTextSelectionStructToArray(const void *luaStruct, uint8_t *next)
81 const struct luaItem_selection *p1 = (const struct luaItem_selection *)luaStruct;
82 next = (uint8_t *)stpcpy((char *)next, p1->options) + 1;
83 *next++ = p1->value; // value
84 *next++ = 0; // min
85 *next++ = luaSelectionOptionMax(p1->options); //max
86 *next++ = 0; // default value
87 return (uint8_t *)stpcpy((char *)next, p1->units);
90 static uint8_t *luaCommandStructToArray(const void *luaStruct, uint8_t *next)
92 const struct luaItem_command *p1 = (const struct luaItem_command *)luaStruct;
93 *next++ = p1->step;
94 *next++ = 200; // timeout in 10ms
95 return (uint8_t *)stpcpy((char *)next, p1->info);
98 static uint8_t *luaInt8StructToArray(const void *luaStruct, uint8_t *next)
100 const struct luaItem_int8 *p1 = (const struct luaItem_int8 *)luaStruct;
101 memcpy(next, &p1->properties, sizeof(p1->properties));
102 next += sizeof(p1->properties);
103 *next++ = 0; // default value
104 return (uint8_t *)stpcpy((char *)next, p1->units);
107 static uint8_t *luaInt16StructToArray(const void *luaStruct, uint8_t *next)
109 const struct luaItem_int16 *p1 = (const struct luaItem_int16 *)luaStruct;
110 memcpy(next, &p1->properties, sizeof(p1->properties));
111 next += sizeof(p1->properties);
112 *next++ = 0; // default value byte 1
113 *next++ = 0; // default value byte 2
114 return (uint8_t *)stpcpy((char *)next, p1->units);
117 static uint8_t *luaStringStructToArray(const void *luaStruct, uint8_t *next)
119 const struct luaItem_string *p1 = (const struct luaItem_string *)luaStruct;
120 return (uint8_t *)stpcpy((char *)next, p1->value);
122 static uint8_t *luaFolderStructToArray(const void *luaStruct, uint8_t *next)
124 const struct luaItem_folder *p1 = (const struct luaItem_folder *)luaStruct;
125 if(p1->dyn_name != NULL){
126 return (uint8_t *)stpcpy((char *)next, p1->dyn_name) + 1;
127 } else {
128 return (uint8_t *)stpcpy((char *)next, p1->common.name) + 1;
131 static uint8_t sendCRSFparam(crsf_frame_type_e frameType, uint8_t fieldChunk, struct luaPropertiesCommon *luaData)
133 uint8_t dataType = luaData->type & CRSF_FIELD_TYPE_MASK;
135 // 256 max payload + (FieldID + ChunksRemain + Parent + Type)
136 // Chunk 1: (FieldID + ChunksRemain + Parent + Type) + fieldChunk0 data
137 // Chunk 2-N: (FieldID + ChunksRemain) + fieldChunk1 data
138 uint8_t chunkBuffer[256+4];
139 // Start the field payload at 2 to leave room for (FieldID + ChunksRemain)
140 chunkBuffer[2] = luaData->parent;
141 chunkBuffer[3] = dataType;
142 #ifdef TARGET_TX
143 // Set the hidden flag
144 chunkBuffer[3] |= luaData->type & CRSF_FIELD_HIDDEN ? 0x80 : 0;
145 if (CRSFHandset::elrsLUAmode) {
146 chunkBuffer[3] |= luaData->type & CRSF_FIELD_ELRS_HIDDEN ? 0x80 : 0;
148 #else
149 chunkBuffer[3] |= luaData->type;
150 uint8_t paramInformation[DEVICE_INFORMATION_LENGTH];
151 #endif
153 // Copy the name to the buffer starting at chunkBuffer[4]
154 uint8_t *chunkStart = (uint8_t *)stpcpy((char *)&chunkBuffer[4], luaData->name) + 1;
155 uint8_t *dataEnd;
157 switch(dataType) {
158 case CRSF_TEXT_SELECTION:
159 dataEnd = luaTextSelectionStructToArray(luaData, chunkStart);
160 break;
161 case CRSF_COMMAND:
162 dataEnd = luaCommandStructToArray(luaData, chunkStart);
163 break;
164 case CRSF_INT8: // fallthrough
165 case CRSF_UINT8:
166 dataEnd = luaInt8StructToArray(luaData, chunkStart);
167 break;
168 case CRSF_INT16: // fallthrough
169 case CRSF_UINT16:
170 dataEnd = luaInt16StructToArray(luaData, chunkStart);
171 break;
172 case CRSF_STRING: // fallthough
173 case CRSF_INFO:
174 dataEnd = luaStringStructToArray(luaData, chunkStart);
175 break;
176 case CRSF_FOLDER:
177 // re-fetch the lua data name, because luaFolderStructToArray will decide whether
178 //to return the fixed name or dynamic name.
179 chunkStart = luaFolderStructToArray(luaData, &chunkBuffer[4]);
180 // subtract 1 because dataSize expects the end to not include the null
181 // which is already accounted for in chunkStart
182 dataEnd = chunkStart - 1;
183 break;
184 case CRSF_FLOAT:
185 case CRSF_OUT_OF_RANGE:
186 default:
187 return 0;
190 // dataEnd points to the end of the last string
191 // -2 bytes Lua chunk header: FieldId, ChunksRemain
192 // +1 for the null on the last string
193 uint8_t dataSize = (dataEnd - chunkBuffer) - 2 + 1;
194 // Maximum number of chunked bytes that can be sent in one response
195 // 6 bytes CRSF header/CRC: Dest, Len, Type, ExtSrc, ExtDst, CRC
196 // 2 bytes Lua chunk header: FieldId, ChunksRemain
197 #ifdef TARGET_TX
198 uint8_t chunkMax = handset->GetMaxPacketBytes() - 6 - 2;
199 #else
200 uint8_t chunkMax = CRSF_MAX_PACKET_LEN - 6 - 2;
201 #endif
202 // How many chunks needed to send this field (rounded up)
203 uint8_t chunkCnt = (dataSize + chunkMax - 1) / chunkMax;
204 // Data left to send is adjustedSize - chunks sent already
205 uint8_t chunkSize = min((uint8_t)(dataSize - (fieldChunk * chunkMax)), chunkMax);
207 // Move chunkStart back 2 bytes to add (FieldId + ChunksRemain) to each packet
208 chunkStart = &chunkBuffer[fieldChunk * chunkMax];
209 chunkStart[0] = luaData->id; // FieldId
210 chunkStart[1] = chunkCnt - (fieldChunk + 1); // ChunksRemain
211 #ifdef TARGET_TX
212 CRSFHandset::packetQueueExtended(frameType, chunkStart, chunkSize + 2);
213 #else
214 memcpy(paramInformation + sizeof(crsf_ext_header_t),chunkStart,chunkSize + 2);
216 CRSF::SetExtendedHeaderAndCrc(paramInformation, frameType, chunkSize + CRSF_FRAME_LENGTH_EXT_TYPE_CRC + 2, CRSF_ADDRESS_CRSF_RECEIVER, CRSF_ADDRESS_CRSF_TRANSMITTER);
218 telemetry.AppendTelemetryPackage(paramInformation);
219 #endif
220 return chunkCnt - (fieldChunk+1);
223 static void pushResponseChunk(struct luaItem_command *cmd) {
224 DBGVLN("sending response for [%s] chunk=%u step=%u", cmd->common.name, nextStatusChunk, cmd->step);
225 if (sendCRSFparam(CRSF_FRAMETYPE_PARAMETER_SETTINGS_ENTRY, nextStatusChunk, (struct luaPropertiesCommon *)cmd) == 0) {
226 nextStatusChunk = 0;
227 } else {
228 nextStatusChunk++;
232 void sendLuaCommandResponse(struct luaItem_command *cmd, luaCmdStep_e step, const char *message) {
233 cmd->step = step;
234 cmd->info = message;
235 nextStatusChunk = 0;
236 pushResponseChunk(cmd);
239 #ifdef TARGET_TX
240 static void luaSupressCriticalErrors()
242 // clear the critical error bits of the warning flags
243 luaWarningFlags &= 0b00011111;
246 void setLuaWarningFlag(lua_Flags flag, bool value)
248 if (value)
250 luaWarningFlags |= 1 << (uint8_t)flag;
252 else
254 luaWarningFlags &= ~(1 << (uint8_t)flag);
258 static void updateElrsFlags()
260 setLuaWarningFlag(LUA_FLAG_MODEL_MATCH, connectionState == connected && connectionHasModelMatch == false);
261 setLuaWarningFlag(LUA_FLAG_CONNECTED, connectionState == connected);
262 setLuaWarningFlag(LUA_FLAG_ISARMED, handset->IsArmed());
265 void sendELRSstatus()
267 constexpr const char *messages[] = { //higher order = higher priority
268 "", //status2 = connected status
269 "", //status1, reserved for future use
270 "Model Mismatch", //warning3, model mismatch
271 "[ ! Armed ! ]", //warning2, AUX1 high / armed
272 "", //warning1, reserved for future use
273 "Not while connected", //critical warning3, trying to change a protected value while connected
274 "Baud rate too low", //critical warning2, changing packet rate and baud rate too low
275 "" //critical warning1, reserved for future use
277 const char * warningInfo = "";
279 for (int i=7 ; i>=0 ; i--)
281 if (luaWarningFlags & (1<<i))
283 warningInfo = messages[i];
284 break;
287 uint8_t buffer[sizeof(tagLuaElrsParams) + strlen(warningInfo) + 1];
288 struct tagLuaElrsParams * const params = (struct tagLuaElrsParams *)buffer;
290 params->pktsBad = CRSFHandset::BadPktsCountResult;
291 params->pktsGood = htobe16(CRSFHandset::GoodPktsCountResult);
292 params->flags = luaWarningFlags;
293 // to support sending a params.msg, buffer should be extended by the strlen of the message
294 // and copied into params->msg (with trailing null)
295 strcpy(params->msg, warningInfo);
296 CRSFHandset::packetQueueExtended(0x2E, &buffer, sizeof(buffer));
299 void luaRegisterDevicePingCallback(void (*callback)())
301 devicePingCallback = callback;
304 #endif
306 void luaParamUpdateReq(uint8_t type, uint8_t index, uint8_t arg)
308 parameterType = type;
309 parameterIndex = index;
310 parameterArg = arg;
311 UpdateParamReq = true;
314 void registerLUAParameter(void *definition, luaCallback callback, uint8_t parent)
316 if (definition == nullptr)
318 static uint8_t agentLiteFolder[4+LUA_MAX_PARAMS+2] = "HooJ";
319 static struct luaItem_folder luaAgentLite = {
320 {(const char *)agentLiteFolder, CRSF_FOLDER},
323 paramDefinitions[0] = (struct luaPropertiesCommon *)&luaAgentLite;
324 paramCallbacks[0] = 0;
325 uint8_t *pos = agentLiteFolder + 4;
326 for (int i=1;i<=lastLuaField;i++)
328 if (paramDefinitions[i]->parent == 0)
330 *pos++ = i;
333 *pos++ = 0xFF;
334 *pos++ = 0;
335 return;
338 struct luaPropertiesCommon *p = (struct luaPropertiesCommon *)definition;
339 lastLuaField++;
340 p->id = lastLuaField;
341 p->parent = parent;
342 paramDefinitions[lastLuaField] = p;
343 paramCallbacks[lastLuaField] = callback;
346 bool luaHandleUpdateParameter()
348 if (UpdateParamReq == false)
350 return false;
353 switch(parameterType)
355 case CRSF_FRAMETYPE_PARAMETER_WRITE:
356 if (parameterIndex == 0)
358 // special case for elrs linkstat request
359 #ifdef TARGET_TX
360 DBGVLN("ELRS status request");
361 updateElrsFlags();
362 sendELRSstatus();
363 } else if (parameterIndex == 0x2E) {
364 luaSupressCriticalErrors();
365 #endif
366 } else {
367 uint8_t id = parameterIndex;
368 uint8_t arg = parameterArg;
369 struct luaPropertiesCommon *p = paramDefinitions[id];
370 DBGLN("Set Lua [%s]=%u", p->name, arg);
371 if (id < LUA_MAX_PARAMS && paramCallbacks[id]) {
372 // While the command is executing, the handset will send `WRITE state=lcsQuery`.
373 // paramCallbacks will set the value when nextStatusChunk == 0, or send any
374 // remaining chunks when nextStatusChunk != 0
375 if (arg == lcsQuery && nextStatusChunk != 0) {
376 pushResponseChunk((struct luaItem_command *)p);
377 } else {
378 paramCallbacks[id](p, arg);
382 break;
384 case CRSF_FRAMETYPE_DEVICE_PING:
385 #ifdef TARGET_TX
386 devicePingCallback();
387 luaSupressCriticalErrors();
388 #endif
389 sendLuaDevicePacket();
390 break;
392 case CRSF_FRAMETYPE_PARAMETER_READ:
394 uint8_t fieldId = parameterIndex;
395 uint8_t fieldChunk = parameterArg;
396 DBGVLN("Read lua param %u %u", fieldId, fieldChunk);
397 if (fieldId < LUA_MAX_PARAMS && paramDefinitions[fieldId])
399 struct luaItem_command *field = (struct luaItem_command *)paramDefinitions[fieldId];
400 uint8_t dataType = field->common.type & CRSF_FIELD_TYPE_MASK;
401 // On first chunk of a command, reset the step/info of the command
402 if (dataType == CRSF_COMMAND && fieldChunk == 0)
404 field->step = lcsIdle;
405 field->info = "";
407 sendCRSFparam(CRSF_FRAMETYPE_PARAMETER_SETTINGS_ENTRY, fieldChunk, &field->common);
410 break;
412 #if defined(TARGET_RX)
413 // This is a bit of a hack, it just so happens that the parameterIndex and parameterArg parameters
414 // are in the same place as the bind command. This should be handled further up the receive chain
415 // but the call in Telemetry.ShouldCallEnterBind() only works if serial data is coming in so the
416 // whole stack needs a bit of a refactor to not have similar code duplicated all over
417 case CRSF_FRAMETYPE_COMMAND:
418 if (parameterIndex == CRSF_COMMAND_SUBCMD_RX && parameterArg == CRSF_COMMAND_SUBCMD_RX_BIND)
419 EnterBindingModeSafely();
420 break;
421 #endif
423 default:
424 DBGLN("Unknown LUA %x", parameterType);
427 UpdateParamReq = false;
428 return true;
431 void sendLuaDevicePacket(void)
433 uint8_t deviceInformation[DEVICE_INFORMATION_LENGTH];
434 CRSF::GetDeviceInformation(deviceInformation, lastLuaField);
435 // does append header + crc again so substract size from length
436 #ifdef TARGET_TX
437 CRSFHandset::packetQueueExtended(CRSF_FRAMETYPE_DEVICE_INFO, deviceInformation + sizeof(crsf_ext_header_t), DEVICE_INFORMATION_PAYLOAD_LENGTH);
438 #else
439 CRSF::SetExtendedHeaderAndCrc(deviceInformation, CRSF_FRAMETYPE_DEVICE_INFO, DEVICE_INFORMATION_FRAME_SIZE, CRSF_ADDRESS_CRSF_RECEIVER, CRSF_ADDRESS_CRSF_TRANSMITTER);
440 telemetry.AppendTelemetryPackage(deviceInformation);
441 #endif