This commit is contained in:
Ben Meadors 2023-01-22 08:49:06 -06:00
parent 4c9f0b2646
commit ac6d4e33d4
4 changed files with 153 additions and 212 deletions

View File

@ -29,8 +29,7 @@ PhoneAPI::~PhoneAPI()
void PhoneAPI::handleStartConfig()
{
// Must be before setting state (because state is how we know !connected)
if (!isConnected())
{
if (!isConnected()) {
onConnectionChanged(true);
observe(&service.fromNumChanged);
observe(&xModem.packetReady);
@ -47,8 +46,7 @@ void PhoneAPI::handleStartConfig()
void PhoneAPI::close()
{
if (state != STATE_SEND_NOTHING)
{
if (state != STATE_SEND_NOTHING) {
state = STATE_SEND_NOTHING;
unobserve(&service.fromNumChanged);
@ -62,11 +60,9 @@ void PhoneAPI::close()
void PhoneAPI::checkConnectionTimeout()
{
if (isConnected())
{
if (isConnected()) {
bool newContact = checkIsConnected();
if (!newContact)
{
if (!newContact) {
LOG_INFO("Lost phone connection\n");
close();
}
@ -84,10 +80,8 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
// return (lastContactMsec != 0) &&
memset(&toRadioScratch, 0, sizeof(toRadioScratch));
if (pb_decode_from_bytes(buf, bufLength, &meshtastic_ToRadio_msg, &toRadioScratch))
{
switch (toRadioScratch.which_payload_variant)
{
if (pb_decode_from_bytes(buf, bufLength, &meshtastic_ToRadio_msg, &toRadioScratch)) {
switch (toRadioScratch.which_payload_variant) {
case meshtastic_ToRadio_packet_tag:
return handleToRadioPacket(toRadioScratch.packet);
case meshtastic_ToRadio_want_config_id_tag:
@ -108,9 +102,7 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
// LOG_DEBUG("Error: unexpected ToRadio variant\n");
break;
}
}
else
{
} else {
LOG_ERROR("Error: ignoring malformed toradio\n");
}
@ -133,8 +125,7 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
*/
size_t PhoneAPI::getFromRadio(uint8_t *buf)
{
if (!available())
{
if (!available()) {
// LOG_DEBUG("getFromRadio=not available\n");
return 0;
}
@ -142,8 +133,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
memset(&fromRadioScratch, 0, sizeof(fromRadioScratch));
// Advance states as needed
switch (state)
{
switch (state) {
case STATE_SEND_NOTHING:
LOG_INFO("getFromRadio=STATE_SEND_NOTHING\n");
break;
@ -160,22 +150,18 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
service.refreshMyNodeInfo(); // Update my NodeInfo because the client will be asking for it soon.
break;
case STATE_SEND_NODEINFO:
{
case STATE_SEND_NODEINFO: {
LOG_INFO("getFromRadio=STATE_SEND_NODEINFO\n");
const meshtastic_NodeInfo *info = nodeInfoForPhone;
nodeInfoForPhone = NULL; // We just consumed a nodeinfo, will need a new one next time
if (info)
{
if (info) {
LOG_INFO("Sending nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", info->num, info->last_heard, info->user.id,
info->user.long_name);
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag;
fromRadioScratch.node_info = *info;
// Stay in current state until done sending nodeinfos
}
else
{
} else {
LOG_INFO("Done sending nodeinfos\n");
state = STATE_SEND_CHANNELS;
// Go ahead and send that ID right now
@ -190,8 +176,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
fromRadioScratch.channel = channels.getByIndex(config_state);
config_state++;
// Advance when we have sent all of our Channels
if (config_state >= MAX_NUM_CHANNELS)
{
if (config_state >= MAX_NUM_CHANNELS) {
state = STATE_SEND_CONFIG;
config_state = _meshtastic_AdminMessage_ConfigType_MIN + 1;
}
@ -200,8 +185,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
case STATE_SEND_CONFIG:
LOG_INFO("getFromRadio=STATE_SEND_CONFIG\n");
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_config_tag;
switch (config_state)
{
switch (config_state) {
case meshtastic_Config_device_tag:
fromRadioScratch.config.which_payload_variant = meshtastic_Config_device_tag;
fromRadioScratch.config.payload_variant.device = config.device;
@ -240,8 +224,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
config_state++;
// Advance when we have sent all of our config objects
if (config_state > (_meshtastic_AdminMessage_ConfigType_MAX + 1))
{
if (config_state > (_meshtastic_AdminMessage_ConfigType_MAX + 1)) {
state = STATE_SEND_MODULECONFIG;
config_state = _meshtastic_AdminMessage_ModuleConfigType_MIN + 1;
}
@ -250,8 +233,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
case STATE_SEND_MODULECONFIG:
LOG_INFO("getFromRadio=STATE_SEND_MODULECONFIG\n");
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_moduleConfig_tag;
switch (config_state)
{
switch (config_state) {
case meshtastic_ModuleConfig_mqtt_tag:
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_mqtt_tag;
fromRadioScratch.moduleConfig.payload_variant.mqtt = moduleConfig.mqtt;
@ -294,8 +276,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
config_state++;
// Advance when we have sent all of our ModuleConfig objects
if (config_state > (_meshtastic_AdminMessage_ModuleConfigType_MAX + 1))
{
if (config_state > (_meshtastic_AdminMessage_ModuleConfigType_MAX + 1)) {
state = STATE_SEND_COMPLETE_ID;
config_state = 0;
}
@ -312,20 +293,15 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
case STATE_SEND_PACKETS:
// Do we have a message from the mesh?
LOG_INFO("getFromRadio=STATE_SEND_PACKETS\n");
if (queueStatusPacketForPhone)
{
if (queueStatusPacketForPhone) {
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_queueStatus_tag;
fromRadioScratch.queueStatus = *queueStatusPacketForPhone;
releaseQueueStatusPhonePacket();
}
else if (xmodemPacketForPhone.control != meshtastic_XModem_Control_NUL)
{
} else if (xmodemPacketForPhone.control != meshtastic_XModem_Control_NUL) {
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_xmodemPacket_tag;
fromRadioScratch.xmodemPacket = xmodemPacketForPhone;
xmodemPacketForPhone = meshtastic_XModem_init_zero;
}
else if (packetForPhone)
{
} else if (packetForPhone) {
printPacket("phone downloaded packet", packetForPhone);
// Encapsulate as a FromRadio packet
@ -340,8 +316,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
}
// Do we have a message from the mesh?
if (fromRadioScratch.which_payload_variant != 0)
{
if (fromRadioScratch.which_payload_variant != 0) {
// Encapsulate as a FromRadio packet
size_t numbytes = pb_encode_to_bytes(buf, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch);
@ -360,8 +335,7 @@ void PhoneAPI::handleDisconnect()
void PhoneAPI::releasePhonePacket()
{
if (packetForPhone)
{
if (packetForPhone) {
service.releaseToPool(packetForPhone); // we just copied the bytes, so don't need this buffer anymore
packetForPhone = NULL;
}
@ -369,8 +343,7 @@ void PhoneAPI::releasePhonePacket()
void PhoneAPI::releaseQueueStatusPhonePacket()
{
if (queueStatusPacketForPhone)
{
if (queueStatusPacketForPhone) {
service.releaseQueueStatusToPool(queueStatusPacketForPhone);
queueStatusPacketForPhone = NULL;
}
@ -381,8 +354,7 @@ void PhoneAPI::releaseQueueStatusPhonePacket()
*/
bool PhoneAPI::available()
{
switch (state)
{
switch (state) {
case STATE_SEND_NOTHING:
return false;
case STATE_SEND_MY_INFO:
@ -397,8 +369,7 @@ bool PhoneAPI::available()
nodeInfoForPhone = nodeDB.readNextInfo();
return true; // Always say we have something, because we might need to advance our state machine
case STATE_SEND_PACKETS:
{
case STATE_SEND_PACKETS: {
if (!queueStatusPacketForPhone)
queueStatusPacketForPhone = service.getQueueStatusForPhone();
bool hasPacket = !!queueStatusPacketForPhone;
@ -441,12 +412,10 @@ int PhoneAPI::onNotify(uint32_t newValue)
checkConnectionTimeout(); // a handy place to check if we've heard from the phone (since the BLE version doesn't call this
// from idle)
if (state == STATE_SEND_PACKETS)
{
if (state == STATE_SEND_PACKETS) {
LOG_INFO("Telling client we have new packets %u\n", newValue);
onNowHasData(newValue);
}
else
} else
LOG_DEBUG("(Client not yet interested in packets)\n");
return 0;

View File

@ -19,119 +19,118 @@
class PhoneAPI
: public Observer<uint32_t> // FIXME, we shouldn't be inheriting from Observer, instead use CallbackObserver as a member
{
enum State
{
STATE_SEND_NOTHING, // Initial state, don't send anything until the client starts asking for config
STATE_SEND_MY_INFO, // send our my info record
STATE_SEND_NODEINFO, // states progress in this order as the device sends to to the client
STATE_SEND_CHANNELS, // Send all channels
STATE_SEND_CONFIG, // Replacement for the old Radioconfig
STATE_SEND_MODULECONFIG, // Send Module specific config
STATE_SEND_COMPLETE_ID,
STATE_SEND_PACKETS // send packets or debug strings
};
enum State {
STATE_SEND_NOTHING, // Initial state, don't send anything until the client starts asking for config
STATE_SEND_MY_INFO, // send our my info record
STATE_SEND_NODEINFO, // states progress in this order as the device sends to to the client
STATE_SEND_CHANNELS, // Send all channels
STATE_SEND_CONFIG, // Replacement for the old Radioconfig
STATE_SEND_MODULECONFIG, // Send Module specific config
STATE_SEND_COMPLETE_ID,
STATE_SEND_PACKETS // send packets or debug strings
};
State state = STATE_SEND_NOTHING;
State state = STATE_SEND_NOTHING;
uint8_t config_state = 0;
uint8_t config_state = 0;
/**
* Each packet sent to the phone has an incrementing count
*/
uint32_t fromRadioNum = 0;
/**
* Each packet sent to the phone has an incrementing count
*/
uint32_t fromRadioNum = 0;
/// We temporarily keep the packet here between the call to available and getFromRadio. We will free it after the phone
/// downloads it
meshtastic_MeshPacket *packetForPhone = NULL;
/// We temporarily keep the packet here between the call to available and getFromRadio. We will free it after the phone
/// downloads it
meshtastic_MeshPacket *packetForPhone = NULL;
// file transfer packets destined for phone. Push it to the queue then free it.
meshtastic_XModem xmodemPacketForPhone = meshtastic_XModem_init_zero;
// file transfer packets destined for phone. Push it to the queue then free it.
meshtastic_XModem xmodemPacketForPhone = meshtastic_XModem_init_zero;
// Keep QueueStatus packet just as packetForPhone
meshtastic_QueueStatus *queueStatusPacketForPhone = NULL;
// Keep QueueStatus packet just as packetForPhone
meshtastic_QueueStatus *queueStatusPacketForPhone = NULL;
/// We temporarily keep the nodeInfo here between the call to available and getFromRadio
const meshtastic_NodeInfo *nodeInfoForPhone = NULL;
/// We temporarily keep the nodeInfo here between the call to available and getFromRadio
const meshtastic_NodeInfo *nodeInfoForPhone = NULL;
meshtastic_ToRadio toRadioScratch = {
0}; // this is a static scratch object, any data must be copied elsewhere before returning
meshtastic_ToRadio toRadioScratch = {
0}; // this is a static scratch object, any data must be copied elsewhere before returning
/// Use to ensure that clients don't get confused about old messages from the radio
uint32_t config_nonce = 0;
/// Use to ensure that clients don't get confused about old messages from the radio
uint32_t config_nonce = 0;
public:
PhoneAPI();
public:
PhoneAPI();
/// Destructor - calls close()
virtual ~PhoneAPI();
/// Destructor - calls close()
virtual ~PhoneAPI();
// Call this when the client drops the connection, resets the state to STATE_SEND_NOTHING
// Unregisters our observer. A closed connection **can** be reopened by calling init again.
virtual void close();
// Call this when the client drops the connection, resets the state to STATE_SEND_NOTHING
// Unregisters our observer. A closed connection **can** be reopened by calling init again.
virtual void close();
/**
* Handle a ToRadio protobuf
* @return true true if a packet was queued for sending (so that caller can yield)
*/
virtual bool handleToRadio(const uint8_t *buf, size_t len);
/**
* Handle a ToRadio protobuf
* @return true true if a packet was queued for sending (so that caller can yield)
*/
virtual bool handleToRadio(const uint8_t *buf, size_t len);
/**
* Get the next packet we want to send to the phone
*
* We assume buf is at least FromRadio_size bytes long.
* Returns number of bytes in the FromRadio packet (or 0 if no packet available)
*/
size_t getFromRadio(uint8_t *buf);
/**
* Get the next packet we want to send to the phone
*
* We assume buf is at least FromRadio_size bytes long.
* Returns number of bytes in the FromRadio packet (or 0 if no packet available)
*/
size_t getFromRadio(uint8_t *buf);
/**
* Return true if we have data available to send to the phone
*/
bool available();
/**
* Return true if we have data available to send to the phone
*/
bool available();
bool isConnected() { return state != STATE_SEND_NOTHING; }
bool isConnected() { return state != STATE_SEND_NOTHING; }
void setInitialState() { state = STATE_SEND_MY_INFO; }
void setInitialState() { state = STATE_SEND_MY_INFO; }
protected:
/// Our fromradio packet while it is being assembled
meshtastic_FromRadio fromRadioScratch = {};
protected:
/// Our fromradio packet while it is being assembled
meshtastic_FromRadio fromRadioScratch = {};
/** the last msec we heard from the client on the other side of this link */
uint32_t lastContactMsec = 0;
/** the last msec we heard from the client on the other side of this link */
uint32_t lastContactMsec = 0;
/// Hookable to find out when connection changes
virtual void onConnectionChanged(bool connected) {}
/// Hookable to find out when connection changes
virtual void onConnectionChanged(bool connected) {}
/// If we haven't heard from the other side in a while then say not connected
void checkConnectionTimeout();
/// If we haven't heard from the other side in a while then say not connected
void checkConnectionTimeout();
/// Check the current underlying physical link to see if the client is currently connected
virtual bool checkIsConnected() = 0;
/// Check the current underlying physical link to see if the client is currently connected
virtual bool checkIsConnected() = 0;
/**
* Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies)
*/
virtual void onNowHasData(uint32_t fromRadioNum) {}
/**
* Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies)
*/
virtual void onNowHasData(uint32_t fromRadioNum) {}
/**
* Subclasses can use this to find out when a client drops the link
*/
virtual void handleDisconnect();
/**
* Subclasses can use this to find out when a client drops the link
*/
virtual void handleDisconnect();
private:
void releasePhonePacket();
private:
void releasePhonePacket();
void releaseQueueStatusPhonePacket();
void releaseQueueStatusPhonePacket();
/// begin a new connection
void handleStartConfig();
/// begin a new connection
void handleStartConfig();
/**
* Handle a packet that the phone wants us to send. We can write to it but can not keep a reference to it
* @return true true if a packet was queued for sending
*/
bool handleToRadioPacket(meshtastic_MeshPacket &p);
/**
* Handle a packet that the phone wants us to send. We can write to it but can not keep a reference to it
* @return true true if a packet was queued for sending
*/
bool handleToRadioPacket(meshtastic_MeshPacket &p);
/// If the mesh service tells us fromNum has changed, tell the phone
virtual int onNotify(uint32_t newValue) override;
/// If the mesh service tells us fromNum has changed, tell the phone
virtual int onNotify(uint32_t newValue) override;
};

View File

@ -34,15 +34,12 @@
XModemAdapter xModem;
XModemAdapter::XModemAdapter()
{
}
XModemAdapter::XModemAdapter() {}
unsigned short XModemAdapter::crc16_ccitt(const pb_byte_t *buffer, int length)
{
unsigned short crc16 = 0;
while (length != 0)
{
while (length != 0) {
crc16 = (unsigned char)(crc16 >> 8) | (crc16 << 8);
crc16 ^= *buffer;
crc16 ^= (unsigned char)(crc16 & 0xff) >> 4;
@ -79,19 +76,15 @@ meshtastic_XModem XModemAdapter::getForPhone()
void XModemAdapter::handlePacket(meshtastic_XModem xmodemPacket)
{
switch (xmodemPacket.control)
{
switch (xmodemPacket.control) {
case meshtastic_XModem_Control_SOH:
case meshtastic_XModem_Control_STX:
if ((xmodemPacket.seq == 0) && !isReceiving && !isTransmitting)
{
if ((xmodemPacket.seq == 0) && !isReceiving && !isTransmitting) {
// NULL packet has the destination filename
memcpy(filename, &xmodemPacket.buffer.bytes, xmodemPacket.buffer.size);
if (xmodemPacket.control == meshtastic_XModem_Control_SOH)
{ // Receive this file and put to Flash
if (xmodemPacket.control == meshtastic_XModem_Control_SOH) { // Receive this file and put to Flash
file = FSCom.open(filename, FILE_O_WRITE);
if (file)
{
if (file) {
sendControl(meshtastic_XModem_Control_ACK);
isReceiving = true;
packetno = 1;
@ -100,13 +93,10 @@ void XModemAdapter::handlePacket(meshtastic_XModem xmodemPacket)
sendControl(meshtastic_XModem_Control_NAK);
isReceiving = false;
break;
}
else
{ // Transmit this file from Flash
} else { // Transmit this file from Flash
LOG_INFO("XModem: Transmitting file %s\n", filename);
file = FSCom.open(filename, FILE_O_READ);
if (file)
{
if (file) {
packetno = 1;
isTransmitting = true;
xmodemStore = meshtastic_XModem_init_zero;
@ -115,8 +105,7 @@ void XModemAdapter::handlePacket(meshtastic_XModem xmodemPacket)
xmodemStore.buffer.size = file.read(xmodemStore.buffer.bytes, sizeof(meshtastic_XModem_buffer_t::bytes));
xmodemStore.crc16 = crc16_ccitt(xmodemStore.buffer.bytes, xmodemStore.buffer.size);
LOG_DEBUG("XModem: STX Notify Sending packet %d, %d Bytes.\n", packetno, xmodemStore.buffer.size);
if (xmodemStore.buffer.size < sizeof(meshtastic_XModem_buffer_t::bytes))
{
if (xmodemStore.buffer.size < sizeof(meshtastic_XModem_buffer_t::bytes)) {
isEOT = true;
// send EOT on next Ack
}
@ -127,15 +116,11 @@ void XModemAdapter::handlePacket(meshtastic_XModem xmodemPacket)
isTransmitting = false;
break;
}
}
else
{
if (isReceiving)
{
} else {
if (isReceiving) {
// normal file data packet
if ((xmodemPacket.seq == packetno) &&
check(xmodemPacket.buffer.bytes, xmodemPacket.buffer.size, xmodemPacket.crc16))
{
check(xmodemPacket.buffer.bytes, xmodemPacket.buffer.size, xmodemPacket.crc16)) {
// valid packet
file.write(xmodemPacket.buffer.bytes, xmodemPacket.buffer.size);
sendControl(meshtastic_XModem_Control_ACK);
@ -145,9 +130,7 @@ void XModemAdapter::handlePacket(meshtastic_XModem xmodemPacket)
// invalid packet
sendControl(meshtastic_XModem_Control_NAK);
break;
}
else if (isTransmitting)
{
} else if (isTransmitting) {
// just received something weird.
sendControl(meshtastic_XModem_Control_CAN);
isTransmitting = false;
@ -170,10 +153,8 @@ void XModemAdapter::handlePacket(meshtastic_XModem xmodemPacket)
break;
case meshtastic_XModem_Control_ACK:
// Acknowledge Send the next packet
if (isTransmitting)
{
if (isEOT)
{
if (isTransmitting) {
if (isEOT) {
sendControl(meshtastic_XModem_Control_EOT);
file.close();
LOG_INFO("XModem: Finished sending file %s\n", filename);
@ -189,25 +170,20 @@ void XModemAdapter::handlePacket(meshtastic_XModem xmodemPacket)
xmodemStore.buffer.size = file.read(xmodemStore.buffer.bytes, sizeof(meshtastic_XModem_buffer_t::bytes));
xmodemStore.crc16 = crc16_ccitt(xmodemStore.buffer.bytes, xmodemStore.buffer.size);
LOG_DEBUG("XModem: ACK Notify Sending packet %d, %d Bytes.\n", packetno, xmodemStore.buffer.size);
if (xmodemStore.buffer.size < sizeof(meshtastic_XModem_buffer_t::bytes))
{
if (xmodemStore.buffer.size < sizeof(meshtastic_XModem_buffer_t::bytes)) {
isEOT = true;
// send EOT on next Ack
}
packetReady.notifyObservers(packetno);
}
else
{
} else {
// just received something weird.
sendControl(meshtastic_XModem_Control_CAN);
}
break;
case meshtastic_XModem_Control_NAK:
// Negative acknowledge. Send the same buffer again
if (isTransmitting)
{
if (--retrans <= 0)
{
if (isTransmitting) {
if (--retrans <= 0) {
sendControl(meshtastic_XModem_Control_CAN);
file.close();
LOG_INFO("XModem: Retransmit timeout, cancelling file %s\n", filename);
@ -221,15 +197,12 @@ void XModemAdapter::handlePacket(meshtastic_XModem xmodemPacket)
xmodemStore.buffer.size = file.read(xmodemStore.buffer.bytes, sizeof(meshtastic_XModem_buffer_t::bytes));
xmodemStore.crc16 = crc16_ccitt(xmodemStore.buffer.bytes, xmodemStore.buffer.size);
LOG_DEBUG("XModem: NAK Notify Sending packet %d, %d Bytes.\n", packetno, xmodemStore.buffer.size);
if (xmodemStore.buffer.size < sizeof(meshtastic_XModem_buffer_t::bytes))
{
if (xmodemStore.buffer.size < sizeof(meshtastic_XModem_buffer_t::bytes)) {
isEOT = true;
// send EOT on next Ack
}
packetReady.notifyObservers(packetno);
}
else
{
} else {
// just received something weird.
sendControl(meshtastic_XModem_Control_CAN);
}

View File

@ -40,37 +40,37 @@
class XModemAdapter
{
public:
// Called when we put a fragment in the outgoing memory
Observable<uint32_t> packetReady;
public:
// Called when we put a fragment in the outgoing memory
Observable<uint32_t> packetReady;
XModemAdapter();
XModemAdapter();
void handlePacket(meshtastic_XModem xmodemPacket);
meshtastic_XModem getForPhone();
void handlePacket(meshtastic_XModem xmodemPacket);
meshtastic_XModem getForPhone();
private:
bool isReceiving = false;
bool isTransmitting = false;
bool isEOT = false;
private:
bool isReceiving = false;
bool isTransmitting = false;
bool isEOT = false;
int retrans = MAXRETRANS;
int retrans = MAXRETRANS;
uint16_t packetno = 0;
uint16_t packetno = 0;
#if defined(ARCH_NRF52)
File file = File(FSCom);
File file = File(FSCom);
#else
File file;
File file;
#endif
char filename[sizeof(meshtastic_XModem_buffer_t::bytes)] = {0};
char filename[sizeof(meshtastic_XModem_buffer_t::bytes)] = {0};
protected:
meshtastic_XModem xmodemStore = meshtastic_XModem_init_zero;
unsigned short crc16_ccitt(const pb_byte_t *buffer, int length);
int check(const pb_byte_t *buf, int sz, unsigned short tcrc);
void sendControl(meshtastic_XModem_Control c);
protected:
meshtastic_XModem xmodemStore = meshtastic_XModem_init_zero;
unsigned short crc16_ccitt(const pb_byte_t *buffer, int length);
int check(const pb_byte_t *buf, int sz, unsigned short tcrc);
void sendControl(meshtastic_XModem_Control c);
};
extern XModemAdapter xModem;