MycilaJSY 13.0.0 Arduino / ESP32 library for the JSY1031, JSY-MK-163, JSY-MK-193, JSY-MK-194, JSY-MK-227, JSY-MK-229, JSY-MK-333 families single-phase and three-phase AC bidirectional meters from Shenzhen Jiansiyan Technologies Co, Ltd.

Loading...
Searching...
No Matches
MycilaJSY.cpp
1// SPDX-License-Identifier: MIT
2/*
3 * Copyright (C) 2023-2025 Mathieu Carbou
4 */
5#include "MycilaJSY.h"
6
7#include <algorithm>
8
9#ifdef MYCILA_LOGGER_SUPPORT
10 #include <MycilaLogger.h>
11extern Mycila::Logger logger;
12 #define LOGD(tag, format, ...) logger.debug(tag, format, ##__VA_ARGS__)
13 #define LOGI(tag, format, ...) logger.info(tag, format, ##__VA_ARGS__)
14 #define LOGW(tag, format, ...) logger.warn(tag, format, ##__VA_ARGS__)
15 #define LOGE(tag, format, ...) logger.error(tag, format, ##__VA_ARGS__)
16#else
17 #define LOGD(tag, format, ...) ESP_LOGD(tag, format, ##__VA_ARGS__)
18 #define LOGI(tag, format, ...) ESP_LOGI(tag, format, ##__VA_ARGS__)
19 #define LOGW(tag, format, ...) ESP_LOGW(tag, format, ##__VA_ARGS__)
20 #define LOGE(tag, format, ...) ESP_LOGE(tag, format, ##__VA_ARGS__)
21#endif
22
23#ifndef GPIO_IS_VALID_OUTPUT_GPIO
24 #define GPIO_IS_VALID_OUTPUT_GPIO(gpio_num) ((gpio_num >= 0) && \
25 (((1ULL << (gpio_num)) & SOC_GPIO_VALID_OUTPUT_GPIO_MASK) != 0))
26#endif
27
28#ifndef GPIO_IS_VALID_GPIO
29 #define GPIO_IS_VALID_GPIO(gpio_num) ((gpio_num >= 0) && \
30 (((1ULL << (gpio_num)) & SOC_GPIO_VALID_GPIO_MASK) != 0))
31#endif
32
33#define LOBYTE(x) ((uint8_t)((x) & 0xFF))
34#define HIBYTE(x) ((uint8_t)((x) >> 8))
35
36#define TAG "JSY"
37#define JSY_LOCK_TIMEOUT 2000
38
40// JSY COMMON REGISTERS
42
43// system
44#define JSY_REGISTER_MODEL1 0x0000 // RO - JSY Model
45#define JSY_REGISTER_MODEL2 0x0001 // RO - JSY Mode and Version
46#define JSY_REGISTER_VOLTAGE_RANGE 0x0002 // RO - Example: 250V
47#define JSY_REGISTER_CURRENT_RANGE 0x0003 // RO - Example: 800 (800 / 10 == 80A)
48
49// communication
50#define JSY_REGISTER_ID_AND_BAUDS 0x0004 // RW - ID (high byte) and Bauds Rate (low byte)
51#define JSY_REGISTER_SWITCH_MODE 0x0005 // RW - Switch Mode (AC/DC)
52#define JSY_MODE_AC 0x01 // AC Mode
53#define JSY_MODE_DC 0x02 // DC Mode
54
56// JSY-MK-1031 REGISTERS
58
59#define JSY_1031_REGISTER_VOLTAGE 0x0048 // RO
60#define JSY_1031_REGISTER_CURRENT 0x0049 // RO, + 0x004A
61#define JSY_1031_REGISTER_ACTIVE_POWER 0x004B // RO, + 0x004C
62#define JSY_1031_REGISTER_ACTIVE_ENERGY 0x004D // RO, + 0x004E
63#define JSY_1031_REGISTER_POWER_FACTOR 0x004F // RO
64#define JSY_1031_REGISTER_FREQUENCY 0x0050 // RO
65#define JSY_1031_REGISTER_CO2 0x0051 // RO, + 0x0052
66#define JSY_1031_REGISTER_RESERVED 0x0053 // RO, + 0x0054, + 0x0055, + 0x0056
67#define JSY_1031_REGISTER_APPARENT_POWER 0x0057 // RO. + 0x0058
68#define JSY_1031_REGISTER_REACTIVE_POWER 0x0059 // RO, + 0x005A
69#define JSY_1031_REGISTER_PHASE_ANGLE 0x005B // RO
70
71#define JSY_1031_REGISTER_LEN 2 // 2 bytes per register
72#define JSY_1031_REGISTER_COUNT 19 // 19 registers
73#define JSY_1031_REGISTER_START JSY_1031_REGISTER_VOLTAGE
74
76// JSY-MK-163 REGISTERS
78
79#define JSY_163_REGISTER_VOLTAGE 0x0048 // RO
80#define JSY_163_REGISTER_CURRENT 0x0049 // RO
81#define JSY_163_REGISTER_ACTIVE_POWER 0x004A // RO
82#define JSY_163_REGISTER_ACTIVE_ENERGY_IMPORTED 0x004B // RO, + 0x004C
83#define JSY_163_REGISTER_POWER_FACTOR 0x004D // RO
84#define JSY_163_REGISTER_ACTIVE_ENERGY_RETURNED 0x004E // RO, + 0x004F
85#define JSY_163_REGISTER_ACTIVE_POWER_SIGN 0x0050 // RO
86#define JSY_163_REGISTER_FREQUENCY 0x0051 // RO
87
88#define JSY_163_REGISTER_LEN 2 // 2 bytes per register
89#define JSY_163_REGISTER_COUNT 10 // 10 registers
90#define JSY_163_REGISTER_START JSY_163_REGISTER_VOLTAGE
91
93// JSY-MK-193 REGISTERS
95
96#define JSY_193_REGISTER_CH1_VOLTAGE 0x0100 // RO
97#define JSY_193_REGISTER_CH1_CURRENT 0x0101 // RO
98#define JSY_193_REGISTER_CH1_ACTIVE_POWER 0x0102 // RO
99#define JSY_193_REGISTER_CH1_ACTIVE_POWER_SIGN 0x0103 // RO
100#define JSY_193_REGISTER_CH1_ACTIVE_ENERGY_POSITIVE 0x0104 // RO, + 0x0105
101#define JSY_193_REGISTER_CH1_ACTIVE_ENERGY_NEGATIVE 0x0106 // RO, + 0x0107
102#define JSY_193_REGISTER_CH1_POWER_FACTOR 0x0108 // RO
103#define JSY_193_REGISTER_CH1_FREQUENCY 0x0109 // RO
104#define JSY_193_REGISTER_CH2_VOLTAGE 0x010A // RO
105#define JSY_193_REGISTER_CH2_CURRENT 0x010B // RO
106#define JSY_193_REGISTER_CH2_ACTIVE_POWER 0x010C // RO
107#define JSY_193_REGISTER_CH2_ACTIVE_POWER_SIGN 0x010D // RO
108#define JSY_193_REGISTER_CH2_ACTIVE_ENERGY_POSITIVE 0x010E // RO, + 0x010F
109#define JSY_193_REGISTER_CH2_ACTIVE_ENERGY_NEGATIVE 0x0110 // RO, + 0x0111
110#define JSY_193_REGISTER_CH2_POWER_FACTOR 0x0112 // RO
111#define JSY_193_REGISTER_CH2_FREQUENCY 0x0113 // RO
112
113#define JSY_193_REGISTER_LEN 2 // 2 bytes per register
114#define JSY_193_REGISTER_COUNT 20 // 20 registers
115#define JSY_193_REGISTER_START JSY_193_REGISTER_CH1_VOLTAGE
116
118// JSY-MK-194 REGISTERS
120
121#define JSY_194_REGISTER_CH1_VOLTAGE 0x0048 // RO
122#define JSY_194_REGISTER_CH1_CURRENT 0x0049 // RO
123#define JSY_194_REGISTER_CH1_ACTIVE_POWER 0x004A // RO
124#define JSY_194_REGISTER_CH1_ACTIVE_ENERGY_IMPORTED 0x004B // RO
125#define JSY_194_REGISTER_CH1_POWER_FACTOR 0x004C // RO
126#define JSY_194_REGISTER_CH1_ACTIVE_ENERGY_RETURNED 0x004D // RO
127#define JSY_194_REGISTER_ACTIVE_POWER_SIGNS 0x004E // RO
128#define JSY_194_REGISTER_FREQUENCY 0x004F // RO
129#define JSY_194_REGISTER_CH2_VOLTAGE 0x0050 // RO
130#define JSY_194_REGISTER_CH2_CURRENT 0x0051 // RO
131#define JSY_194_REGISTER_CH2_ACTIVE_POWER 0x0052 // RO
132#define JSY_194_REGISTER_CH2_ACTIVE_ENERGY_IMPORTED 0x0053 // RO
133#define JSY_194_REGISTER_CH2_POWER_FACTOR 0x0054 // RO
134#define JSY_194_REGISTER_CH2_ACTIVE_ENERGY_RETURNED 0x0055 // RO
135
136#define JSY_194_REGISTER_LEN 4 // 4 bytes per register
137#define JSY_194_REGISTER_COUNT 14 // 14 registers
138#define JSY_194_REGISTER_START JSY_194_REGISTER_CH1_VOLTAGE
139
141// JSY-MK-22x REGISTERS (227, 229)
143
144#define JSY_22x_REGISTER_VOLTAGE 0x0100 // RO, + 0x0101
145#define JSY_22x_REGISTER_CURRENT 0x0102 // RO, + 0x0103
146#define JSY_22x_REGISTER_ACTIVE_POWER 0x0104 // RO, + 0x0105
147#define JSY_22x_REGISTER_REACTIVE_POWER 0x0106 // RO, + 0x0107
148#define JSY_22x_REGISTER_APPARENT_POWER 0x0108 // RO, + 0x0109
149#define JSY_22x_REGISTER_POWER_FACTOR 0x010A // RO, + 0x010B
150#define JSY_22x_REGISTER_FREQUENCY 0x010C // RO, + 0x010D
151#define JSY_22x_REGISTER_ACTIVE_ENERGY 0x010E // RO, + 0x010F
152#define JSY_22x_REGISTER_REACTIVE_ENERGY 0x0110 // RO, + 0x0111
153#define JSY_22x_REGISTER_POWER_SUPPLY 0x0112 // RO, + 0x0113
154#define JSY_22x_REGISTER_ACTIVE_POWER_SIGN 0x0114 // RO
155#define JSY_22x_REGISTER_REACTIVE_POWER_SIGN 0x0115 // RO
156#define JSY_22x_REGISTER_ACTIVE_ENERGY_POSITIVE 0x0116 // RO, + 0x0117
157#define JSY_22x_REGISTER_ACTIVE_ENERGY_NEGATIVE 0x0118 // RO, + 0x0119
158#define JSY_22x_REGISTER_REACTIVE_ENERGY_POSITIVE 0x011A // RO, + 0x011B
159#define JSY_22x_REGISTER_REACTIVE_ENERGY_NEGATIVE 0x011C // RO, + 0x011D
160
161#define JSY_22x_REGISTER_LEN 2 // 2 bytes per register
162#define JSY_22x_REGISTER_COUNT 30 // 20 registers
163#define JSY_22x_REGISTER_START JSY_22x_REGISTER_VOLTAGE
164
166// JSY-MK-333 REGISTERS
168
169#define JSY_333_REGISTER_PHASE_A_VOLTAGE 0x0100 // RO
170#define JSY_333_REGISTER_PHASE_B_VOLTAGE 0x0101 // RO
171#define JSY_333_REGISTER_PHASE_C_VOLTAGE 0x0102 // RO
172#define JSY_333_REGISTER_PHASE_A_CURRENT 0x0103 // RO
173#define JSY_333_REGISTER_PHASE_B_CURRENT 0x0104 // RO
174#define JSY_333_REGISTER_PHASE_C_CURRENT 0x0105 // RO
175#define JSY_333_REGISTER_PHASE_A_ACTIVE_POWER 0x0106 // RO
176#define JSY_333_REGISTER_PHASE_B_ACTIVE_POWER 0x0107 // RO
177#define JSY_333_REGISTER_PHASE_C_ACTIVE_POWER 0x0108 // RO
178#define JSY_333_REGISTER_TOTAL_ACTIVE_POWER 0x0109 // RO + 0x010A
179#define JSY_333_REGISTER_PHASE_A_REACTIVE_POWER 0x010B // RO
180#define JSY_333_REGISTER_PHASE_B_REACTIVE_POWER 0x010C // RO
181#define JSY_333_REGISTER_PHASE_C_REACTIVE_POWER 0x010D // RO
182#define JSY_333_REGISTER_TOTAL_REACTIVE_POWER 0x010E // RO + 0x010F
183#define JSY_333_REGISTER_PHASE_A_APPARENT_POWER 0x0110 // RO
184#define JSY_333_REGISTER_PHASE_B_APPARENT_POWER 0x0111 // RO
185#define JSY_333_REGISTER_PHASE_C_APPARENT_POWER 0x0112 // RO
186#define JSY_333_REGISTER_TOTAL_APPARENT_POWER 0x0113 // RO + 0x0114
187#define JSY_333_REGISTER_FREQUENCY 0x0115 // RO
188#define JSY_333_REGISTER_PHASE_A_POWER_FACTOR 0x0116 // RO
189#define JSY_333_REGISTER_PHASE_B_POWER_FACTOR 0x0117 // RO
190#define JSY_333_REGISTER_PHASE_C_POWER_FACTOR 0x0118 // RO
191#define JSY_333_REGISTER_TOTAL_POWER_FACTOR 0x0119 // RO
192#define JSY_333_REGISTER_PHASE_A_ACTIVE_ENERGY 0x011A // RO + 0x011B
193#define JSY_333_REGISTER_PHASE_B_ACTIVE_ENERGY 0x011C // RO + 0x011D
194#define JSY_333_REGISTER_PHASE_C_ACTIVE_ENERGY 0x011E // RO + 0x011F
195#define JSY_333_REGISTER_TOTAL_ACTIVE_ENERGY 0x0120 // RO + 0x0121
196#define JSY_333_REGISTER_PHASE_A_REACTIVE_ENERGY 0x0122 // RO + 0x0123
197#define JSY_333_REGISTER_PHASE_B_REACTIVE_ENERGY 0x0124 // RO + 0x0125
198#define JSY_333_REGISTER_PHASE_C_REACTIVE_ENERGY 0x0126 // RO + 0x0127
199#define JSY_333_REGISTER_TOTAL_REACTIVE_ENERGY 0x0128 // RO + 0x0129
200#define JSY_333_REGISTER_PHASE_A_APPARENT_ENERGY 0x012A // RO + 0x012B
201#define JSY_333_REGISTER_PHASE_B_APPARENT_ENERGY 0x012C // RO + 0x012D
202#define JSY_333_REGISTER_PHASE_C_APPARENT_ENERGY 0x012E // RO + 0x012F
203#define JSY_333_REGISTER_TOTAL_APPARENT_ENERGY 0x0130 // RO + 0x0131
204#define JSY_333_REGISTER_POWER_SIGNS 0x0132 // RO
205#define JSY_333_REGISTER_ALARMS 0x0133 // RO
206#define JSY_333_REGISTER_PHASE_A_ACTIVE_ENERGY_IMPORTED 0x0134 // RO + 0x0135
207#define JSY_333_REGISTER_PHASE_B_ACTIVE_ENERGY_IMPORTED 0x0136 // RO + 0x0137
208#define JSY_333_REGISTER_PHASE_C_ACTIVE_ENERGY_IMPORTED 0x0138 // RO + 0x0139
209#define JSY_333_REGISTER_TOTAL_ACTIVE_ENERGY_IMPORTED 0x013A // RO + 0x013B
210#define JSY_333_REGISTER_PHASE_A_ACTIVE_ENERGY_RETURNED 0x013C // RO + 0x013D
211#define JSY_333_REGISTER_PHASE_B_ACTIVE_ENERGY_RETURNED 0x013E // RO + 0x013F
212#define JSY_333_REGISTER_PHASE_C_ACTIVE_ENERGY_RETURNED 0x0140 // RO + 0x0141
213#define JSY_333_REGISTER_TOTAL_ACTIVE_ENERGY_RETURNED 0x0142 // RO + 0x0143
214#define JSY_333_REGISTER_PHASE_A_REACTIVE_ENERGY_IMPORTED 0x0144 // RO + 0x0145
215#define JSY_333_REGISTER_PHASE_B_REACTIVE_ENERGY_IMPORTED 0x0146 // RO + 0x0147
216#define JSY_333_REGISTER_PHASE_C_REACTIVE_ENERGY_IMPORTED 0x0148 // RO + 0x0149
217#define JSY_333_REGISTER_TOTAL_REACTIVE_ENERGY_IMPORTED 0x014A // RO + 0x014B
218#define JSY_333_REGISTER_PHASE_A_REACTIVE_ENERGY_RETURNED 0x014C // RO + 0x014D
219#define JSY_333_REGISTER_PHASE_B_REACTIVE_ENERGY_RETURNED 0x014E // RO + 0x014F
220#define JSY_333_REGISTER_PHASE_C_REACTIVE_ENERGY_RETURNED 0x0150 // RO + 0x0151
221#define JSY_333_REGISTER_TOTAL_REACTIVE_ENERGY_RETURNED 0x0152 // RO + 0x0153
222#define JSY_333_REGISTER_PHASE_A_L_U 0x0154 // RO -- WARNING: UNDOCUMENTED
223#define JSY_333_REGISTER_PHASE_B_L_U 0x0155 // RO -- WARNING: UNDOCUMENTED
224#define JSY_333_REGISTER_PHASE_C_L_U 0x0156 // RO -- WARNING: UNDOCUMENTED
225#define JSY_333_REGISTER_PHASE_A_PHASE_ANGLE_U 0x0157 // RO -- WARNING: UNDOCUMENTED
226#define JSY_333_REGISTER_PHASE_B_PHASE_ANGLE_U 0x0158 // RO -- WARNING: UNDOCUMENTED
227#define JSY_333_REGISTER_PHASE_C_PHASE_ANGLE_U 0x0159 // RO -- WARNING: UNDOCUMENTED
228#define JSY_333_REGISTER_PHASE_A_PHASE_ANGLE_I 0x015A // RO -- WARNING: UNDOCUMENTED
229#define JSY_333_REGISTER_PHASE_B_PHASE_ANGLE_I 0x015B // RO -- WARNING: UNDOCUMENTED
230#define JSY_333_REGISTER_PHASE_C_PHASE_ANGLE_I 0x015C // RO -- WARNING: UNDOCUMENTED
231#define JSY_333_REGISTER_PHASE_A_PHASE_ANGLE_UI 0x015D // RO -- WARNING: UNDOCUMENTED
232#define JSY_333_REGISTER_PHASE_B_PHASE_ANGLE_UI 0x015E // RO -- WARNING: UNDOCUMENTED
233#define JSY_333_REGISTER_PHASE_C_PHASE_ANGLE_UI 0x015F // RO -- WARNING: UNDOCUMENTED
234#define JSY_333_REGISTER_PHASE_A_THD_U 0x0160 // RO -- WARNING: UNDOCUMENTED
235#define JSY_333_REGISTER_PHASE_B_THD_U 0x0161 // RO -- WARNING: UNDOCUMENTED
236#define JSY_333_REGISTER_PHASE_C_THD_U 0x0162 // RO -- WARNING: UNDOCUMENTED
237#define JSY_333_REGISTER_PHASE_A_THD_I 0x0163 // RO -- WARNING: UNDOCUMENTED
238#define JSY_333_REGISTER_PHASE_B_THD_I 0x0164 // RO -- WARNING: UNDOCUMENTED
239#define JSY_333_REGISTER_PHASE_C_THD_I 0x0165 // RO -- WARNING: UNDOCUMENTED
240
241#define JSY_333_REGISTER_LEN 2 // 2 bytes per register
242#define JSY_333_REGISTER_COUNT 102 // registers
243#define JSY_333_REGISTER_START JSY_333_REGISTER_PHASE_A_VOLTAGE
244
246// JSY PROTOCOL
248
249// commands values
250#define JSY_CMD_READ_REGISTERS 0x03
251#define JSY_CMD_READ_RELAY1 0x01
252#define JSY_CMD_WRITE_REGISTERS 0x10
253#define JSY_CMD_WRITE_RELAY1 0x05
254
255// request indexes
256#define JSY_REQUEST_ADDRESS 0
257#define JSY_REQUEST_CMD 1
258#define JSY_REQUEST_READ_REGISTER_ADDR_HIGH 2
259#define JSY_REQUEST_READ_REGISTER_ADDR_LOW 3
260#define JSY_REQUEST_READ_REGISTER_COUNT_HIGH 4
261#define JSY_REQUEST_READ_REGISTER_COUNT_LOW 5
262#define JSY_REQUEST_SET_ADDRESS 7
263#define JSY_REQUEST_SET_BAUDS 8
264#define JSY_REQUEST_SET_MODE 8
265
266// response indexes
267#define JSY_RESPONSE_ADDRESS 0
268#define JSY_RESPONSE_CMD 1
269#define JSY_RESPONSE_DATA_LEN 2
270#define JSY_RESPONSE_DATA 3
271
272// response
273#define JSY_RESPONSE_SIZE_READ 5 // address(1), cmd(1), len(1), data(?), crc(2)
274#define JSY_RESPONSE_SIZE_READ_MODEL JSY_RESPONSE_SIZE_READ + 2 // address(1), cmd(1), len(1), data(2), crc(2)
275#define JSY_RESPONSE_SIZE_READ_MODE JSY_RESPONSE_SIZE_READ_MODEL
276#define JSY_RESPONSE_SIZE_RESET_ENERGY 8 // address(1), cmd(1), len(1), register(2), count(2), crc(2)
277#define JSY_RESPONSE_SIZE_SWITCH_MODE 8 // address(1), cmd(1), register(2), data(2), crc(2)
278#define JSY_RESPONSE_SIZE_SET_COM 8 // address(1), cmd(1), data(4), crc(2)
279
280static constexpr uint8_t JSY_REQUEST_READ_REGISTERS[] = {
281 MYCILA_JSY_ADDRESS_BROADCAST,
282 JSY_CMD_READ_REGISTERS,
283 0x00, // register start address (high byte)
284 0x00, // register start address (low byte)
285 0x00, // number of registers to read (high byte)
286 0x00, // number of registers to read (low byte)
287 0x00, // CRC (low)
288 0x00 // CRC (high)
289};
290static constexpr size_t JSY_REQUEST_READ_REGISTERS_LEN = sizeof(JSY_REQUEST_READ_REGISTERS);
291
292static constexpr uint8_t JSY_REQUEST_READ_MODEL[] = {
293 MYCILA_JSY_ADDRESS_BROADCAST,
294 JSY_CMD_READ_REGISTERS,
295 HIBYTE(JSY_REGISTER_MODEL1),
296 LOBYTE(JSY_REGISTER_MODEL1),
297 0x00, // number of registers to read (high byte)
298 0x01, // number of registers to read (low byte)
299 0x00, // CRC (low)
300 0x00 // CRC (high)
301};
302static constexpr size_t JSY_REQUEST_READ_MODEL_LEN = sizeof(JSY_REQUEST_READ_MODEL);
303
304static constexpr uint8_t JSY_REQUEST_READ_MODE[] = {
305 MYCILA_JSY_ADDRESS_BROADCAST,
306 JSY_CMD_READ_REGISTERS,
307 HIBYTE(JSY_REGISTER_MODEL2),
308 LOBYTE(JSY_REGISTER_MODEL2),
309 0x00, // number of registers to read (high byte)
310 0x01, // number of registers to read (low byte)
311 0x00, // CRC (low)
312 0x00 // CRC (high)
313};
314static constexpr size_t JSY_REQUEST_READ_MODE_LEN = sizeof(JSY_REQUEST_READ_MODE);
315
316static constexpr uint8_t JSY_REQUEST_RESET_ENERGY[] = {
317 MYCILA_JSY_ADDRESS_BROADCAST,
318 JSY_CMD_WRITE_REGISTERS,
319 0x00, // start address (high byte)
320 0x0C, // start address (low byte)
321 0x00, // number of registers to write (high byte)
322 0x02, // number of registers to write (low byte)
323 0x04, // number of bytes to follow
324 0x00, // data
325 0x00, // data
326 0x00, // data
327 0x00, // data
328 0x00, // CRC (low)
329 0x00 // CRC (high)
330};
331static constexpr size_t JSY_REQUEST_RESET_ENERGY_LEN = sizeof(JSY_REQUEST_RESET_ENERGY);
332
333static constexpr uint8_t JSY_REQUEST_SET_COM[] = {
334 MYCILA_JSY_ADDRESS_BROADCAST,
335 JSY_CMD_WRITE_REGISTERS,
336 HIBYTE(JSY_REGISTER_ID_AND_BAUDS),
337 LOBYTE(JSY_REGISTER_ID_AND_BAUDS),
338 0x00, // number of registers to write (high byte)
339 0x01, // number of registers to write (low byte)
340 0x02, // number of bytes to follow
341 0x00, // new device address
342 0x00, // BAUDS ID
343 0x00, // CRC (low)
344 0x00 // CRC (high)
345};
346static constexpr size_t JSY_REQUEST_SET_COM_LEN = sizeof(JSY_REQUEST_SET_COM);
347
348static constexpr uint8_t JSY_REQUEST_SWITCH_MODE[] = {
349 MYCILA_JSY_ADDRESS_BROADCAST,
350 JSY_CMD_WRITE_REGISTERS,
351 HIBYTE(JSY_REGISTER_SWITCH_MODE),
352 LOBYTE(JSY_REGISTER_SWITCH_MODE),
353 0x00, // number of registers to write (high byte)
354 0x01, // number of registers to write (low byte)
355 0x02, // number of bytes to follow
356 0x00, // switch signal (high byte)
357 0x00, // switch signal (low byte)
358 0x00, // CRC (low)
359 0x00 // CRC (high)
360};
361static constexpr size_t JSY_REQUEST_SWITCH_MODE_LEN = sizeof(JSY_REQUEST_SWITCH_MODE);
362
364// Bauds
366
367static constexpr Mycila::JSY::BaudRate AUTO_DETECT_BAUD_RATES[] = {
368 Mycila::JSY::BaudRate::BAUD_4800, // default value for some JSY
369 Mycila::JSY::BaudRate::BAUD_9600, // default value for some JSY
370 Mycila::JSY::BaudRate::BAUD_19200, // supported speed for some JSY
371 Mycila::JSY::BaudRate::BAUD_38400, // supported speed for some JSY
372 Mycila::JSY::BaudRate::BAUD_1200, // supported speed for some JSY but slower - will probably never be used
373 Mycila::JSY::BaudRate::BAUD_2400, // supported speed for some JSY but slower - will probably never be used
374};
375static constexpr size_t AUTO_DETECT_BAUD_RATES_COUNT = 6;
376
378// CRC16 Table
380
381static constexpr uint16_t CRCTable[] = {
382 0x0000,
383 0xC0C1,
384 0xC181,
385 0x0140,
386 0xC301,
387 0x03C0,
388 0x0280,
389 0xC241,
390 0xC601,
391 0x06C0,
392 0x0780,
393 0xC741,
394 0x0500,
395 0xC5C1,
396 0xC481,
397 0x0440,
398 0xCC01,
399 0x0CC0,
400 0x0D80,
401 0xCD41,
402 0x0F00,
403 0xCFC1,
404 0xCE81,
405 0x0E40,
406 0x0A00,
407 0xCAC1,
408 0xCB81,
409 0x0B40,
410 0xC901,
411 0x09C0,
412 0x0880,
413 0xC841,
414 0xD801,
415 0x18C0,
416 0x1980,
417 0xD941,
418 0x1B00,
419 0xDBC1,
420 0xDA81,
421 0x1A40,
422 0x1E00,
423 0xDEC1,
424 0xDF81,
425 0x1F40,
426 0xDD01,
427 0x1DC0,
428 0x1C80,
429 0xDC41,
430 0x1400,
431 0xD4C1,
432 0xD581,
433 0x1540,
434 0xD701,
435 0x17C0,
436 0x1680,
437 0xD641,
438 0xD201,
439 0x12C0,
440 0x1380,
441 0xD341,
442 0x1100,
443 0xD1C1,
444 0xD081,
445 0x1040,
446 0xF001,
447 0x30C0,
448 0x3180,
449 0xF141,
450 0x3300,
451 0xF3C1,
452 0xF281,
453 0x3240,
454 0x3600,
455 0xF6C1,
456 0xF781,
457 0x3740,
458 0xF501,
459 0x35C0,
460 0x3480,
461 0xF441,
462 0x3C00,
463 0xFCC1,
464 0xFD81,
465 0x3D40,
466 0xFF01,
467 0x3FC0,
468 0x3E80,
469 0xFE41,
470 0xFA01,
471 0x3AC0,
472 0x3B80,
473 0xFB41,
474 0x3900,
475 0xF9C1,
476 0xF881,
477 0x3840,
478 0x2800,
479 0xE8C1,
480 0xE981,
481 0x2940,
482 0xEB01,
483 0x2BC0,
484 0x2A80,
485 0xEA41,
486 0xEE01,
487 0x2EC0,
488 0x2F80,
489 0xEF41,
490 0x2D00,
491 0xEDC1,
492 0xEC81,
493 0x2C40,
494 0xE401,
495 0x24C0,
496 0x2580,
497 0xE541,
498 0x2700,
499 0xE7C1,
500 0xE681,
501 0x2640,
502 0x2200,
503 0xE2C1,
504 0xE381,
505 0x2340,
506 0xE101,
507 0x21C0,
508 0x2080,
509 0xE041,
510 0xA001,
511 0x60C0,
512 0x6180,
513 0xA141,
514 0x6300,
515 0xA3C1,
516 0xA281,
517 0x6240,
518 0x6600,
519 0xA6C1,
520 0xA781,
521 0x6740,
522 0xA501,
523 0x65C0,
524 0x6480,
525 0xA441,
526 0x6C00,
527 0xACC1,
528 0xAD81,
529 0x6D40,
530 0xAF01,
531 0x6FC0,
532 0x6E80,
533 0xAE41,
534 0xAA01,
535 0x6AC0,
536 0x6B80,
537 0xAB41,
538 0x6900,
539 0xA9C1,
540 0xA881,
541 0x6840,
542 0x7800,
543 0xB8C1,
544 0xB981,
545 0x7940,
546 0xBB01,
547 0x7BC0,
548 0x7A80,
549 0xBA41,
550 0xBE01,
551 0x7EC0,
552 0x7F80,
553 0xBF41,
554 0x7D00,
555 0xBDC1,
556 0xBC81,
557 0x7C40,
558 0xB401,
559 0x74C0,
560 0x7580,
561 0xB541,
562 0x7700,
563 0xB7C1,
564 0xB681,
565 0x7640,
566 0x7200,
567 0xB2C1,
568 0xB381,
569 0x7340,
570 0xB101,
571 0x71C0,
572 0x7080,
573 0xB041,
574 0x5000,
575 0x90C1,
576 0x9181,
577 0x5140,
578 0x9301,
579 0x53C0,
580 0x5280,
581 0x9241,
582 0x9601,
583 0x56C0,
584 0x5780,
585 0x9741,
586 0x5500,
587 0x95C1,
588 0x9481,
589 0x5440,
590 0x9C01,
591 0x5CC0,
592 0x5D80,
593 0x9D41,
594 0x5F00,
595 0x9FC1,
596 0x9E81,
597 0x5E40,
598 0x5A00,
599 0x9AC1,
600 0x9B81,
601 0x5B40,
602 0x9901,
603 0x59C0,
604 0x5880,
605 0x9841,
606 0x8801,
607 0x48C0,
608 0x4980,
609 0x8941,
610 0x4B00,
611 0x8BC1,
612 0x8A81,
613 0x4A40,
614 0x4E00,
615 0x8EC1,
616 0x8F81,
617 0x4F40,
618 0x8D01,
619 0x4DC0,
620 0x4C80,
621 0x8C41,
622 0x4400,
623 0x84C1,
624 0x8581,
625 0x4540,
626 0x8701,
627 0x47C0,
628 0x4680,
629 0x8641,
630 0x8201,
631 0x42C0,
632 0x4380,
633 0x8341,
634 0x4100,
635 0x81C1,
636 0x8081,
637 0x4040};
638
640// begin / end
642
643 void Mycila::JSY::begin(HardwareSerial& serial,
644 const int8_t rxPin,
645 const int8_t txPin,
646 const BaudRate baudRate,
647 const uint8_t destinationAddress,
648 const uint16_t model,
649 const bool async,
650 const uint8_t core,
651 const uint32_t stackSize,
652 const uint32_t pause) {
653 if (_enabled)
654 return;
655
656 if (GPIO_IS_VALID_GPIO(rxPin)) {
657 _pinRX = (gpio_num_t)rxPin;
658 } else {
659 LOGE(TAG, "Disable JSY: Invalid Serial RX (JSY TX pin): %" PRId8, rxPin);
660 _pinRX = GPIO_NUM_NC;
661 return;
662 }
663
664 if (GPIO_IS_VALID_OUTPUT_GPIO(txPin)) {
665 _pinTX = (gpio_num_t)txPin;
666 } else {
667 LOGE(TAG, "Disable JSY: Invalid Serial TX (JSY RX pin): %" PRId8, txPin);
668 _pinTX = GPIO_NUM_NC;
669 return;
670 }
671
672 LOGI(TAG, "Enable JSY @ 0x%02X on Serial RX (JSY TX Pin): %" PRId8 " and Serial TX (JSY RX Pin): %" PRId8, destinationAddress, rxPin, txPin);
673
674 _pause = pause;
675 _serial = &serial;
676
677 if (baudRate == BaudRate::UNKNOWN) {
678 _baudRate = _detectBauds(destinationAddress);
679
680 if (_baudRate == BaudRate::UNKNOWN) {
681 if (_lastAddress == MYCILA_JSY_ADDRESS_UNKNOWN)
682 LOGE(TAG, "Unable to read any JSY @ 0x%02X at any supported speed.", destinationAddress);
683 else
684 LOGE(TAG, "Unable to read any JSY @ 0x%02X at any supported speed but found one @ 0x%02X.", destinationAddress, _lastAddress);
685
686 _serial->end();
687 return;
688 }
689
690 } else {
691 LOGW(TAG, "JSY @ 0x%02X bauds detection skipped, forcing baud rate: %" PRIu32, destinationAddress, _baudRate);
692 _openSerial(baudRate);
693
694 _baudRate = BaudRate::UNKNOWN;
695 for (int j = 0; j < MYCILA_JSY_RETRY_COUNT; j++) {
696 if (_canRead(destinationAddress, baudRate)) {
697 _baudRate = baudRate;
698 break;
699 }
700 }
701
702 if (_baudRate == BaudRate::UNKNOWN) {
703 LOGE(TAG, "Unable to read any JSY @ 0x%02X at speed: %" PRIu32, destinationAddress, baudRate);
704 _serial->end();
705 return;
706 }
707 }
708
709 _enabled = true;
710 _model = model ? model : readModel(destinationAddress);
711
712 if (_model != MYCILA_JSY_MK_1031 &&
713 _model != MYCILA_JSY_MK_163 &&
714 _model != MYCILA_JSY_MK_193 &&
715 _model != MYCILA_JSY_MK_194 &&
716 _model != MYCILA_JSY_MK_227 &&
717 _model != MYCILA_JSY_MK_229 &&
718 _model != MYCILA_JSY_MK_333) {
719 LOGE(TAG, "Unsupported JSY model: JSY-MK-%X", _model);
720 // unsupported
721 _enabled = false;
722 _serial->end();
723 return;
724 }
725
726 _destinationAddress = destinationAddress;
727 LOGI(TAG, "Detected JSY-MK-%X @ 0x%02X with speed %" PRIu32 " bauds", _model, _lastAddress, _baudRate);
728
729 assert(!async || xTaskCreateUniversal(_jsyTask, "jsyTask", stackSize, this, MYCILA_JSY_ASYNC_PRIORITY, &_taskHandle, core) == pdPASS);
730}
731
732 void Mycila::JSY::end() {
733 if (_enabled) {
734 LOGI(TAG, "Disable JSY @ 0x%02X", _destinationAddress);
735 _enabled = false;
736 while (_taskHandle != NULL) {
737 // JSY takes at least 40-160 ms to finish a read
738 delay(50);
739 }
740 _baudRate = BaudRate::UNKNOWN;
741 _lastAddress = MYCILA_JSY_ADDRESS_UNKNOWN;
742 _model = MYCILA_JSY_MK_UNKNOWN;
743 _data.clear();
744 _serial->end();
745 }
746}
747
749// read
751
752bool Mycila::JSY::_read(const uint8_t address, uint16_t model) {
753 if (!_enabled)
754 return false;
755
756 std::lock_guard<std::mutex> lock(_mutex);
757
758#ifdef MYCILA_JSY_DEBUG
759 Serial.printf("[JSY] read(0x%02X)\n", address);
760#endif
761
762 memcpy(_buffer, JSY_REQUEST_READ_REGISTERS, JSY_REQUEST_READ_REGISTERS_LEN);
763
764 // fill the request with the registers to read
765 // this depends on the model
766 uint16_t registerStart = 0;
767 uint16_t registerCount = 0;
768 uint8_t registerSize = 0;
769
770 switch (model) {
771 case MYCILA_JSY_MK_1031:
772 registerSize = JSY_1031_REGISTER_LEN;
773 registerStart = JSY_1031_REGISTER_START;
774 registerCount = JSY_1031_REGISTER_COUNT;
775 break;
776
777 case MYCILA_JSY_MK_163:
778 registerSize = JSY_163_REGISTER_LEN;
779 registerStart = JSY_163_REGISTER_START;
780 registerCount = JSY_163_REGISTER_COUNT;
781 break;
782
783 case MYCILA_JSY_MK_193:
784 registerSize = JSY_193_REGISTER_LEN;
785 registerStart = JSY_193_REGISTER_START;
786 registerCount = JSY_193_REGISTER_COUNT;
787 break;
788
789 case MYCILA_JSY_MK_194:
790 registerSize = JSY_194_REGISTER_LEN;
791 registerStart = JSY_194_REGISTER_START;
792 registerCount = JSY_194_REGISTER_COUNT;
793 break;
794
795 case MYCILA_JSY_MK_227:
796 case MYCILA_JSY_MK_229:
797 registerSize = JSY_22x_REGISTER_LEN;
798 registerStart = JSY_22x_REGISTER_START;
799 registerCount = JSY_22x_REGISTER_COUNT;
800 break;
801
802 case MYCILA_JSY_MK_333:
803 registerSize = JSY_333_REGISTER_LEN;
804 registerStart = JSY_333_REGISTER_START;
805 registerCount = JSY_333_REGISTER_COUNT;
806 break;
807
808 default:
809 break;
810 }
811
812 _buffer[JSY_REQUEST_READ_REGISTER_ADDR_HIGH] = HIBYTE(registerStart);
813 _buffer[JSY_REQUEST_READ_REGISTER_ADDR_LOW] = LOBYTE(registerStart);
814 _buffer[JSY_REQUEST_READ_REGISTER_COUNT_HIGH] = HIBYTE(registerCount);
815 _buffer[JSY_REQUEST_READ_REGISTER_COUNT_LOW] = LOBYTE(registerCount);
816
817 _send(address, JSY_REQUEST_READ_REGISTERS_LEN);
818 ReadResult result = _timedRead(address, JSY_RESPONSE_SIZE_READ + registerCount * registerSize, _baudRate);
819
820 if (result == ReadResult::READ_TIMEOUT) {
821 // reset live values in case of read timeout
822 _data.clear();
823 if (_callback) {
824 _callback(EventType::EVT_READ_TIMEOUT, _data);
825 }
826 return false;
827 }
828
829 if (result == ReadResult::READ_ERROR_COUNT || result == ReadResult::READ_ERROR_CRC) {
830 // reset live values in case of read failure
831 _data.clear();
832 if (_callback) {
833 _callback(EventType::EVT_READ_ERROR, _data);
834 }
835 return false;
836 }
837
838 if (result == ReadResult::READ_ERROR_ADDRESS) {
839 // we have set a destination address, but we read another device
840 if (_callback) {
841 _callback(EventType::EVT_READ_ERROR, _data);
842 }
843 return false;
844 }
845
846 assert(result == ReadResult::READ_SUCCESS);
847
848 _data.address = _buffer[JSY_RESPONSE_ADDRESS];
849 _data.model = model;
850
851 switch (model) {
852 case MYCILA_JSY_MK_1031: {
853 // single channel
854 _data._metrics[0].frequency = _register16(_buffer, registerStart, registerSize, JSY_1031_REGISTER_FREQUENCY) * 0.01f;
855 _data._metrics[0].voltage = _register16(_buffer, registerStart, registerSize, JSY_1031_REGISTER_VOLTAGE) * 0.01f;
856 _data._metrics[0].current = _register32(_buffer, registerStart, registerSize, JSY_1031_REGISTER_CURRENT) * 0.0001f;
857 _data._metrics[0].activePower = _register32(_buffer, registerStart, registerSize, JSY_1031_REGISTER_ACTIVE_POWER) * 0.0001f; // note: spec says /100 but in reality this is /10000
858 _data._metrics[0].activeEnergy = _register32(_buffer, registerStart, registerSize, JSY_1031_REGISTER_ACTIVE_ENERGY) * 10;
859 _data._metrics[0].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_1031_REGISTER_POWER_FACTOR) * 0.001f;
860 _data._metrics[0].apparentPower = _register32(_buffer, registerStart, registerSize, JSY_1031_REGISTER_APPARENT_POWER) * 0.0001f; // note: spec says /100 but in reality this is /10000
861 _data._metrics[0].reactivePower = _register32(_buffer, registerStart, registerSize, JSY_1031_REGISTER_REACTIVE_POWER) * 0.0001f; // note: spec says /100 but in reality this is /10000
862
863 // aggregate
864 _data.aggregate = _data._metrics[0];
865
866 break;
867 }
868
869 case MYCILA_JSY_MK_163: {
870 // signs
871 // _buffer[19] unused
872 // _buffer[20] is the sign of power
873 uint8_t sign = _register8(_buffer, registerStart, registerSize, JSY_163_REGISTER_ACTIVE_POWER_SIGN, 1);
874
875 // single channel
876 _data._metrics[0].frequency = _register16(_buffer, registerStart, registerSize, JSY_163_REGISTER_FREQUENCY) * 0.01f;
877 _data._metrics[0].voltage = _register16(_buffer, registerStart, registerSize, JSY_163_REGISTER_VOLTAGE) * 0.01f;
878 _data._metrics[0].current = _register16(_buffer, registerStart, registerSize, JSY_163_REGISTER_CURRENT) * 0.01f;
879 _data._metrics[0].activePower = _register16(_buffer, registerStart, registerSize, JSY_163_REGISTER_ACTIVE_POWER) * (sign ? -1.0f : 1.0f);
880 _data._metrics[0].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_163_REGISTER_ACTIVE_ENERGY_IMPORTED) * 5.0f / 16.0f;
881 _data._metrics[0].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_163_REGISTER_POWER_FACTOR) * 0.001f;
882 _data._metrics[0].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_163_REGISTER_ACTIVE_ENERGY_RETURNED) * 5.0f / 16.0f;
883
884 // calculate remaining metrics
885 // S = P / PF
886 _data._metrics[0].apparentPower = _data._metrics[0].powerFactor == 0 ? 0 : std::abs(_data._metrics[0].activePower / _data._metrics[0].powerFactor);
887 // Q = std::sqrt(S^2 - P^2)
888 _data._metrics[0].reactivePower = std::sqrt(_data._metrics[0].apparentPower * _data._metrics[0].apparentPower - _data._metrics[0].activePower * _data._metrics[0].activePower);
889 // E = Ei + Er
890 _data._metrics[0].activeEnergy = _data._metrics[0].activeEnergyImported + _data._metrics[0].activeEnergyReturned;
891
892 // aggregate
893 _data.aggregate = _data._metrics[0];
894
895 break;
896 }
897
898 case MYCILA_JSY_MK_193: {
899 // channel 1
900 _data._metrics[0].voltage = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_VOLTAGE) * 0.01f;
901 _data._metrics[0].current = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_CURRENT) * 0.01f;
902 _data._metrics[0].activePower = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_ACTIVE_POWER) * (_register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_ACTIVE_POWER_SIGN) ? -1.0f : 1.0f);
903 _data._metrics[0].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_ACTIVE_ENERGY_POSITIVE) * 10;
904 _data._metrics[0].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_ACTIVE_ENERGY_NEGATIVE) * 10;
905 _data._metrics[0].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_POWER_FACTOR) * 0.001f;
906 _data._metrics[0].frequency = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_FREQUENCY) * 0.01f;
907
908 // channel 2
909 _data._metrics[1].voltage = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_VOLTAGE) * 0.01f;
910 _data._metrics[1].current = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_CURRENT) * 0.01f;
911 _data._metrics[1].activePower = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_ACTIVE_POWER) * (_register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_ACTIVE_POWER_SIGN) ? -1.0f : 1.0f);
912 _data._metrics[1].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_ACTIVE_ENERGY_POSITIVE) * 10;
913 _data._metrics[1].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_ACTIVE_ENERGY_NEGATIVE) * 10;
914 _data._metrics[1].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_POWER_FACTOR) * 0.001f;
915 _data._metrics[1].frequency = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_FREQUENCY) * 0.01f;
916
917 // calculate remaining metrics
918 // S = P / PF
919 _data._metrics[0].apparentPower = _data._metrics[0].powerFactor == 0 ? 0 : std::abs(_data._metrics[0].activePower / _data._metrics[0].powerFactor);
920 _data._metrics[1].apparentPower = _data._metrics[1].powerFactor == 0 ? 0 : std::abs(_data._metrics[1].activePower / _data._metrics[1].powerFactor);
921 // Q = std::sqrt(S^2 - P^2)
922 _data._metrics[0].reactivePower = std::sqrt(_data._metrics[0].apparentPower * _data._metrics[0].apparentPower - _data._metrics[0].activePower * _data._metrics[0].activePower);
923 _data._metrics[1].reactivePower = std::sqrt(_data._metrics[1].apparentPower * _data._metrics[1].apparentPower - _data._metrics[1].activePower * _data._metrics[1].activePower);
924 // E = Ei + Er
925 _data._metrics[0].activeEnergy = _data._metrics[0].activeEnergyImported + _data._metrics[0].activeEnergyReturned;
926 _data._metrics[1].activeEnergy = _data._metrics[1].activeEnergyImported + _data._metrics[1].activeEnergyReturned;
927
928 // aggregate
929 _data.aggregate = _data._metrics[0];
930 _data.aggregate += _data._metrics[1];
931 _data.aggregate.voltage = std::max(_data._metrics[0].voltage, _data._metrics[1].voltage);
932 _data.aggregate.frequency = std::max(_data._metrics[0].frequency, _data._metrics[1].frequency);
933
934 break;
935 }
936
937 case MYCILA_JSY_MK_194: {
938 // signs
939 // _buffer[27] is the sign of power1
940 // _buffer[28] is the sign of power2
941 // _buffer[29] unused
942 // _buffer[30] unused
943 uint8_t sign0 = _register8(_buffer, registerStart, registerSize, JSY_194_REGISTER_ACTIVE_POWER_SIGNS, 0);
944 uint8_t sign1 = _register8(_buffer, registerStart, registerSize, JSY_194_REGISTER_ACTIVE_POWER_SIGNS, 1);
945
946 // frequency
947 float frequency = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_FREQUENCY) * 0.01f;
948
949 // channel 1
950 _data._metrics[0].frequency = frequency;
951 _data._metrics[0].voltage = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH1_VOLTAGE) * 0.0001f;
952 _data._metrics[0].current = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH1_CURRENT) * 0.0001f;
953 _data._metrics[0].activePower = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH1_ACTIVE_POWER) * (sign0 ? -0.0001f : 0.0001f);
954 _data._metrics[0].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH1_ACTIVE_ENERGY_IMPORTED) * 0.1f;
955 _data._metrics[0].powerFactor = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH1_POWER_FACTOR) * 0.001f;
956 _data._metrics[0].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH1_ACTIVE_ENERGY_RETURNED) * 0.1f;
957
958 // channel 2
959 _data._metrics[1].frequency = frequency;
960 _data._metrics[1].voltage = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH2_VOLTAGE) * 0.0001f;
961 _data._metrics[1].current = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH2_CURRENT) * 0.0001f;
962 _data._metrics[1].activePower = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH2_ACTIVE_POWER) * (sign1 ? -0.0001f : 0.0001f);
963 _data._metrics[1].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH2_ACTIVE_ENERGY_IMPORTED) * 0.1f;
964 _data._metrics[1].powerFactor = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH2_POWER_FACTOR) * 0.001f;
965 _data._metrics[1].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH2_ACTIVE_ENERGY_RETURNED) * 0.1f;
966
967 // calculate remaining metrics
968 // S = P / PF
969 _data._metrics[0].apparentPower = _data._metrics[0].powerFactor == 0 ? 0 : std::abs(_data._metrics[0].activePower / _data._metrics[0].powerFactor);
970 _data._metrics[1].apparentPower = _data._metrics[1].powerFactor == 0 ? 0 : std::abs(_data._metrics[1].activePower / _data._metrics[1].powerFactor);
971 // Q = std::sqrt(S^2 - P^2)
972 _data._metrics[0].reactivePower = std::sqrt(_data._metrics[0].apparentPower * _data._metrics[0].apparentPower - _data._metrics[0].activePower * _data._metrics[0].activePower);
973 _data._metrics[1].reactivePower = std::sqrt(_data._metrics[1].apparentPower * _data._metrics[1].apparentPower - _data._metrics[1].activePower * _data._metrics[1].activePower);
974 // E = Ei + Er
975 _data._metrics[0].activeEnergy = _data._metrics[0].activeEnergyImported + _data._metrics[0].activeEnergyReturned;
976 _data._metrics[1].activeEnergy = _data._metrics[1].activeEnergyImported + _data._metrics[1].activeEnergyReturned;
977
978 // aggregate
979 _data.aggregate = _data._metrics[0];
980 _data.aggregate += _data._metrics[1];
981 _data.aggregate.frequency = frequency;
982 _data.aggregate.voltage = std::max(_data._metrics[0].voltage, _data._metrics[1].voltage);
983
984 break;
985 }
986
987 case MYCILA_JSY_MK_227:
988 case MYCILA_JSY_MK_229: {
989 // single channel
990 _data._metrics[0].voltage = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_VOLTAGE) * 0.0001f;
991 _data._metrics[0].current = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_CURRENT) * 0.0001f;
992 _data._metrics[0].activePower = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_ACTIVE_POWER) * (_register16(_buffer, registerStart, registerSize, JSY_22x_REGISTER_ACTIVE_POWER_SIGN) ? -0.0001f : 0.0001f);
993 _data._metrics[0].reactivePower = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_REACTIVE_POWER) * (_register16(_buffer, registerStart, registerSize, JSY_22x_REGISTER_REACTIVE_POWER_SIGN) ? -0.0001f : 0.0001f);
994 _data._metrics[0].apparentPower = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_APPARENT_POWER) * 0.0001f;
995 _data._metrics[0].powerFactor = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_POWER_FACTOR) * 0.001f;
996 _data._metrics[0].frequency = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_FREQUENCY) * 0.01f;
997 _data._metrics[0].activeEnergy = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_ACTIVE_ENERGY);
998 _data._metrics[0].reactiveEnergy = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_REACTIVE_ENERGY);
999 _data._metrics[0].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_ACTIVE_ENERGY_POSITIVE);
1000 _data._metrics[0].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_ACTIVE_ENERGY_NEGATIVE);
1001 _data._metrics[0].reactiveEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_REACTIVE_ENERGY_POSITIVE);
1002 _data._metrics[0].reactiveEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_REACTIVE_ENERGY_NEGATIVE);
1003
1004 // aggregate
1005 _data.aggregate = _data._metrics[0];
1006
1007 break;
1008 }
1009
1010 case MYCILA_JSY_MK_333: {
1011 // signs
1012 // _buffer[203] unused
1013 // _buffer[204] bit 7: sign of total reactive power
1014 // _buffer[204] bit 6: sign of phase C reactive power
1015 // _buffer[204] bit 5: sign of phase B reactive power
1016 // _buffer[204] bit 4: sign of phase A reactive power
1017 // _buffer[204] bit 3: sign of total active power
1018 // _buffer[204] bit 2: sign of phase C active power
1019 // _buffer[204] bit 1: sign of phase B active power
1020 // _buffer[204] bit 0: sign of phase A active power
1021 uint8_t sign7 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x80;
1022 uint8_t sign6 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x40;
1023 uint8_t sign5 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x20;
1024 uint8_t sign4 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x10;
1025 uint8_t sign3 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x08;
1026 uint8_t sign2 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x04;
1027 uint8_t sign1 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x02;
1028 uint8_t sign0 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x01;
1029
1030 // frequency
1031 float frequency = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_FREQUENCY) * 0.01f;
1032
1033 // phase A
1034 _data._metrics[0].frequency = frequency;
1035 _data._metrics[0].voltage = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_VOLTAGE) * 0.01f;
1036 _data._metrics[0].current = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_CURRENT) * 0.01f;
1037 _data._metrics[0].activePower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_ACTIVE_POWER) * (sign0 ? -1.0f : 1.0f);
1038 _data._metrics[0].reactivePower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_REACTIVE_POWER) * (sign4 ? -1.0f : 1.0f);
1039 _data._metrics[0].apparentPower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_APPARENT_POWER);
1040 _data._metrics[0].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_POWER_FACTOR) * 0.001f;
1041 _data._metrics[0].activeEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_ACTIVE_ENERGY) * 10;
1042 _data._metrics[0].reactiveEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_REACTIVE_ENERGY) * 10;
1043 _data._metrics[0].apparentEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_APPARENT_ENERGY) * 10;
1044 _data._metrics[0].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_ACTIVE_ENERGY_IMPORTED) * 10;
1045 _data._metrics[0].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_ACTIVE_ENERGY_RETURNED) * 10;
1046 _data._metrics[0].reactiveEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_REACTIVE_ENERGY_IMPORTED) * 10;
1047 _data._metrics[0].reactiveEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_REACTIVE_ENERGY_RETURNED) * 10;
1048 _data._metrics[0].phaseAngleU = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_PHASE_ANGLE_U) * 0.01f;
1049 _data._metrics[0].phaseAngleI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_PHASE_ANGLE_I) * 0.01f;
1050 _data._metrics[0].phaseAngleUI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_PHASE_ANGLE_UI) * 0.01f;
1051 _data._metrics[0].thdU = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_THD_U) * 0.01f;
1052 _data._metrics[0].thdI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_THD_I) * 0.01f;
1053
1054 // phase B
1055 _data._metrics[1].frequency = frequency;
1056 _data._metrics[1].voltage = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_VOLTAGE) * 0.01f;
1057 _data._metrics[1].current = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_CURRENT) * 0.01f;
1058 _data._metrics[1].activePower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_ACTIVE_POWER) * (sign1 ? -1.0f : 1.0f);
1059 _data._metrics[1].reactivePower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_REACTIVE_POWER) * (sign5 ? -1.0f : 1.0f);
1060 _data._metrics[1].apparentPower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_APPARENT_POWER);
1061 _data._metrics[1].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_POWER_FACTOR) * 0.001f;
1062 _data._metrics[1].activeEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_ACTIVE_ENERGY) * 0.01f;
1063 _data._metrics[1].reactiveEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_REACTIVE_ENERGY) * 10;
1064 _data._metrics[1].apparentEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_APPARENT_ENERGY) * 10;
1065 _data._metrics[1].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_ACTIVE_ENERGY_IMPORTED) * 10;
1066 _data._metrics[1].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_ACTIVE_ENERGY_RETURNED) * 10;
1067 _data._metrics[1].reactiveEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_REACTIVE_ENERGY_IMPORTED) * 10;
1068 _data._metrics[1].reactiveEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_REACTIVE_ENERGY_RETURNED) * 10;
1069 _data._metrics[1].phaseAngleU = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_PHASE_ANGLE_U) * 0.01f;
1070 _data._metrics[1].phaseAngleI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_PHASE_ANGLE_I) * 0.01f;
1071 _data._metrics[1].phaseAngleUI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_PHASE_ANGLE_UI) * 0.01f;
1072 _data._metrics[1].thdU = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_THD_U) * 0.01f;
1073 _data._metrics[1].thdI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_THD_I) * 0.01f;
1074
1075 // phase C
1076 _data._metrics[2].frequency = frequency;
1077 _data._metrics[2].voltage = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_VOLTAGE) * 0.01f;
1078 _data._metrics[2].current = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_CURRENT) * 0.01f;
1079 _data._metrics[2].activePower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_ACTIVE_POWER) * (sign2 ? -1.0f : 1.0f);
1080 _data._metrics[2].reactivePower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_REACTIVE_POWER) * (sign6 ? -1.0f : 1.0f);
1081 _data._metrics[2].apparentPower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_APPARENT_POWER);
1082 _data._metrics[2].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_POWER_FACTOR) * 0.001f;
1083 _data._metrics[2].activeEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_ACTIVE_ENERGY) * 10;
1084 _data._metrics[2].reactiveEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_REACTIVE_ENERGY) * 10;
1085 _data._metrics[2].apparentEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_APPARENT_ENERGY) * 10;
1086 _data._metrics[2].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_ACTIVE_ENERGY_IMPORTED) * 10;
1087 _data._metrics[2].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_ACTIVE_ENERGY_RETURNED) * 10;
1088 _data._metrics[2].reactiveEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_REACTIVE_ENERGY_IMPORTED) * 10;
1089 _data._metrics[2].reactiveEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_REACTIVE_ENERGY_RETURNED) * 10;
1090 _data._metrics[2].phaseAngleU = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_PHASE_ANGLE_U) * 0.01f;
1091 _data._metrics[2].phaseAngleI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_PHASE_ANGLE_I) * 0.01f;
1092 _data._metrics[2].phaseAngleUI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_PHASE_ANGLE_UI) * 0.01f;
1093 _data._metrics[2].thdU = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_THD_U) * 0.01f;
1094 _data._metrics[2].thdI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_THD_I) * 0.01f;
1095
1096 // aggregate
1097 _data.aggregate.frequency = frequency;
1098 _data.aggregate.activePower = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_ACTIVE_POWER) * (sign3 ? -1.0f : 1.0f);
1099 _data.aggregate.reactivePower = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_REACTIVE_POWER) * (sign7 ? -1.0f : 1.0f);
1100 _data.aggregate.apparentPower = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_APPARENT_POWER);
1101 _data.aggregate.powerFactor = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_POWER_FACTOR) * 0.001f;
1102 _data.aggregate.activeEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_ACTIVE_ENERGY) * 10;
1103 _data.aggregate.reactiveEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_REACTIVE_ENERGY) * 10;
1104 _data.aggregate.apparentEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_APPARENT_ENERGY) * 10;
1105 _data.aggregate.activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_ACTIVE_ENERGY_IMPORTED) * 10;
1106 _data.aggregate.activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_ACTIVE_ENERGY_RETURNED) * 10;
1107 _data.aggregate.reactiveEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_REACTIVE_ENERGY_IMPORTED) * 10;
1108 _data.aggregate.reactiveEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_REACTIVE_ENERGY_RETURNED) * 10;
1109 _data.aggregate.current = _data._metrics[0].current + _data._metrics[1].current + _data._metrics[2].current;
1110 _data.aggregate.voltage = _data.aggregate.current == 0 ? NAN : _data.aggregate.apparentPower / _data.aggregate.current;
1111
1112 break;
1113 }
1114
1115 default:
1116 break;
1117 }
1118
1119 _time = millis();
1120
1121 if (_callback) {
1122 _callback(EventType::EVT_READ, _data);
1123 }
1124
1125 return true;
1126}
1127
1129// readModel
1131
1132uint16_t Mycila::JSY::readModel(const uint8_t address) {
1133 if (!_enabled)
1134 return MYCILA_JSY_MK_UNKNOWN;
1135
1136 LOGD(TAG, "readModel(0x%02X)", address);
1137
1138 std::lock_guard<std::mutex> lock(_mutex);
1139
1140#ifdef MYCILA_JSY_DEBUG
1141 Serial.printf("[JSY] readModel(0x%02X)\n", address);
1142#endif
1143
1144 memcpy(_buffer, JSY_REQUEST_READ_MODEL, JSY_REQUEST_READ_MODEL_LEN);
1145 _send(address, JSY_REQUEST_READ_MODEL_LEN);
1146 ReadResult result = _timedRead(address, JSY_RESPONSE_SIZE_READ_MODEL, _baudRate);
1147
1148 if (result != ReadResult::READ_SUCCESS) {
1149 return MYCILA_JSY_MK_UNKNOWN;
1150 }
1151
1152 return (_buffer[JSY_RESPONSE_DATA] << 8) + _buffer[JSY_RESPONSE_DATA + 1];
1153}
1154
1156// readMode / setMode
1158
1159Mycila::JSY::Mode Mycila::JSY::_readMode(const uint8_t address, const uint16_t model) {
1160 if (!_enabled)
1161 return Mode::UNKNOWN;
1162
1163 switch (model) {
1164 case MYCILA_JSY_MK_163:
1165 case MYCILA_JSY_MK_193:
1166 case MYCILA_JSY_MK_194:
1167 case MYCILA_JSY_MK_333:
1168 return Mode::AC;
1169 case MYCILA_JSY_MK_227:
1170 case MYCILA_JSY_MK_229:
1171 return Mode::DC;
1172 case MYCILA_JSY_MK_1031:
1173 break; // reads the mode just after
1174 default:
1175 return Mode::UNKNOWN;
1176 }
1177
1178 LOGD(TAG, "readMode(0x%02X)", address);
1179
1180 std::lock_guard<std::mutex> lock(_mutex);
1181
1182#ifdef MYCILA_JSY_DEBUG
1183 Serial.printf("[JSY] readMode(0x%02X)\n", address);
1184#endif
1185
1186 memcpy(_buffer, JSY_REQUEST_READ_MODE, JSY_REQUEST_READ_MODE_LEN);
1187 _send(address, JSY_REQUEST_READ_MODE_LEN);
1188 ReadResult result = _timedRead(address, JSY_RESPONSE_SIZE_READ_MODE, _baudRate);
1189
1190 if (result != ReadResult::READ_SUCCESS) {
1191 return Mode::UNKNOWN;
1192 }
1193
1194 switch (_buffer[JSY_RESPONSE_DATA]) {
1195 case JSY_MODE_AC:
1196 return Mode::AC;
1197 case JSY_MODE_DC:
1198 return Mode::DC;
1199 default:
1200 return Mode::UNKNOWN;
1201 }
1202}
1203
1204bool Mycila::JSY::_setMode(const uint8_t address, const uint16_t model, const Mode mode) {
1205 if (!_enabled)
1206 return false;
1207
1208 if (mode == Mode::UNKNOWN)
1209 return false;
1210
1211 switch (model) {
1212 case MYCILA_JSY_MK_163:
1213 case MYCILA_JSY_MK_193:
1214 case MYCILA_JSY_MK_194:
1215 case MYCILA_JSY_MK_333:
1216 return mode == Mode::AC;
1217 case MYCILA_JSY_MK_227:
1218 case MYCILA_JSY_MK_229:
1219 return mode == Mode::DC;
1220 case MYCILA_JSY_MK_1031:
1221 break; // sets the mode just after
1222 default:
1223 return false;
1224 }
1225
1226 LOGD(TAG, "setMode(0x%02X) mode=%s", address, mode == Mode::AC ? "AC" : "DC");
1227
1228 std::lock_guard<std::mutex> lock(_mutex);
1229
1230#ifdef MYCILA_JSY_DEBUG
1231 Serial.printf("[JSY] setMode(0x%02X, %s)\n", address, mode == Mode::AC ? "AC" : "DC");
1232#endif
1233
1234 memcpy(_buffer, JSY_REQUEST_SWITCH_MODE, JSY_REQUEST_SWITCH_MODE_LEN);
1235
1236 _buffer[JSY_REQUEST_SET_MODE] = mode == Mode::AC ? JSY_MODE_AC : JSY_MODE_DC;
1237
1238 _send(address, JSY_REQUEST_SWITCH_MODE_LEN);
1239 ReadResult result = _timedRead(address, JSY_RESPONSE_SIZE_SWITCH_MODE, _baudRate);
1240
1241 return result == ReadResult::READ_SUCCESS;
1242}
1243
1245// resetEnergy
1247
1248 bool Mycila::JSY::resetEnergy(const uint8_t address) {
1249 if (!_enabled)
1250 return false;
1251
1252 LOGD(TAG, "resetEnergy(0x%02X)", address);
1253
1254 std::lock_guard<std::mutex> lock(_mutex);
1255
1256#ifdef MYCILA_JSY_DEBUG
1257 Serial.printf("[JSY] resetEnergy(0x%02X)\n", address);
1258#endif
1259
1260 memcpy(_buffer, JSY_REQUEST_RESET_ENERGY, JSY_REQUEST_RESET_ENERGY_LEN);
1261 _send(address, JSY_REQUEST_RESET_ENERGY_LEN);
1262 ReadResult result = _timedRead(address, JSY_RESPONSE_SIZE_RESET_ENERGY, _baudRate);
1263
1264 return result == ReadResult::READ_SUCCESS;
1265}
1266
1268// settings
1270
1271Mycila::JSY::BaudRate Mycila::JSY::getMinAvailableBaudRate() const {
1272 if (!_enabled)
1273 return BaudRate::UNKNOWN;
1274 return getMinAvailableBaudRate(_model);
1275}
1276
1277Mycila::JSY::BaudRate Mycila::JSY::getMinAvailableBaudRate(uint16_t model) {
1278 switch (model) {
1279 case MYCILA_JSY_MK_163:
1280 case MYCILA_JSY_MK_193:
1281 case MYCILA_JSY_MK_194:
1282 case MYCILA_JSY_MK_227:
1283 case MYCILA_JSY_MK_229:
1284 return BaudRate::BAUD_1200;
1285 return BaudRate::BAUD_1200;
1286 case MYCILA_JSY_MK_1031:
1287 case MYCILA_JSY_MK_333:
1288 return BaudRate::BAUD_4800;
1289 default:
1290 return BaudRate::UNKNOWN;
1291 }
1292}
1293
1294Mycila::JSY::BaudRate Mycila::JSY::getMaxAvailableBaudRate() const {
1295 if (!_enabled)
1296 return BaudRate::UNKNOWN;
1297 return getMaxAvailableBaudRate(_model);
1298}
1299
1300Mycila::JSY::BaudRate Mycila::JSY::getMaxAvailableBaudRate(uint16_t model) {
1301 switch (model) {
1302 case MYCILA_JSY_MK_163:
1303 case MYCILA_JSY_MK_227:
1304 case MYCILA_JSY_MK_229:
1305 return BaudRate::BAUD_9600;
1306 case MYCILA_JSY_MK_1031:
1307 case MYCILA_JSY_MK_333:
1308 return BaudRate::BAUD_19200;
1309 case MYCILA_JSY_MK_193:
1310 case MYCILA_JSY_MK_194:
1311 return BaudRate::BAUD_38400;
1312 default:
1313 return BaudRate::UNKNOWN;
1314 }
1315}
1316
1317 bool Mycila::JSY::isBaudRateSupported(Mycila::JSY::BaudRate baudRate) const {
1318 if (!_enabled)
1319 return false;
1320 return isBaudRateSupported(_model, baudRate);
1321}
1322
1323bool Mycila::JSY::isBaudRateSupported(uint16_t model, Mycila::JSY::BaudRate baudRate) {
1324 return getMinAvailableBaudRate(model) <= baudRate && baudRate <= getMaxAvailableBaudRate(model);
1325}
1326
1327 bool Mycila::JSY::setBaudRate(const uint8_t address, const BaudRate baudRate) {
1328 return _set(address, address ? address : (_lastAddress ? _lastAddress : MYCILA_JSY_ADDRESS_DEFAULT), baudRate);
1329}
1330
1331 bool Mycila::JSY::setDeviceAddress(const uint8_t address, const uint8_t newAddress) {
1332 return _set(address, newAddress, _baudRate);
1333}
1334
1335bool Mycila::JSY::_set(const uint8_t address, const uint8_t newAddress, const BaudRate newBaudRate) {
1336 if (!_enabled)
1337 return false;
1338
1339 if (newBaudRate == BaudRate::UNKNOWN)
1340 return false;
1341
1342 if (newAddress == MYCILA_JSY_ADDRESS_UNKNOWN)
1343 return false;
1344
1345 LOGD(TAG, "set(0x%02X) address=0x%02X, bauds=%" PRIu32, address, newAddress, newBaudRate);
1346
1347 std::lock_guard<std::mutex> lock(_mutex);
1348
1349#ifdef MYCILA_JSY_DEBUG
1350 Serial.printf("[JSY] _set(0x%02X)\n", address);
1351#endif
1352
1353 memcpy(_buffer, JSY_REQUEST_SET_COM, JSY_REQUEST_SET_COM_LEN);
1354
1355 // set address
1356 _buffer[JSY_REQUEST_SET_ADDRESS] = newAddress;
1357
1358 // set baud rate ID
1359 switch (newBaudRate) {
1360 case BaudRate::BAUD_1200:
1361 _buffer[JSY_REQUEST_SET_BAUDS] = 0x03;
1362 break;
1363 case BaudRate::BAUD_2400:
1364 _buffer[JSY_REQUEST_SET_BAUDS] = 0x04;
1365 break;
1366 case BaudRate::BAUD_4800:
1367 _buffer[JSY_REQUEST_SET_BAUDS] = 0x05;
1368 break;
1369 case BaudRate::BAUD_9600:
1370 _buffer[JSY_REQUEST_SET_BAUDS] = 0x06;
1371 break;
1372 case BaudRate::BAUD_19200:
1373 _buffer[JSY_REQUEST_SET_BAUDS] = 0x07;
1374 break;
1375 case BaudRate::BAUD_38400:
1376 _buffer[JSY_REQUEST_SET_BAUDS] = 0x08;
1377 break;
1378 default:
1379 assert(false);
1380 break;
1381 }
1382
1383 _send(address, JSY_REQUEST_SET_COM_LEN);
1384 ReadResult result = _timedRead(address, JSY_RESPONSE_SIZE_SET_COM, _baudRate);
1385
1386 // unexpected error ?
1387 if (result != ReadResult::READ_SUCCESS && result != ReadResult::READ_ERROR_ADDRESS) {
1388 return false;
1389 }
1390
1391 // response from unexpected address ?
1392 if (result == ReadResult::READ_ERROR_ADDRESS && _lastAddress != newAddress) {
1393 return false;
1394 }
1395
1396 _openSerial(newBaudRate);
1397
1398 bool success = false;
1399 for (int i = 0; i < MYCILA_JSY_RETRY_COUNT; i++) {
1400 if (_canRead(address, newBaudRate)) {
1401 success = true;
1402 break;
1403 }
1404 }
1405
1406 if (success) {
1407 // update baud rate
1408 _baudRate = newBaudRate;
1409
1410 // update destination address if needed
1411 if (_destinationAddress != MYCILA_JSY_ADDRESS_BROADCAST && _destinationAddress == address) {
1412 _destinationAddress = newAddress;
1413 }
1414
1415 } else {
1416 LOGE(TAG, "Unable to read JSY @ 0x%02X at speed: %" PRIu32, address, newBaudRate);
1417 if (_baudRate != BaudRate::UNKNOWN) {
1418 _openSerial(_baudRate);
1419 }
1420 }
1421
1422 return success;
1423}
1424
1426// toJson
1428
1429#ifdef MYCILA_JSON_SUPPORT
1430void Mycila::JSY::toJson(const JsonObject& root) const {
1431 root["enabled"] = _enabled;
1432 root["time"] = _time;
1433 root["speed"] = _baudRate;
1434 _data.toJson(root);
1435}
1436#endif
1437
1439// I/O
1441
1442bool Mycila::JSY::_canRead(const uint8_t address, BaudRate baudRate) {
1443#ifdef MYCILA_JSY_DEBUG
1444 Serial.printf("[JSY] _canRead(0x%02X)\n", address);
1445#endif
1446 memcpy(_buffer, JSY_REQUEST_READ_MODEL, JSY_REQUEST_READ_MODEL_LEN);
1447 _send(address, JSY_REQUEST_READ_MODEL_LEN);
1448 return _timedRead(address, JSY_RESPONSE_SIZE_READ_MODEL, baudRate) == ReadResult::READ_SUCCESS;
1449}
1450
1451Mycila::JSY::ReadResult Mycila::JSY::_timedRead(const uint8_t expectedAddress, const size_t expectedLen, const BaudRate baudRate) {
1452 size_t count = 0;
1453 while (count < expectedLen) {
1454 size_t read = _serial->readBytes(_buffer + count, expectedLen - count);
1455 if (read) {
1456 count += read;
1457 } else {
1458 break;
1459 }
1460 }
1461
1462#ifdef MYCILA_JSY_DEBUG
1463 Serial.printf("[JSY] timedRead(0x%02X) %d < ", expectedAddress, count);
1464 for (size_t i = 0; i < count; i++) {
1465 Serial.printf("0x%02X ", _buffer[i]);
1466 }
1467 Serial.println();
1468#endif
1469
1470 _drop();
1471
1472 // timeout ?
1473 if (count == 0) {
1474 LOGD(TAG, "timedRead(0x%02X) timeout", expectedAddress);
1475 return ReadResult::READ_TIMEOUT;
1476 }
1477
1478 // check length
1479 if (count != expectedLen) {
1480 LOGD(TAG, "timedRead(0x%02X) error: len %d != %d", expectedAddress, count, expectedLen);
1481 return ReadResult::READ_ERROR_COUNT;
1482 }
1483
1484 // CRC check
1485 uint16_t crc = _crc16(_buffer, expectedLen - 2);
1486 if (_buffer[expectedLen - 2] != LOBYTE(crc) || _buffer[expectedLen - 1] != HIBYTE(crc)) {
1487 LOGD(TAG, "timedRead(0x%02X) error: bad CRC 0x%02X 0x%02X != 0x%02X 0x%02X", expectedAddress, _buffer[expectedLen - 2], _buffer[expectedLen - 1], LOBYTE(crc), HIBYTE(crc));
1488 return ReadResult::READ_ERROR_CRC;
1489 }
1490
1491 _lastAddress = _buffer[JSY_RESPONSE_ADDRESS];
1492
1493 // address check
1494 if (expectedAddress != MYCILA_JSY_ADDRESS_BROADCAST && expectedAddress != _lastAddress) {
1495 LOGD(TAG, "timedRead(0x%02X) error: wrong device address 0x%02X", expectedAddress, _lastAddress);
1496 return ReadResult::READ_ERROR_ADDRESS;
1497 }
1498
1499 return ReadResult::READ_SUCCESS;
1500}
1501
1502void Mycila::JSY::_send(const uint8_t address, const size_t len) {
1503 // set destination address
1504 _buffer[JSY_REQUEST_ADDRESS] = address;
1505
1506 // crc16
1507 uint16_t crc = _crc16(_buffer, len - 2);
1508 _buffer[len - 2] = LOBYTE(crc);
1509 _buffer[len - 1] = HIBYTE(crc);
1510
1511#ifdef MYCILA_JSY_DEBUG
1512 Serial.printf("[JSY] send(0x%02X) %d > ", address, len);
1513 for (size_t i = 0; i < len; i++) {
1514 Serial.printf("0x%02X ", _buffer[i]);
1515 }
1516 Serial.println();
1517#endif
1518
1519 _serial->flush(false);
1520 _serial->write(_buffer, len);
1521}
1522
1523size_t Mycila::JSY::_drop() {
1524 size_t count = 0;
1525 if (_serial->available()) {
1526#ifdef MYCILA_JSY_DEBUG
1527 Serial.printf("[JSY] drop < ");
1528#endif
1529 while (_serial->available()) {
1530#ifdef MYCILA_JSY_DEBUG
1531 Serial.printf("0x%02X ", _serial->read());
1532#else
1533 _serial->read();
1534#endif
1535 count++;
1536 }
1537#ifdef MYCILA_JSY_DEBUG
1538 Serial.println();
1539#endif
1540 }
1541 return count;
1542}
1543
1544void Mycila::JSY::_openSerial(BaudRate baudRate) {
1545 LOGD(TAG, "openSerial(%" PRIu32 ")", baudRate);
1546 _serial->begin(baudRate, SERIAL_8N1, _pinRX, _pinTX);
1547 _serial->setTimeout(MYCILA_JSY_READ_TIMEOUT_MS);
1548 while (!_serial)
1549 yield();
1550 while (!_serial->availableForWrite())
1551 yield();
1552 _drop();
1553 _serial->flush(false);
1554}
1555
1556Mycila::JSY::BaudRate Mycila::JSY::_detectBauds(const uint8_t address) {
1557 for (int i = 0; i < AUTO_DETECT_BAUD_RATES_COUNT * 2; i++) {
1558 BaudRate baudRate = AUTO_DETECT_BAUD_RATES[i % AUTO_DETECT_BAUD_RATES_COUNT];
1559 LOGD(TAG, "find(0x%02X) %" PRIu32 " bauds", address, baudRate);
1560 _openSerial(baudRate);
1561 for (int j = 0; j < MYCILA_JSY_RETRY_COUNT; j++) {
1562 if (_canRead(address, baudRate)) {
1563 return baudRate;
1564 }
1565 }
1566 }
1567 return BaudRate::UNKNOWN;
1568}
1569
1571// static
1573
1574// For CRC: https://crccalc.com
1575// Select CRC-16/MODBUS
1576inline uint16_t Mycila::JSY::_crc16(const uint8_t* data, size_t len) {
1577 uint16_t crc = 0xFFFF;
1578 while (len--) {
1579 uint8_t temp = *(data++) ^ LOBYTE(crc);
1580 crc = (crc >> 8) ^ pgm_read_word_near(CRCTable + temp);
1581 }
1582 return crc;
1583}
1584
1585uint8_t Mycila::JSY::_register8(const uint8_t* buffer, const uint16_t registerStart, const uint16_t registerSize, const uint16_t registerAddress, uint8_t index) {
1586 return buffer[JSY_RESPONSE_DATA + (registerAddress - registerStart) * registerSize + index];
1587}
1588
1589uint16_t Mycila::JSY::_register16(const uint8_t* buffer, const uint16_t registerStart, const uint16_t registerSize, const uint16_t registerAddress) {
1590 const size_t start = JSY_RESPONSE_DATA + (registerAddress - registerStart) * registerSize;
1591 return (buffer[start] << 8) + buffer[start + 1];
1592}
1593
1594uint32_t Mycila::JSY::_register32(const uint8_t* buffer, const uint16_t registerStart, const uint16_t registerSize, const uint16_t registerAddress) {
1595 const size_t start = JSY_RESPONSE_DATA + (registerAddress - registerStart) * registerSize;
1596 return (buffer[start] << 24) +
1597 (buffer[start + 1] << 16) +
1598 (buffer[start + 2] << 8) +
1599 (buffer[start + 3]);
1600}
1601
1602 const char* Mycila::JSY::getModelName(uint16_t model) {
1603 switch (model) {
1604 case MYCILA_JSY_MK_1031:
1605 return MYCILA_JSY_MK_1031_NAME;
1606 case MYCILA_JSY_MK_163:
1607 return MYCILA_JSY_MK_163_NAME;
1608 case MYCILA_JSY_MK_193:
1609 return MYCILA_JSY_MK_193_NAME;
1610 case MYCILA_JSY_MK_194:
1611 return MYCILA_JSY_MK_194_NAME;
1612 case MYCILA_JSY_MK_227:
1613 return MYCILA_JSY_MK_227_NAME;
1614 case MYCILA_JSY_MK_229:
1615 return MYCILA_JSY_MK_229_NAME;
1616 case MYCILA_JSY_MK_333:
1617 return MYCILA_JSY_MK_333_NAME;
1618 default:
1619 return emptyString.c_str();
1620 }
1621}
1622
1623void Mycila::JSY::_jsyTask(void* params) {
1624 JSY* jsy = reinterpret_cast<JSY*>(params);
1625 while (jsy->_enabled) {
1626 if (jsy->read()) {
1627 if (jsy->_pause > 0) {
1628 delay(jsy->_pause);
1629 } else {
1630 yield();
1631 }
1632 } else if (jsy->_pause > 0) {
1633 delay(jsy->_pause);
1634 } else {
1635 delay(10);
1636 }
1637 }
1638 jsy->_taskHandle = NULL;
1639 vTaskDelete(NULL);
1640}
Mycila::JSY
Definition MycilaJSY.h:85
Mycila::JSY::read
bool read()
Read the JSY values.
Definition MycilaJSY.h:473
Mycila::JSY::begin
void begin(HardwareSerial &serial, int8_t rxPin, int8_t txPin, bool async, uint8_t core=MYCILA_JSY_ASYNC_CORE, uint32_t stackSize=MYCILA_JSY_ASYNC_STACK_SIZE, uint32_t pause=MYCILA_JSY_ASYNC_READ_PAUSE_MS)
Initialize the JSY with the given RX and TX pins.
Definition MycilaJSY.h:348
Mycila::JSY::setDeviceAddress
bool setDeviceAddress(uint8_t newAddress)
Set a new address for a device.
Definition MycilaJSY.h:394
Mycila::JSY::resetEnergy
bool resetEnergy()
Reset the energy counters of the JSY.
Definition MycilaJSY.h:488
Mycila::JSY::setBaudRate
bool setBaudRate(BaudRate baudRate)
Change the baud rate of the JSY.
Definition MycilaJSY.h:507
Mycila::JSY::getMaxAvailableBaudRate
BaudRate getMaxAvailableBaudRate() const
Get the maximum available baud rate supported by the current JSY model connected.
Definition MycilaJSY.cpp:1294
Mycila::JSY::isBaudRateSupported
bool isBaudRateSupported(BaudRate baudRate) const
Check if a baud rate is supported by the current JSY model connected.
Definition MycilaJSY.cpp:1317
Mycila::JSY::end
void end()
Ends the JSY communication.
Definition MycilaJSY.cpp:732
Mycila::JSY::readModel
uint16_t readModel()
Reads the JSY model.
Definition MycilaJSY.h:411