9 extern Telemetry telemetry
;
11 #include "CRSFHandset.h"
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;
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
39 char c
= *strOptions
++;
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'){
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
;
62 //if count is equal to the parameter value, print out the label to the array
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
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
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
;
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;
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
;
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;
149 chunkBuffer
[3] |= luaData
->type
;
150 uint8_t paramInformation
[DEVICE_INFORMATION_LENGTH
];
153 // Copy the name to the buffer starting at chunkBuffer[4]
154 uint8_t *chunkStart
= (uint8_t *)stpcpy((char *)&chunkBuffer
[4], luaData
->name
) + 1;
158 case CRSF_TEXT_SELECTION
:
159 dataEnd
= luaTextSelectionStructToArray(luaData
, chunkStart
);
162 dataEnd
= luaCommandStructToArray(luaData
, chunkStart
);
164 case CRSF_INT8
: // fallthrough
166 dataEnd
= luaInt8StructToArray(luaData
, chunkStart
);
168 case CRSF_INT16
: // fallthrough
170 dataEnd
= luaInt16StructToArray(luaData
, chunkStart
);
172 case CRSF_STRING
: // fallthough
174 dataEnd
= luaStringStructToArray(luaData
, chunkStart
);
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;
185 case CRSF_OUT_OF_RANGE
:
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
198 uint8_t chunkMax
= handset
->GetMaxPacketBytes() - 6 - 2;
200 uint8_t chunkMax
= CRSF_MAX_PACKET_LEN
- 6 - 2;
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
212 CRSFHandset::packetQueueExtended(frameType
, chunkStart
, chunkSize
+ 2);
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
);
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) {
232 void sendLuaCommandResponse(struct luaItem_command
*cmd
, luaCmdStep_e step
, const char *message
) {
236 pushResponseChunk(cmd
);
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
)
250 luaWarningFlags
|= 1 << (uint8_t)flag
;
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
];
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
;
306 void luaParamUpdateReq(uint8_t type
, uint8_t index
, uint8_t arg
)
308 parameterType
= type
;
309 parameterIndex
= index
;
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)
338 struct luaPropertiesCommon
*p
= (struct luaPropertiesCommon
*)definition
;
340 p
->id
= lastLuaField
;
342 paramDefinitions
[lastLuaField
] = p
;
343 paramCallbacks
[lastLuaField
] = callback
;
346 bool luaHandleUpdateParameter()
348 if (UpdateParamReq
== false)
353 switch(parameterType
)
355 case CRSF_FRAMETYPE_PARAMETER_WRITE
:
356 if (parameterIndex
== 0)
358 // special case for elrs linkstat request
360 DBGVLN("ELRS status request");
363 } else if (parameterIndex
== 0x2E) {
364 luaSupressCriticalErrors();
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
);
378 paramCallbacks
[id
](p
, arg
);
384 case CRSF_FRAMETYPE_DEVICE_PING
:
386 devicePingCallback();
387 luaSupressCriticalErrors();
389 sendLuaDevicePacket();
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
;
407 sendCRSFparam(CRSF_FRAMETYPE_PARAMETER_SETTINGS_ENTRY
, fieldChunk
, &field
->common
);
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();
424 DBGLN("Unknown LUA %x", parameterType
);
427 UpdateParamReq
= false;
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
437 CRSFHandset::packetQueueExtended(CRSF_FRAMETYPE_DEVICE_INFO
, deviceInformation
+ sizeof(crsf_ext_header_t
), DEVICE_INFORMATION_PAYLOAD_LENGTH
);
439 CRSF::SetExtendedHeaderAndCrc(deviceInformation
, CRSF_FRAMETYPE_DEVICE_INFO
, DEVICE_INFORMATION_FRAME_SIZE
, CRSF_ADDRESS_CRSF_RECEIVER
, CRSF_ADDRESS_CRSF_TRANSMITTER
);
440 telemetry
.AppendTelemetryPackage(deviceInformation
);