Coverage Report

Created: 2026-06-30 07:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/epan/dissectors/packet-lithionics.c
Line
Count
Source
1
/* packet-lithionics.c
2
 * Routines for Lithionics NeverDie Battery Management System (BMS)
3
 * By Michael Mann <Michael.Mann@jbtc.com>
4
 * Copyright 2018 Michael Mann
5
 *
6
 * Wireshark - Network traffic analyzer
7
 * By Gerald Combs <gerald@wireshark.org>
8
 * Copyright 1998 Gerald Combs
9
 *
10
 * SPDX-License-Identifier: GPL-2.0-or-later
11
 *
12
 * From https://lithionicsbattery.com/wp-content/uploads/2018/06/NeverDie-BMS-Advanced-RS232-UART-serial-protocol-Rev7.15.pdf
13
 */
14
15
#include "config.h"
16
17
#include <epan/packet.h>
18
#include <epan/tfs.h>
19
#include <epan/unit_strings.h>
20
#include <wsutil/strtoi.h>
21
22
void proto_register_lithionics(void);
23
void proto_reg_handoff_lithionics(void);
24
25
static dissector_handle_t lithionics_handle;
26
27
static int proto_lithionics;
28
29
static int hf_lithionics_battery_address;
30
static int hf_lithionics_amp_hours_remain;
31
static int hf_lithionics_volts;
32
static int hf_lithionics_bat_gauge;
33
static int hf_lithionics_soc;
34
static int hf_lithionics_direction;
35
static int hf_lithionics_amps;
36
static int hf_lithionics_watts;
37
static int hf_lithionics_temperature;
38
static int hf_lithionics_system_status;
39
static int hf_lithionics_system_status_high_voltage_state;
40
static int hf_lithionics_system_status_charge_source_detected;
41
static int hf_lithionics_system_status_neverdie_reserve_state;
42
static int hf_lithionics_system_status_optoloop_cell_open;
43
static int hf_lithionics_system_status_reserve_voltage_range;
44
static int hf_lithionics_system_status_low_voltage_state;
45
static int hf_lithionics_system_status_battery_protection_state;
46
static int hf_lithionics_system_status_power_off_state;
47
static int hf_lithionics_system_status_aux_contacts_state;
48
static int hf_lithionics_system_status_aux_contacts_error;
49
static int hf_lithionics_system_status_precharge_error;
50
static int hf_lithionics_system_status_contactor_flutter;
51
static int hf_lithionics_system_status_ac_power_present;
52
static int hf_lithionics_system_status_tsm_charger_present;
53
static int hf_lithionics_system_status_tsm_charger_error;
54
static int hf_lithionics_system_status_external_temp_sensor_error;
55
static int hf_lithionics_system_status_agsr_state;
56
static int hf_lithionics_system_status_high_temperature_state;
57
static int hf_lithionics_system_status_low_temperature_state;
58
static int hf_lithionics_system_status_aux_input1_state;
59
static int hf_lithionics_system_status_charge_disable_state;
60
static int hf_lithionics_system_status_overcurrent_state;
61
static int hf_lithionics_system_status_reserved;
62
static int hf_lithionics_temination;
63
64
static int ett_lithionics;
65
static int ett_lithionics_system_status;
66
67
static int* const system_status_flags[] = {
68
  &hf_lithionics_system_status_high_voltage_state,
69
  &hf_lithionics_system_status_charge_source_detected,
70
  &hf_lithionics_system_status_neverdie_reserve_state,
71
  &hf_lithionics_system_status_optoloop_cell_open,
72
  &hf_lithionics_system_status_reserve_voltage_range,
73
  &hf_lithionics_system_status_low_voltage_state,
74
  &hf_lithionics_system_status_battery_protection_state,
75
  &hf_lithionics_system_status_power_off_state,
76
  &hf_lithionics_system_status_aux_contacts_state,
77
  &hf_lithionics_system_status_aux_contacts_error,
78
  &hf_lithionics_system_status_precharge_error,
79
  &hf_lithionics_system_status_contactor_flutter,
80
  &hf_lithionics_system_status_ac_power_present,
81
  &hf_lithionics_system_status_tsm_charger_present,
82
  &hf_lithionics_system_status_tsm_charger_error,
83
  &hf_lithionics_system_status_external_temp_sensor_error,
84
  &hf_lithionics_system_status_agsr_state,
85
  &hf_lithionics_system_status_high_temperature_state,
86
  &hf_lithionics_system_status_low_temperature_state,
87
  &hf_lithionics_system_status_aux_input1_state,
88
  &hf_lithionics_system_status_charge_disable_state,
89
  &hf_lithionics_system_status_overcurrent_state,
90
  &hf_lithionics_system_status_reserved,
91
  NULL
92
};
93
94
static const value_string lithionics_direction_vals[] = {
95
  { 0,   "Discharging" },
96
  { 1,   "Charging" },
97
  { 0, NULL }
98
};
99
100
static const true_false_string tfs_lithionics_high_voltage_state = { "Above HVC", "Below HVC" };
101
static const true_false_string tfs_lithionics_reserve_voltage_range = { "Below RVC", "Above RVC" };
102
static const true_false_string tfs_lithionics_low_voltage_state = { "Below LVC", "Above LVC" };
103
static const true_false_string tfs_lithionics_battery_protection_state = { "Recovering from abnormal event", "Normal" };
104
static const true_false_string tfs_lithionics_power_off_state = { "Command", "Button" };
105
static const true_false_string tfs_lithionics_high_temperature_state = { "Exceeds allowed threshold", "Normal" };
106
static const true_false_string tfs_lithionics_low_temperature_state = { "Below allowed threshold", "Normal" };
107
108
static int
109
dissect_lithionics(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
110
0
{
111
0
  proto_tree      *lithionics_tree, *status_tree;
112
0
  proto_item      *ti;
113
0
  int       offset = 0;
114
0
  char*     str;
115
0
  float     f;
116
0
  uint32_t      value;
117
118
0
  col_set_str(pinfo->cinfo, COL_PROTOCOL, "Lithionics");
119
0
  col_clear(pinfo->cinfo, COL_INFO);
120
121
0
  ti = proto_tree_add_item(tree, proto_lithionics, tvb, 0, -1, ENC_NA);
122
0
  lithionics_tree = proto_item_add_subtree(ti, ett_lithionics);
123
124
  //just put the whole packet string (minus newlines) in the Info column
125
0
  col_set_str(pinfo->cinfo, COL_INFO, (const char*)tvb_get_string_enc(pinfo->pool, tvb, offset, tvb_reported_length_remaining(tvb, offset)-2, ENC_ASCII));
126
127
0
  str = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset + 1, 1, ENC_ASCII);
128
0
  if (!ws_strtou32(str, NULL, &value))
129
0
    proto_tree_add_uint_format_value(lithionics_tree, hf_lithionics_battery_address, tvb, offset, 2, 0, "<Invalid value \"%s\">", str);
130
0
  else
131
0
    proto_tree_add_uint(lithionics_tree, hf_lithionics_battery_address, tvb, offset, 2, value);
132
0
  offset += 2;
133
134
0
  str = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset + 1, 5, ENC_ASCII);
