From 6212fac6af4e0feb156b018327c623882acf23ac Mon Sep 17 00:00:00 2001 From: Serge Vakulenko Date: Wed, 29 Aug 2018 21:04:13 -0700 Subject: [PATCH] Parse Name and ID. Update timestamp. --- md380.c | 209 ++++++++++++++++++++++++++++++++++++++++++++++++++----- radio.c | 1 + radio.h | 1 + util.c | 52 ++++++++++++++ util.h | 16 +++++ uv380.c | 210 +++++++++++++++++++++++++++++++++++++++++++++++++++----- 6 files changed, 453 insertions(+), 36 deletions(-) diff --git a/md380.c b/md380.c index 45e2b6d..a572829 100644 --- a/md380.c +++ b/md380.c @@ -42,9 +42,8 @@ #define NMESSAGES 50 #define MEMSZ 0x40000 -#define OFFSET_VERSION 0x02001 -#define OFFSET_ID 0x02084 -#define OFFSET_NAME 0x020b0 +#define OFFSET_TIMESTMP 0x02001 +#define OFFSET_SETTINGS 0x02040 #define OFFSET_MSG 0x02180 #define OFFSET_CONTACTS 0x05f80 #define OFFSET_GLISTS 0x0ec20 @@ -227,6 +226,86 @@ typedef struct { uint16_t member[31]; // Channels } scanlist_t; +// +// General settings. +// TODO: verify the general settings with CPS +// +typedef struct { + + // Bytes 0-19 + uint16_t intro_screen_line1[10]; + + // Bytes 20-39 + uint16_t intro_screen_line2[10]; + + // Bytes 40-63 + uint8_t _unused40[24]; + + // Byte 64 + uint8_t _unused64_0 : 3, + monitor_type : 1, + _unused64_4 : 1, + disable_all_leds : 1, + _unused64_6 : 2; + + // Byte 65 + uint8_t talk_permit_tone : 2, + pw_and_lock_enable : 1, + ch_free_indication_tone : 1, + _unused65_4 : 1, + disable_all_tones : 1, + save_mode_receive : 1, + save_preamble : 1; + + // Byte 66 + uint8_t _unused66_0 : 2, + keypad_tones : 1, + intro_screen : 1, + _unused66_4 : 4; + + // Byte 67 + uint8_t _unused67; + + // Bytes 68-71 + uint8_t radio_id[3]; + uint8_t _unused71; + + // Bytes 72-84 + uint8_t tx_preamble_duration; + uint8_t group_call_hang_time; + uint8_t private_call_hang_time; + uint8_t vox_sensitivity; + uint8_t _unused76[2]; + uint8_t rx_low_battery_interval; + uint8_t call_alert_tone_duration; + uint8_t lone_worker_response_time; + uint8_t lone_worker_reminder_time; + uint8_t _unused82; + uint8_t scan_digital_hang_time; + uint8_t scan_analog_hang_time; + + // Byte 85 + uint8_t _unused85_0 : 6, + backlight_time : 2; + + // Bytes 86-87 + uint8_t set_keypad_lock_time; + uint8_t mode; + + // Bytes 88-95 + uint32_t power_on_password; + uint32_t radio_prog_password; + + // Bytes 96-103 + uint8_t pc_prog_password[8]; + + // Bytes 104-111 + uint8_t _unused104[8]; + + // Bytes 112-143 + uint16_t radio_name[16]; +} general_settings_t; + static const char *POWER_NAME[] = { "Low", "High" }; static const char *SQUELCH_NAME[] = { "Tight", "Normal" }; static const char *BANDWIDTH[] = { "12.5", "20", "25" }; @@ -502,25 +581,27 @@ static void print_chanlist(FILE *out, uint16_t *unsorted, int nchan) static void print_id(FILE *out) { - const unsigned char *data = &radio_mem[OFFSET_VERSION]; + general_settings_t *gs = (general_settings_t*) &radio_mem[OFFSET_SETTINGS]; + unsigned id = gs->radio_id[0] | (gs->radio_id[1] << 8) | (gs->radio_id[2] << 16); + unsigned char *timestamp = &radio_mem[OFFSET_TIMESTMP]; fprintf(out, "Name: "); - if (radio_mem[OFFSET_NAME] != 0 && *(uint16_t*)&radio_mem[OFFSET_NAME] != 0xffff) { - print_unicode(out, (uint16_t*) &radio_mem[OFFSET_NAME], 16, 0); + if (gs->radio_name[0] != 0 && gs->radio_name[0] != 0xffff) { + print_unicode(out, gs->radio_name, 16, 0); } else { fprintf(out, "-"); } - fprintf(out, "\nID: %u\n", *(uint32_t*) &radio_mem[OFFSET_ID] & 0xffffff); + fprintf(out, "\nID: %u\n", id); - if (*data != 0xff) { + if (*timestamp != 0xff) { fprintf(out, "Last Programmed Date: %d%d%d%d-%d%d-%d%d", - data[0] >> 4, data[0] & 15, data[1] >> 4, data[1] & 15, - data[2] >> 4, data[2] & 15, data[3] >> 4, data[3] & 15); + timestamp[0] >> 4, timestamp[0] & 15, timestamp[1] >> 4, timestamp[1] & 15, + timestamp[2] >> 4, timestamp[2] & 15, timestamp[3] >> 4, timestamp[3] & 15); fprintf(out, " %d%d:%d%d:%d%d\n", - data[4] >> 4, data[4] & 15, data[5] >> 4, data[5] & 15, - data[6] >> 4, data[6] & 15); + timestamp[4] >> 4, timestamp[4] & 15, timestamp[5] >> 4, timestamp[5] & 15, + timestamp[6] >> 4, timestamp[6] & 15); fprintf(out, "CPS Software Version: V%x%x.%x%x\n", - data[7], data[8], data[9], data[10]); + timestamp[7], timestamp[8], timestamp[9], timestamp[10]); } } @@ -1078,6 +1159,8 @@ static void md380_save_image(radio_device_t *radio, FILE *img) // static void md380_parse_parameter(radio_device_t *radio, char *param, char *value) { + general_settings_t *gs = (general_settings_t*) &radio_mem[OFFSET_SETTINGS]; + if (strcasecmp("Radio", param) == 0) { if (strcasecmp(radio->name, value) != 0) { fprintf(stderr, "Bad value for %s: %s\n", param, value); @@ -1085,6 +1168,29 @@ static void md380_parse_parameter(radio_device_t *radio, char *param, char *valu } return; } + + if (strcasecmp ("Name", param) == 0) { + // Decode utf8 text to ucs2. + utf8_decode(gs->radio_name, value, 16); + return; + } + + if (strcasecmp ("ID", param) == 0) { + uint32_t id = strtoul(value, 0, 0); + gs->radio_id[0] = id; + gs->radio_id[1] = id >> 8; + gs->radio_id[2] = id >> 16; + return; + } + + if (strcasecmp ("Last Programmed Date", param) == 0) { + // Ignore. + return; + } + if (strcasecmp ("CPS Software Version", param) == 0) { + // Ignore. + return; + } fprintf(stderr, "Unknown parameter: %s = %s\n", param, value); exit(-1); } @@ -1096,7 +1202,21 @@ static void md380_parse_parameter(radio_device_t *radio, char *param, char *valu // static int parse_digital_channel(int first_row, char *line) { - //TODO: parse digital channel + //TODO: parse digital channel Name + //TODO: parse digital channel Receive + //TODO: parse digital channel Transmit + //TODO: parse digital channel Power + //TODO: parse digital channel Scan + //TODO: parse digital channel AS + //TODO: parse digital channel Sq + //TODO: parse digital channel TOT + //TODO: parse digital channel RO + //TODO: parse digital channel Admit + //TODO: parse digital channel Color + //TODO: parse digital channel Slot + //TODO: parse digital channel InCall + //TODO: parse digital channel RxGL + //TODO: parse digital channel TxContact return 0; } @@ -1108,7 +1228,19 @@ static int parse_digital_channel(int first_row, char *line) static int parse_analog_channel(int first_row, char *line) { #if 1 - //TODO: parse analog channel + //TODO: parse analog channel Name + //TODO: parse analog channel Receive + //TODO: parse analog channel Transmit + //TODO: parse analog channel Power + //TODO: parse analog channel Scan + //TODO: parse analog channel AS + //TODO: parse analog channel Sq + //TODO: parse analog channel TOT + //TODO: parse analog channel RO + //TODO: parse analog channel Admit + //TODO: parse analog channel RxTone + //TODO: parse analog channel TxTone + //TODO: parse analog channel Width return 0; #else char num_str[256], name_str[256], rxfreq_str[256], offset_str[256]; @@ -1272,7 +1404,11 @@ static int parse_zones(int first_row, char *line) // static int parse_scanlist(int first_row, char *line) { - //TODO: parse scanlist + //TODO: parse scanlist Name + //TODO: parse scanlist PCh1 + //TODO: parse scanlist PCh2 + //TODO: parse scanlist TxCh + //TODO: parse scanlist Channels return 0; } @@ -1282,7 +1418,10 @@ static int parse_scanlist(int first_row, char *line) // static int parse_contact(int first_row, char *line) { - //TODO: parse contact + //TODO: parse contact Name + //TODO: parse contact Type + //TODO: parse contact ID + //TODO: parse contact RxTone return 0; } @@ -1292,7 +1431,7 @@ static int parse_contact(int first_row, char *line) // static int parse_grouplist(int first_row, char *line) { - //TODO: parse grouplist + //TODO: parse grouplist Contacts return 0; } @@ -1334,6 +1473,39 @@ static int md380_parse_row(radio_device_t *radio, int table_id, int first_row, c return 0; } +// +// Update timestamp. +// +static void md380_update_timestamp(radio_device_t *radio) +{ + unsigned char *timestamp = &radio_mem[OFFSET_TIMESTMP]; + char p[16]; + + // Last Programmed Date + get_timestamp(p); + timestamp[0] = ((p[0] & 0xf) << 4) | (p[1] & 0xf); // year upper + timestamp[1] = ((p[2] & 0xf) << 4) | (p[3] & 0xf); // year lower + timestamp[2] = ((p[4] & 0xf) << 4) | (p[5] & 0xf); // month + timestamp[3] = ((p[6] & 0xf) << 4) | (p[7] & 0xf); // day + timestamp[4] = ((p[8] & 0xf) << 4) | (p[9] & 0xf); // hour + timestamp[5] = ((p[10] & 0xf) << 4) | (p[11] & 0xf); // minute + timestamp[6] = ((p[12] & 0xf) << 4) | (p[13] & 0xf); // second + + // CPS Software Version: Vdx.xx + const char *dot = strchr(VERSION, '.'); + if (dot) { + timestamp[7] = 0x0d; // D means Dmrconfig + timestamp[8] = dot[-1] & 0x0f; + if (dot[2] == '.') { + timestamp[9] = 0; + timestamp[10] = dot[1] & 0x0f; + } else { + timestamp[9] = dot[1] & 0x0f; + timestamp[10] = dot[2] & 0x0f; + } + } +} + // // TYT MD-380 // @@ -1349,4 +1521,5 @@ radio_device_t radio_md380 = { md380_parse_parameter, md380_parse_header, md380_parse_row, + md380_update_timestamp, }; diff --git a/radio.c b/radio.c index 0c15c95..936efa2 100644 --- a/radio.c +++ b/radio.c @@ -257,6 +257,7 @@ badline: fprintf(stderr, "Invalid line: '%s'\n", line); } } fclose(conf); + device->update_timestamp(device); } // diff --git a/radio.h b/radio.h index 2e771f3..6f80843 100644 --- a/radio.h +++ b/radio.h @@ -88,6 +88,7 @@ struct _radio_device_t { void (*parse_parameter)(radio_device_t *radio, char *param, char *value); int (*parse_header)(radio_device_t *radio, char *line); int (*parse_row)(radio_device_t *radio, int table_id, int first_row, char *line); + void (*update_timestamp)(radio_device_t *radio); }; extern radio_device_t radio_md380; // TYT MD-380 diff --git a/util.c b/util.c index c7968a7..7bf3ad6 100644 --- a/util.c +++ b/util.c @@ -30,6 +30,7 @@ #include #include #include +#include #ifdef MINGW32 # include #else @@ -231,3 +232,54 @@ void print_unicode(FILE *out, const unsigned short *text, unsigned nchars, int f } } } + +// +// Get local time in format: YYYYMMDDhhmmss +// +void get_timestamp(char p[16]) +{ + time_t now = time(NULL); + struct tm *local = localtime(&now); + + if (! local) { + perror("localtime"); + exit(-1); + } + if (!strftime(p, 16, "%Y%m%d%H%M%S", local)) { + perror("strftime"); + exit(-1); + } +} + +// +// Fetch Unicode symbol from UTF-8 string. +// Advance string pointer. +// +int utf8_to_unicode(const char **p) +{ + int c1, c2, c3; + + c1 = (unsigned char) *(*p)++; + if (! (c1 & 0x80)) + return c1; + c2 = (unsigned char) *(*p)++; + if (! (c1 & 0x20)) + return (c1 & 0x1f) << 6 | (c2 & 0x3f); + c3 = (unsigned char) *(*p)++; + return (c1 & 0x0f) << 12 | (c2 & 0x3f) << 6 | (c3 & 0x3f); +} + +// +// Decode UTF-8 string into UCS-2 string, at most nsym characters. +// +void utf8_decode(unsigned short *dst, const char *src, unsigned nsym) +{ + for (; nsym > 0; nsym--) { + if ((*dst++ = utf8_to_unicode(&src)) == 0) { + // Clear the remaining bytes. + while (--nsym > 0) + *dst++ = 0; + break; + } + } +} diff --git a/util.h b/util.h index 003e1f5..e14185e 100644 --- a/util.h +++ b/util.h @@ -129,3 +129,19 @@ void putc_utf8(unsigned short ch, FILE *out); // Print utf16 text as utf8. // void print_unicode(FILE *out, const unsigned short *text, unsigned nchars, int fill_flag); + +// +// Fetch Unicode symbol from UTF-8 string. +// Advance string pointer. +// +int utf8_to_unicode(const char **p); + +// +// Decode UTF-8 string into UCS-2 string, at most nsym characters. +// +void utf8_decode(unsigned short *dst, const char *src, unsigned nsym); + +// +// Get local time in format: YYYYMMDDhhmmss +// +void get_timestamp(char p[16]); diff --git a/uv380.c b/uv380.c index 971e989..bed9ee1 100644 --- a/uv380.c +++ b/uv380.c @@ -42,9 +42,8 @@ #define NMESSAGES 50 #define MEMSZ 0xd0000 -#define OFFSET_VERSION 0x02001 -#define OFFSET_ID 0x02084 -#define OFFSET_NAME 0x020b0 +#define OFFSET_TIMESTMP 0x02001 +#define OFFSET_SETTINGS 0x02040 #define OFFSET_MSG 0x02180 #define OFFSET_GLISTS 0x0ec20 #define OFFSET_ZONES 0x149e0 @@ -245,6 +244,86 @@ typedef struct { uint16_t member[31]; // Channels } scanlist_t; +// +// General settings. +// TODO: verify the general settings with CPS +// +typedef struct { + + // Bytes 0-19 + uint16_t intro_screen_line1[10]; + + // Bytes 20-39 + uint16_t intro_screen_line2[10]; + + // Bytes 40-63 + uint8_t _unused40[24]; + + // Byte 64 + uint8_t _unused64_0 : 3, + monitor_type : 1, + _unused64_4 : 1, + disable_all_leds : 1, + _unused64_6 : 2; + + // Byte 65 + uint8_t talk_permit_tone : 2, + pw_and_lock_enable : 1, + ch_free_indication_tone : 1, + _unused65_4 : 1, + disable_all_tones : 1, + save_mode_receive : 1, + save_preamble : 1; + + // Byte 66 + uint8_t _unused66_0 : 2, + keypad_tones : 1, + intro_screen : 1, + _unused66_4 : 4; + + // Byte 67 + uint8_t _unused67; + + // Bytes 68-71 + uint8_t radio_id[3]; + uint8_t _unused71; + + // Bytes 72-84 + uint8_t tx_preamble_duration; + uint8_t group_call_hang_time; + uint8_t private_call_hang_time; + uint8_t vox_sensitivity; + uint8_t _unused76[2]; + uint8_t rx_low_battery_interval; + uint8_t call_alert_tone_duration; + uint8_t lone_worker_response_time; + uint8_t lone_worker_reminder_time; + uint8_t _unused82; + uint8_t scan_digital_hang_time; + uint8_t scan_analog_hang_time; + + // Byte 85 + uint8_t _unused85_0 : 6, + backlight_time : 2; + + // Bytes 86-87 + uint8_t set_keypad_lock_time; + uint8_t mode; + + // Bytes 88-95 + uint32_t power_on_password; + uint32_t radio_prog_password; + + // Bytes 96-103 + uint8_t pc_prog_password[8]; + + // Bytes 104-111 + uint8_t _unused104[8]; + + // Bytes 112-143 + uint16_t radio_name[16]; +} general_settings_t; + static const char *POWER_NAME[] = { "Low", "???", "Mid", "High" }; static const char *BANDWIDTH[] = { "12.5", "20", "25" }; static const char *CONTACT_TYPE[] = { "-", "Group", "Private", "All" }; @@ -518,25 +597,27 @@ static void print_chanlist(FILE *out, uint16_t *unsorted, int nchan) static void print_id(FILE *out) { - const unsigned char *data = &radio_mem[OFFSET_VERSION]; + general_settings_t *gs = (general_settings_t*) &radio_mem[OFFSET_SETTINGS]; + unsigned id = gs->radio_id[0] | (gs->radio_id[1] << 8) | (gs->radio_id[2] << 16); + unsigned char *timestamp = &radio_mem[OFFSET_TIMESTMP]; fprintf(out, "Name: "); - if (radio_mem[OFFSET_NAME] != 0 && *(uint16_t*)&radio_mem[OFFSET_NAME] != 0xffff) { - print_unicode(out, (uint16_t*) &radio_mem[OFFSET_NAME], 16, 0); + if (gs->radio_name[0] != 0 && gs->radio_name[0] != 0xffff) { + print_unicode(out, gs->radio_name, 16, 0); } else { fprintf(out, "-"); } - fprintf(out, "\nID: %u\n", *(uint32_t*) &radio_mem[OFFSET_ID] & 0xffffff); + fprintf(out, "\nID: %u\n", id); - if (*data != 0xff) { + if (*timestamp != 0xff) { fprintf(out, "Last Programmed Date: %d%d%d%d-%d%d-%d%d", - data[0] >> 4, data[0] & 15, data[1] >> 4, data[1] & 15, - data[2] >> 4, data[2] & 15, data[3] >> 4, data[3] & 15); + timestamp[0] >> 4, timestamp[0] & 15, timestamp[1] >> 4, timestamp[1] & 15, + timestamp[2] >> 4, timestamp[2] & 15, timestamp[3] >> 4, timestamp[3] & 15); fprintf(out, " %d%d:%d%d:%d%d\n", - data[4] >> 4, data[4] & 15, data[5] >> 4, data[5] & 15, - data[6] >> 4, data[6] & 15); + timestamp[4] >> 4, timestamp[4] & 15, timestamp[5] >> 4, timestamp[5] & 15, + timestamp[6] >> 4, timestamp[6] & 15); fprintf(out, "CPS Software Version: V%x%x.%x%x\n", - data[7], data[8], data[9], data[10]); + timestamp[7], timestamp[8], timestamp[9], timestamp[10]); } } @@ -1119,6 +1200,8 @@ static void uv380_save_image(radio_device_t *radio, FILE *img) // static void uv380_parse_parameter(radio_device_t *radio, char *param, char *value) { + general_settings_t *gs = (general_settings_t*) &radio_mem[OFFSET_SETTINGS]; + if (strcasecmp("Radio", param) == 0) { // Accept either MD-2017 or MD-UV380. if (strcasecmp("TYT MD-2017", value) != 0 && @@ -1128,6 +1211,29 @@ static void uv380_parse_parameter(radio_device_t *radio, char *param, char *valu } return; } + + if (strcasecmp ("Name", param) == 0) { + // Decode utf8 text to ucs2. + utf8_decode(gs->radio_name, value, 16); + return; + } + + if (strcasecmp ("ID", param) == 0) { + uint32_t id = strtoul(value, 0, 0); + gs->radio_id[0] = id; + gs->radio_id[1] = id >> 8; + gs->radio_id[2] = id >> 16; + return; + } + + if (strcasecmp ("Last Programmed Date", param) == 0) { + // Ignore. + return; + } + if (strcasecmp ("CPS Software Version", param) == 0) { + // Ignore. + return; + } fprintf(stderr, "Unknown parameter: %s = %s\n", param, value); exit(-1); } @@ -1139,7 +1245,21 @@ static void uv380_parse_parameter(radio_device_t *radio, char *param, char *valu // static int parse_digital_channel(int first_row, char *line) { - //TODO: parse digital channel + //TODO: parse digital channel Name + //TODO: parse digital channel Receive + //TODO: parse digital channel Transmit + //TODO: parse digital channel Power + //TODO: parse digital channel Scan + //TODO: parse digital channel AS + //TODO: parse digital channel Sq + //TODO: parse digital channel TOT + //TODO: parse digital channel RO + //TODO: parse digital channel Admit + //TODO: parse digital channel Color + //TODO: parse digital channel Slot + //TODO: parse digital channel InCall + //TODO: parse digital channel RxGL + //TODO: parse digital channel TxContact return 0; } @@ -1151,7 +1271,19 @@ static int parse_digital_channel(int first_row, char *line) static int parse_analog_channel(int first_row, char *line) { #if 1 - //TODO: parse analog channel + //TODO: parse analog channel Name + //TODO: parse analog channel Receive + //TODO: parse analog channel Transmit + //TODO: parse analog channel Power + //TODO: parse analog channel Scan + //TODO: parse analog channel AS + //TODO: parse analog channel Sq + //TODO: parse analog channel TOT + //TODO: parse analog channel RO + //TODO: parse analog channel Admit + //TODO: parse analog channel RxTone + //TODO: parse analog channel TxTone + //TODO: parse analog channel Width return 0; #else char num_str[256], name_str[256], rxfreq_str[256], offset_str[256]; @@ -1315,7 +1447,11 @@ static int parse_zones(int first_row, char *line) // static int parse_scanlist(int first_row, char *line) { - //TODO: parse scanlist + //TODO: parse scanlist Name + //TODO: parse scanlist PCh1 + //TODO: parse scanlist PCh2 + //TODO: parse scanlist TxCh + //TODO: parse scanlist Channels return 0; } @@ -1325,7 +1461,10 @@ static int parse_scanlist(int first_row, char *line) // static int parse_contact(int first_row, char *line) { - //TODO: parse contact + //TODO: parse contact Name + //TODO: parse contact Type + //TODO: parse contact ID + //TODO: parse contact RxTone return 0; } @@ -1335,7 +1474,7 @@ static int parse_contact(int first_row, char *line) // static int parse_grouplist(int first_row, char *line) { - //TODO: parse grouplist + //TODO: parse grouplist Contacts return 0; } @@ -1377,6 +1516,39 @@ static int uv380_parse_row(radio_device_t *radio, int table_id, int first_row, c return 0; } +// +// Update timestamp. +// +static void uv380_update_timestamp(radio_device_t *radio) +{ + unsigned char *timestamp = &radio_mem[OFFSET_TIMESTMP]; + char p[16]; + + // Last Programmed Date + get_timestamp(p); + timestamp[0] = ((p[0] & 0xf) << 4) | (p[1] & 0xf); // year upper + timestamp[1] = ((p[2] & 0xf) << 4) | (p[3] & 0xf); // year lower + timestamp[2] = ((p[4] & 0xf) << 4) | (p[5] & 0xf); // month + timestamp[3] = ((p[6] & 0xf) << 4) | (p[7] & 0xf); // day + timestamp[4] = ((p[8] & 0xf) << 4) | (p[9] & 0xf); // hour + timestamp[5] = ((p[10] & 0xf) << 4) | (p[11] & 0xf); // minute + timestamp[6] = ((p[12] & 0xf) << 4) | (p[13] & 0xf); // second + + // CPS Software Version: Vdx.xx + const char *dot = strchr(VERSION, '.'); + if (dot) { + timestamp[7] = 0x0d; // D means Dmrconfig + timestamp[8] = dot[-1] & 0x0f; + if (dot[2] == '.') { + timestamp[9] = 0; + timestamp[10] = dot[1] & 0x0f; + } else { + timestamp[9] = dot[1] & 0x0f; + timestamp[10] = dot[2] & 0x0f; + } + } +} + // // TYT MD-UV380 // @@ -1392,6 +1564,7 @@ radio_device_t radio_uv380 = { uv380_parse_parameter, uv380_parse_header, uv380_parse_row, + uv380_update_timestamp, }; // @@ -1409,4 +1582,5 @@ radio_device_t radio_md2017 = { uv380_parse_parameter, uv380_parse_header, uv380_parse_row, + uv380_update_timestamp, };