// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2022 David Tiller, K4DET
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
/********************************************************************************
*
* File: eotd.c
*
* Purpose: Functions for processing received EOTD transmissions and
* converting to text format.
*
*******************************************************************************/
#include "direwolf.h"
#include
#include
#include
#include
#include
#include
#include
#include "textcolor.h"
#include "eotd_defs.h"
#include "eotd.h"
#undef EOTD_RAW
#define EOTD_TIMESTAMP
#define EOTD_APPEND_HEX
/*-------------------------------------------------------------------
*
* Convert EOTD binary block (from HDLC frame) to text.
*
* In: Pointer to EOTD binary block and number of bytes.
* Out: text.
*
*--------------------------------------------------------------------*/
void add_comma(char *text, int text_size) {
strlcat(text, ",", text_size);
}
int get_chain(uint64_t pkt, char *text, int text_size) {
uint32_t val;
val = pkt & 0x03ULL;
strlcat(text, "chain=", text_size);
switch(val) {
case 0:
strlcat(text, "MIDDLE", text_size);
break;
case 1:
strlcat(text, "LAST", text_size);
break;
case 2:
strlcat(text, "FIRST", text_size);
break;
case 3:
strlcat(text, "ONLY", text_size);
break;
}
return val;
}
void get_r2f_dev_batt_stat(uint64_t pkt, char *text, int text_size) {
uint32_t val;
pkt >>= 2;
val = pkt & 0x03ULL;
strlcat(text, "devbat=", text_size);
switch(val) {
case 3:
strlcat(text, "OK", text_size);
break;
case 2:
strlcat(text, "WEAK", text_size);
break;
case 1:
strlcat(text, "VERY_WEAK", text_size);
break;
case 0:
strlcat(text, "NOT_MONITORED", text_size);
break;
}
}
void get_r2f_msg_id_type(uint64_t pkt, char *text, int text_size) {
uint32_t val;
char temp[32];
uint64_t temp_pkt = pkt >> 4;
val = temp_pkt & 0x07ULL;
strlcat(text, "msgid=", text_size);
switch(val) {
case 0:
strlcat(text, "ONEWAY", text_size);
break;
case 7: // TEST button, maybe
// Test the CONFIRM bit
if ((pkt & 0x10000000000ULL) == 0) {
strlcat(text, "TEST/ARM_REQ", text_size);
} else {
strlcat(text, "ARM_CONFIRM", text_size);
}
break;
default:
sprintf(temp, "CUSTOM(%d)", val);
strlcat(text, temp, text_size);
break;
}
}
void get_r2f_unit_addr_code(uint64_t pkt, char *text, int text_size) {
uint32_t val;
char temp[32];
pkt >>= 7;
val = pkt & 0x1ffffULL;
strlcat(text, "unit_addr=", text_size);
sprintf(temp, "%d", val);
strlcat(text, temp, text_size);
}
void get_r2f_brake_pressure(uint64_t pkt, char *text, int text_size) {
uint32_t val;
char temp[32];
pkt >>= 24;
val = pkt & 0x7fULL;
strlcat(text, "brake_status=", text_size);
switch (val) {
case 127:
strlcat(text, "GO", text_size);
break;
case 126:
strlcat(text, "NO-GO", text_size);
break;
default:
if (val < 45) {
sprintf(temp, "NO-GO(%d psig)", val);
} else {
sprintf(temp, "GO(%d psig)", val);
}
strlcat(text, temp, text_size);
break;
}
}
void get_r2f_disc_bits(uint64_t pkt, char *text, int text_size) {
uint32_t val;
char temp[32];
pkt >>= 31;
val = pkt & 0xffULL;
strlcat(text, "disc_bits=", text_size);
sprintf(temp, "%02x", val);
strlcat(text, temp, text_size);
}
void get_r2f_valve_bit(uint64_t pkt, char *text, int text_size) {
uint32_t val;
pkt >>= 39;
val = pkt & 0x01;
strlcat(text, "valve=", text_size);
strlcat(text, val == 0 ? "FAILED" : "OPERATIONAL", text_size);
}
void get_r2f_confirm_bit(uint64_t pkt, char *text, int text_size) {
uint32_t val;
pkt >>= 40;
val = pkt & 0x01;
strlcat(text, "confirm=", text_size);
strlcat(text, val == 0 ? "UPDATE" : "RESPONSE", text_size);
}
void get_r2f_disc_bit1(uint64_t pkt, char *text, int text_size) {
uint32_t val;
char temp[32];
pkt >>= 41;
val = pkt & 0x01;
strlcat(text, "disc_bit_1=", text_size);
sprintf(temp, "%d", val);
strlcat(text, temp, text_size);
}
void get_r2f_motion_bit(uint64_t pkt, char *text, int text_size) {
uint32_t val;
pkt >>= 42;
val = pkt & 0x01;
strlcat(text, "motion=", text_size);
strlcat(text, val == 0 ? "STOPPED/NOT_MONITORED" : "IN_MOTION", text_size);
}
void get_r2f_mkr_light_batt_bit(uint64_t pkt, char *text, int text_size) {
uint32_t val;
pkt >>= 43;
val = pkt & 0x01;
strlcat(text, "light_batt=", text_size);
strlcat(text, val == 0 ? "OK/NOT_MONITORED" : "WEAK", text_size);
}
void get_r2f_mkr_light_bit(uint64_t pkt, char *text, int text_size) {
uint32_t val;
pkt >>= 44;
val = pkt & 0x01;
strlcat(text, "light=", text_size);
strlcat(text, val == 0 ? "OFF/NOT_MONITORED" : "ON", text_size);
}
void decode_basic_r2f(uint64_t pkt, char *text, int text_size) {
strlcat(text, "block=BASIC", text_size);
add_comma(text, text_size);
get_r2f_dev_batt_stat(pkt, text, text_size);
add_comma(text, text_size);
get_r2f_msg_id_type(pkt, text, text_size);
add_comma(text, text_size);
get_r2f_unit_addr_code(pkt, text, text_size);
add_comma(text, text_size);
get_r2f_brake_pressure(pkt, text, text_size);
add_comma(text, text_size);
get_r2f_disc_bits(pkt, text, text_size);
add_comma(text, text_size);
get_r2f_valve_bit(pkt, text, text_size);
add_comma(text, text_size);
get_r2f_confirm_bit(pkt, text, text_size);
add_comma(text, text_size);
get_r2f_disc_bit1(pkt, text, text_size);
add_comma(text, text_size);
get_r2f_motion_bit(pkt, text, text_size);
add_comma(text, text_size);
get_r2f_mkr_light_batt_bit(pkt, text, text_size);
add_comma(text, text_size);
get_r2f_mkr_light_bit(pkt, text, text_size);
}
void get_f2r_msg_id_type(uint64_t pkt, char *text, int text_size) {
uint32_t val;
pkt >>= 2;
val = pkt & 0x07ULL;
strlcat(text, "msgid=", text_size);
strlcat(text, val == 0 ? "VALID" : "INVALID", text_size);
}
void get_f2r_unit_addr_code(uint64_t pkt, char *text, int text_size) {
uint32_t val;
char temp[32];
pkt >>= 5;
val = pkt & 0x1ffffULL;
strlcat(text, "unit_addr=", text_size);
sprintf(temp, "%d", val);
strlcat(text, temp, text_size);
}
void get_f2r_command(uint64_t pkt, char *text, int text_size) {
uint32_t val;
char temp[32];
pkt >>= 22;
val = pkt & 0xff;
strlcat(text, "cmd=", text_size);
switch(val) {
case 0x55:
strlcat(text, "STATUS_REQ", text_size);
break;
case 0xaa:
strlcat(text, "APPLY_BRAKES", text_size);
break;
default:
sprintf(temp, "UNKNOWN(%d)", val);
strlcat(text, temp, text_size);
break;
}
}
int get_option_format(uint64_t pkt, char *text, int text_size) {
uint32_t val;
pkt >>= 2;
val = pkt & 0x01;
return val;
}
void decode_basic_f2r(uint64_t pkt, char *text, int text_size) {
strlcat(text, "block=BASIC", text_size);
add_comma(text, text_size);
get_f2r_msg_id_type(pkt, text, text_size);
add_comma(text, text_size);
get_f2r_unit_addr_code(pkt, text, text_size);
add_comma(text, text_size);
get_f2r_command(pkt, text, text_size);
}
void decode_r2f_option_block(uint64_t pkt, char *text, int text_size) {
int indicator = get_option_format(pkt, text, text_size);
if (indicator == 0) {
// BINARY
strlcat(text, "block=OPT_BINARY", text_size);
add_comma(text, text_size);
int type, val;
char temp[32];
pkt >>= 3; // Skip chain and format bits
for (int i = 0; i < 3; i++ ) {
type = pkt & 0x7f;
pkt >>= 7;
val = pkt & 0x7f;
pkt >>= 7;
// 'No Data' indicator
if (type == 0) {
continue;
}
sprintf(temp, "TYPE_%c=%d", 'A' + i, type);
strlcat(text, temp, text_size);
add_comma(text, text_size);
sprintf(temp, "VALUE_%c=%d", 'A' + i, val);
strlcat(text, temp, text_size);
if (i != 2) add_comma(text, text_size);
}
} else {
// ASCII
strlcat(text, "block=OPT_ASCII", text_size);
add_comma(text, text_size);
char msg[8] = { 0 };
pkt >>= 3; // Skip chain and format bits
for (int i = 0; i < 6; i++) {
msg[i] = pkt & 0x7f;
pkt >>= 7;
}
strlcat(text, "message=", text_size);
strlcat(text, msg, text_size);
}
}
void decode_f2r_option_block(uint64_t pkt, char *text, int text_size) {
strlcat(text, "TODO: F2R OPTION BLOCK", text_size);
}
void eotd_to_text (unsigned char *eotd, int eotd_len, char *text, int text_size)
{
assert (eotd_len == EOTD_LENGTH + 1);
uint64_t pkt = 0ULL;
for (int i = 0; i < EOTD_LENGTH; i++) {
pkt <<= 8;
pkt |= eotd[i];
}
*text = '\0';
char eotd_type = eotd[EOTD_LENGTH];
#ifndef EOTD_RAW
if (eotd_type == EOTD_TYPE_F2R) {
strlcat(text, "FRONT>REAR:", text_size);
} else {
strlcat(text, "REAR>FRONT:", text_size);
}
#ifdef EOTD_TIMESTAMP
struct timeval tv;
gettimeofday(&tv, NULL);
struct tm *now = localtime(&tv.tv_sec);
char date_buffer[32];
strlcat(text, "ts=", text_size);
sprintf(date_buffer, "%d-%02d-%02dT%02d:%02d:%02d.%03d,",
now->tm_year + 1900, now->tm_mon + 1, now->tm_mday,
now->tm_hour, now->tm_min, now->tm_sec, tv.tv_usec / 1000);
strlcat(text, date_buffer, text_size);
#endif
int chain = get_chain(pkt, text, text_size);
add_comma(text, text_size);
// check for 'first' block - it's always the basic block
if (chain & 0x02) {
if (eotd_type == EOTD_TYPE_R2F) {
decode_basic_r2f(pkt, text, text_size);
} else {
decode_basic_f2r(pkt, text, text_size);
}
} else {
if (eotd_type == EOTD_TYPE_R2F) {
decode_r2f_option_block(pkt, text, text_size);
} else {
decode_f2r_option_block(pkt, text, text_size);
}
}
#ifdef EOTD_APPEND_HEX
char hex[64];
add_comma(text, text_size);
snprintf(hex, sizeof(hex), "%llx", pkt);
strlcat(text, "hex=", text_size);
for (int i = 56; i >= 0; i -= 8) {
sprintf(hex, "%02x ", (unsigned char) (pkt >> i) & 0xff);
strlcat(text, hex, text_size);
}
text[strlen(text) - 1] = '\0'; // zap trailing space
#endif
#else
char temp[8];
for (int i = 0; i < 8; i++) {
sprintf(temp, " %02x", eotd[i]);
strlcat(text, temp, text_size);
}
#endif
}