135
0
  if (!ws_strtou32(str, NULL, &value))
136
0
    proto_tree_add_float_format_value(lithionics_tree, hf_lithionics_amp_hours_remain, tvb, offset, 6, 0.0, "<Invalid value \"%s\">", str);
137
0
  else {
138
0
    f = (float)(value*.1);
139
0
    proto_tree_add_float_format_value(lithionics_tree, hf_lithionics_amp_hours_remain, tvb, offset, 6, f, "%0.1fAh", f);
140
0
  }
141
0
  offset += 6;
142
143
0
  str = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset + 1, 4, ENC_ASCII);
144
0
  if (!ws_strtou32(str, NULL, &value))
145
0
    proto_tree_add_float_format_value(lithionics_tree, hf_lithionics_volts, tvb, offset, 5, 0.0, "<Invalid value \"%s\">", str);
146
0
  else {
147
0
    f = (float)(value*.1);
148
0
    proto_tree_add_float_format_value(lithionics_tree, hf_lithionics_volts, tvb, offset, 5, f, "%0.1fV", f);
149
0
  }
150
0
  offset += 5;
151
152
0
  str = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset + 1, 3, ENC_ASCII);
153
0
  if (!ws_strtou32(str, NULL, &value))
154
0
    proto_tree_add_uint_format_value(lithionics_tree, hf_lithionics_bat_gauge, tvb, offset, 4, 0, "<Invalid value \"%s\">", str);
155
0
  else
156
0
    proto_tree_add_uint(lithionics_tree, hf_lithionics_bat_gauge, tvb, offset, 4, value);
157
0
  offset += 4;
158
159
0
  str = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset + 1, 3, ENC_ASCII);
160
0
  if (!ws_strtou32(str, NULL, &value))
161
0
    proto_tree_add_uint_format_value(lithionics_tree, hf_lithionics_soc, tvb, offset, 4, 0, "<Invalid value \"%s\">", str);
162
0
  else
163
0
    proto_tree_add_uint(lithionics_tree, hf_lithionics_soc, tvb, offset, 4, value);
164
0
  offset += 4;
165
166
0
  str = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset + 1, 1, ENC_ASCII);
167
0
  if (!ws_strtou32(str, NULL, &value))
168
0
    proto_tree_add_uint_format_value(lithionics_tree, hf_lithionics_direction, tvb, offset, 2, 0, "<Invalid value \"%s\">", str);
169
0
  else
170
0
    proto_tree_add_uint(lithionics_tree, hf_lithionics_direction, tvb, offset, 2, value);
171
0
  offset += 2;
172
173
0
  str = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset + 1, 5, ENC_ASCII);
174
0
  if (!ws_strtou32(str, NULL, &value))
175
0
    proto_tree_add_float_format_value(lithionics_tree, hf_lithionics_amps, tvb, offset, 6, 0.0, "<Invalid value \"%s\">", str);
176
0
  else {
177
0
    f = (float)(value*.1);
178
0
    proto_tree_add_float_format_value(lithionics_tree, hf_lithionics_amps, tvb, offset, 6, f, "%0.1fAmp", f);
179
0
  }
180
0
  offset += 6;
181
182
0
  str = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset + 1, 6, ENC_ASCII);
183
0
  if (!ws_strtou32(str, NULL, &value))
184
0
    proto_tree_add_uint_format_value(lithionics_tree, hf_lithionics_watts, tvb, offset, 7, 0, "<Invalid value \"%s\">", str);
185
0
  else
186
0
    proto_tree_add_uint(lithionics_tree, hf_lithionics_watts, tvb, offset, 7, value);
187
0
  offset += 7;
188
189
0
  str = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset + 1, 3, ENC_ASCII);
190
0
  if (!ws_strtou32(str, NULL, &value))
191
0
    proto_tree_add_uint_format_value(lithionics_tree, hf_lithionics_temperature, tvb, offset, 4, 0, "<Invalid value \"%s\">", str);
192
0
  else
193
0
    proto_tree_add_uint(lithionics_tree, hf_lithionics_temperature, tvb, offset, 4, value);
194
0
  offset += 4;
195
196
0
  str = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset + 1, 6, ENC_ASCII);
197
  //do this over proto_tree_add_bitmask_value to get better field highlighting
198
0
  if (!ws_hexstrtou32(str, NULL, &value))
199
0
    proto_tree_add_uint_format_value(lithionics_tree, hf_lithionics_system_status, tvb, offset, 7, 0, "<Invalid value \"%s\">", str);
200
0
  else {
201
0
    ti = proto_tree_add_uint(lithionics_tree, hf_lithionics_system_status, tvb, offset, 7, value);
202
0
    status_tree = proto_item_add_subtree(ti, ett_lithionics_system_status);
203
0
    proto_tree_add_bitmask_list_value(status_tree, tvb, offset, 7, system_status_flags, value);
204
0
  }
205
0
  offset += 7;
206
207
0
  proto_tree_add_item(lithionics_tree, hf_lithionics_temination, tvb, offset, 2, ENC_NA);
208
0
  offset += 2;
209
210
0
  return offset;
211
0
}
212
213
void
214
proto_register_lithionics(void)
215
14
{
216
217
14
  static hf_register_info hf[] = {
218
14
    { &hf_lithionics_battery_address,
219
14
      { "Battery address", "lithionics_bms.battery_address", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } },
220
14
    { &hf_lithionics_amp_hours_remain,
221
14
      { "Amp Hours Remaining", "lithionics_bms.amp_hours_remain", FT_FLOAT, BASE_NONE, NULL, 0x0, NULL, HFILL } },
222
14
    { &hf_lithionics_volts,
223
14
      { "Volts", "lithionics_bms.volts", FT_FLOAT, BASE_NONE, NULL, 0x0, NULL, HFILL } },
224
14
    { &hf_lithionics_bat_gauge,
225
14
      { "Bat gauge", "lithionics_bms.bat_gauge", FT_UINT32, BASE_DEC|BASE_UNIT_STRING, UNS(&units_percent), 0x0, NULL, HFILL } },
226
14
    { &hf_lithionics_soc,
227
14
      { "SoC", "lithionics_bms.soc", FT_UINT32, BASE_DEC|BASE_UNIT_STRING, UNS(&units_percent), 0x0, NULL, HFILL } },
228
14
    { &hf_lithionics_direction,
229
14
      { "Direction", "lithionics_bms.direction", FT_UINT16, BASE_DEC, VALS(lithionics_direction_vals), 0x0, NULL, HFILL } },
230
14
    { &hf_lithionics_amps,
231
14
      { "Amps", "lithionics_bms.amps", FT_FLOAT, BASE_NONE, NULL, 0x0, NULL, HFILL } },
232
14
    { &hf_lithionics_watts,
233
14
      { "Watts", "lithionics_bms.watts", FT_UINT32, BASE_DEC|BASE_UNIT_STRING, UNS(&units_watt), 0x0, NULL, HFILL } },
234
14
    { &hf_lithionics_temperature,
235
14
      { "Temperature", "lithionics_bms.temperature", FT_UINT32, BASE_DEC|BASE_UNIT_STRING, UNS(&units_degree_degrees), 0x0, NULL, HFILL } },
236
14
    { &hf_lithionics_temination,
237
14
      { "Newline Termination", "lithionics_bms.termination", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } },
238
14
    { &hf_lithionics_system_status,
239
14
      { "System Status", "lithionics_bms.system_status", FT_UINT24, BASE_HEX, NULL, 0x0, NULL, HFILL } },
240
14
    { &hf_lithionics_system_status_high_voltage_state,
241
14
      { "High Voltage State", "lithionics_bms.system_status.high_voltage_state", FT_BOOLEAN, 24, TFS(&tfs_lithionics_high_voltage_state), 0x000001, NULL, HFILL } },
242
14
    { &hf_lithionics_system_status_charge_source_detected,
243
14
      { "Charge Source Detected", "lithionics_bms.system_status.charge_source_detected", FT_BOOLEAN, 24, NULL, 0x000002, NULL, HFILL } },
244
14
    { &hf_lithionics_system_status_neverdie_reserve_state,
245
14
      { "NeverDie Reserve State", "lithionics_bms.system_status.neverdie_reserve_state", FT_BOOLEAN, 24, NULL, 0x000004, NULL, HFILL } },
246
14
    { &hf_lithionics_system_status_optoloop_cell_open,
247
14
      { "OptoLoop Cell Loop is open", "lithionics_bms.system_status.optoloop_cell_open", FT_BOOLEAN, 24, NULL, 0x000008, NULL, HFILL } },
248
14
    { &hf_lithionics_system_status_reserve_voltage_range,
249
14
      { "Reserve Voltage Range", "lithionics_bms.system_status.reserve_voltage_range", FT_BOOLEAN, 24, TFS(&tfs_lithionics_reserve_voltage_range), 0x000010, NULL, HFILL } },
250
14
    { &hf_lithionics_system_status_low_voltage_state,
251
14
      { "Low Voltage State", "lithionics_bms.system_status.low_voltage_state", FT_BOOLEAN, 24, TFS(&tfs_lithionics_low_voltage_state), 0x000020, NULL, HFILL } },
252
14
    { &hf_lithionics_system_status_battery_protection_state,
253
14
      { "Battery Protection State", "lithionics_bms.system_status.battery_protection_state", FT_BOOLEAN, 24, TFS(&tfs_lithionics_battery_protection_state), 0x000040, NULL, HFILL } },
254
14
    { &hf_lithionics_system_status_power_off_state,
255
14
      { "Power Off State", "lithionics_bms.system_status.power_off_state", FT_BOOLEAN, 24, TFS(&tfs_lithionics_power_off_state), 0x000080, NULL, HFILL } },
256
14
    { &hf_lithionics_system_status_aux_contacts_state,
257
14
      { "AUX Contacts State", "lithionics_bms.system_status.aux_contacts_state", FT_BOOLEAN, 24, NULL, 0x000100, NULL, HFILL } },
258
14
    { &hf_lithionics_system_status_aux_contacts_error,
259
14
      { "AUX Contacts Error", "lithionics_bms.system_status.aux_contacts_error", FT_BOOLEAN, 24, NULL, 0x000200, NULL, HFILL } },
260
14
    { &hf_lithionics_system_status_precharge_error,
261
14
      { "Pre-charge Error", "lithionics_bms.system_status.precharge_error", FT_BOOLEAN, 24, NULL, 0x000400, NULL, HFILL } },
262
14
    { &hf_lithionics_system_status_contactor_flutter,
263
14
      { "Contactor Flutter", "lithionics_bms.system_status.contactor_flutter", FT_BOOLEAN, 24, NULL, 0x000800, NULL, HFILL } },
264
14
    { &hf_lithionics_system_status_ac_power_present,
265
14
      { "AC Power Present", "lithionics_bms.system_status.ac_power_present", FT_BOOLEAN, 24, NULL, 0x001000, NULL, HFILL } },
266
14
    { &hf_lithionics_system_status_tsm_charger_present,
267
14
      { "TSM Charger Present", "lithionics_bms.system_status.tsm_charger_present", FT_BOOLEAN, 24, NULL, 0x002000, NULL, HFILL } },
268
14
    { &hf_lithionics_system_status_tsm_charger_error,
269
14
      { "TSM Charger Error", "lithionics_bms.system_status.tsm_charger_error", FT_BOOLEAN, 24, NULL, 0x004000, NULL, HFILL } },
270
14
    { &hf_lithionics_system_status_external_temp_sensor_error,
271
14
      { "External Temp Sensor Error", "lithionics_bms.system_status.external_temp_sensor_error", FT_BOOLEAN, 24, NULL, 0x008000, NULL, HFILL } },
272
14
    { &hf_lithionics_system_status_agsr_state,
273
14
      { "AGSR State", "lithionics_bms.system_status.agsr_state", FT_BOOLEAN, 24, NULL, 0x010000, NULL, HFILL } },
274
14
    { &hf_lithionics_system_status_high_temperature_state,
275
14
      { "High Temperature State", "lithionics_bms.system_status.high_temperature_state", FT_BOOLEAN, 24, TFS(&tfs_lithionics_high_temperature_state), 0x020000, NULL, HFILL } },
276
14
    { &hf_lithionics_system_status_low_temperature_state,
277
14
      { "Low Temperature State", "lithionics_bms.system_status.low_temperature_state", FT_BOOLEAN, 24, TFS(&tfs_lithionics_low_temperature_state), 0x040000, NULL, HFILL } },
278
14
    { &hf_lithionics_system_status_aux_input1_state,
279
14
      { "Auxiliary Input 1 State", "lithionics_bms.system_status.aux_input1_state", FT_BOOLEAN, 24, NULL, 0x080000, NULL, HFILL } },
280
14
    { &hf_lithionics_system_status_charge_disable_state,
281
14
      { "Charge Disable State", "lithionics_bms.system_status.charge_disable_state", FT_BOOLEAN, 24, NULL, 0x100000, NULL, HFILL } },
282
14
    { &hf_lithionics_system_status_overcurrent_state,
283
14
      { "Overcurrent State", "lithionics_bms.system_status.overcurrent_state", FT_BOOLEAN, 24, NULL, 0x200000, NULL, HFILL } },
284
14
    { &hf_lithionics_system_status_reserved,
285
14
      { "Reserved", "lithionics_bms.system_status.reserved", FT_UINT24, BASE_HEX, NULL, 0xC00000, NULL, HFILL } },
286
287
14
  };
288
289
14
  static int *ett[] = {
290
14
    &ett_lithionics,
291
14
    &ett_lithionics_system_status,
292
14
  };
293
294
14
  proto_lithionics = proto_register_protocol("Lithionics Battery Management System", "Lithionics BMS", "lithionics_bms");
295
14
  proto_register_field_array(proto_lithionics, hf, array_length(hf));
296
14
  proto_register_subtree_array(ett, array_length(ett));
297
298
14
  lithionics_handle = register_dissector("lithionics_bms", dissect_lithionics, proto_lithionics);
299
14
}
300
301
void
302
proto_reg_handoff_lithionics(void)
303
14
{
304
14
  dissector_add_for_decode_as("udp.port", lithionics_handle);
305
14
}
306
307
/*
308
 * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
309
 *
310
 * Local variables:
311
 * c-basic-offset: 4
312
 * tab-width: 8
313
 * indent-tabs-mode: t
314
 * End:
315
 *
316
 * vi: set shiftwidth=4 tabstop=8 expandtab:
317
 * :indentSize=4:tabSize=8:noTabs=false:
318
 */