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
643void 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
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 std::lock_guard<std::mutex> lock(_mutex);
741 LOGD(TAG, "Closing Serial for JSY @ 0x%02X", _destinationAddress);
742 _serial->end();
743 _serial = nullptr;
744 _baudRate = BaudRate::UNKNOWN;
745 _lastAddress = MYCILA_JSY_ADDRESS_UNKNOWN;
746 _model = MYCILA_JSY_MK_UNKNOWN;
747 _data.clear();
748 }
749}
750
752// read
754
755bool Mycila::JSY::_read(const uint8_t address, uint16_t model) {
756 if (!_enabled)
757 return false;
758
759 std::lock_guard<std::mutex> lock(_mutex);
760
761#ifdef MYCILA_JSY_DEBUG
762 Serial.printf("[JSY] read(0x%02X)\n", address);
763#endif
764
765 memcpy(_buffer, JSY_REQUEST_READ_REGISTERS, JSY_REQUEST_READ_REGISTERS_LEN);
766
767 // fill the request with the registers to read
768 // this depends on the model
769 uint16_t registerStart = 0;
770 uint16_t registerCount = 0;
771 uint8_t registerSize = 0;
772
773 switch (model) {
774 case MYCILA_JSY_MK_1031:
775 registerSize = JSY_1031_REGISTER_LEN;
776 registerStart = JSY_1031_REGISTER_START;
777 registerCount = JSY_1031_REGISTER_COUNT;
778 break;
779
780 case MYCILA_JSY_MK_163:
781 registerSize = JSY_163_REGISTER_LEN;
782 registerStart = JSY_163_REGISTER_START;
783 registerCount = JSY_163_REGISTER_COUNT;
784 break;
785
786 case MYCILA_JSY_MK_193:
787 registerSize = JSY_193_REGISTER_LEN;
788 registerStart = JSY_193_REGISTER_START;
789 registerCount = JSY_193_REGISTER_COUNT;
790 break;
791
792 case MYCILA_JSY_MK_194:
793 registerSize = JSY_194_REGISTER_LEN;
794 registerStart = JSY_194_REGISTER_START;
795 registerCount = JSY_194_REGISTER_COUNT;
796 break;
797
798 case MYCILA_JSY_MK_227:
799 case MYCILA_JSY_MK_229:
800 registerSize = JSY_22x_REGISTER_LEN;
801 registerStart = JSY_22x_REGISTER_START;
802 registerCount = JSY_22x_REGISTER_COUNT;
803 break;
804
805 case MYCILA_JSY_MK_333:
806 registerSize = JSY_333_REGISTER_LEN;
807 registerStart = JSY_333_REGISTER_START;
808 registerCount = JSY_333_REGISTER_COUNT;
809 break;
810
811 default:
812 break;
813 }
814
815 _buffer[JSY_REQUEST_READ_REGISTER_ADDR_HIGH] = HIBYTE(registerStart);
816 _buffer[JSY_REQUEST_READ_REGISTER_ADDR_LOW] = LOBYTE(registerStart);
817 _buffer[JSY_REQUEST_READ_REGISTER_COUNT_HIGH] = HIBYTE(registerCount);
818 _buffer[JSY_REQUEST_READ_REGISTER_COUNT_LOW] = LOBYTE(registerCount);
819
820 _send(address, JSY_REQUEST_READ_REGISTERS_LEN);
821 ReadResult result = _timedRead(address, JSY_RESPONSE_SIZE_READ + registerCount * registerSize, _baudRate);
822
823 if (result == ReadResult::READ_TIMEOUT) {
824 // reset live values in case of read timeout
825 _data.clear();
826 if (_callback) {
827 _callback(EventType::EVT_READ_TIMEOUT, _data);
828 }
829 return false;
830 }
831
832 if (result == ReadResult::READ_ERROR_COUNT || result == ReadResult::READ_ERROR_CRC) {
833 // reset live values in case of read failure
834 _data.clear();
835 if (_callback) {
836 _callback(EventType::EVT_READ_ERROR, _data);
837 }
838 return false;
839 }
840
841 if (result == ReadResult::READ_ERROR_ADDRESS) {
842 // we have set a destination address, but we read another device
843 if (_callback) {
844 _callback(EventType::EVT_READ_ERROR, _data);
845 }
846 return false;
847 }
848
849 assert(result == ReadResult::READ_SUCCESS);
850
851 _data.address = _buffer[JSY_RESPONSE_ADDRESS];
852 _data.model = model;
853
854 switch (model) {
855 case MYCILA_JSY_MK_1031: {
856 // single channel
857 _data._metrics[0].frequency = _register16(_buffer, registerStart, registerSize, JSY_1031_REGISTER_FREQUENCY) * 0.01f;
858 _data._metrics[0].voltage = _register16(_buffer, registerStart, registerSize, JSY_1031_REGISTER_VOLTAGE) * 0.01f;
859 _data._metrics[0].current = _register32(_buffer, registerStart, registerSize, JSY_1031_REGISTER_CURRENT) * 0.0001f;
860 _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
861 _data._metrics[0].activeEnergy = _register32(_buffer, registerStart, registerSize, JSY_1031_REGISTER_ACTIVE_ENERGY) * 10;
862 _data._metrics[0].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_1031_REGISTER_POWER_FACTOR) * 0.001f;
863 _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
864 _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
865
866 // aggregate
867 _data.aggregate = _data._metrics[0];
868
869 break;
870 }
871
872 case MYCILA_JSY_MK_163: {
873 // signs
874 // _buffer[19] unused
875 // _buffer[20] is the sign of power
876 uint8_t sign = _register8(_buffer, registerStart, registerSize, JSY_163_REGISTER_ACTIVE_POWER_SIGN, 1);
877
878 // single channel
879 _data._metrics[0].frequency = _register16(_buffer, registerStart, registerSize, JSY_163_REGISTER_FREQUENCY) * 0.01f;
880 _data._metrics[0].voltage = _register16(_buffer, registerStart, registerSize, JSY_163_REGISTER_VOLTAGE) * 0.01f;
881 _data._metrics[0].current = _register16(_buffer, registerStart, registerSize, JSY_163_REGISTER_CURRENT) * 0.01f;
882 _data._metrics[0].activePower = _register16(_buffer, registerStart, registerSize, JSY_163_REGISTER_ACTIVE_POWER) * (sign ? -1.0f : 1.0f);
883 _data._metrics[0].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_163_REGISTER_ACTIVE_ENERGY_IMPORTED) * 5.0f / 16.0f;
884 _data._metrics[0].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_163_REGISTER_POWER_FACTOR) * 0.001f;
885 _data._metrics[0].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_163_REGISTER_ACTIVE_ENERGY_RETURNED) * 5.0f / 16.0f;
886
887 // calculate remaining metrics
888 // S = P / PF
889 _data._metrics[0].apparentPower = _data._metrics[0].powerFactor == 0 ? 0 : std::abs(_data._metrics[0].activePower / _data._metrics[0].powerFactor);
890 // Q = std::sqrt(S^2 - P^2)
891 _data._metrics[0].reactivePower = std::sqrt(_data._metrics[0].apparentPower * _data._metrics[0].apparentPower - _data._metrics[0].activePower * _data._metrics[0].activePower);
892 // E = Ei + Er
893 _data._metrics[0].activeEnergy = _data._metrics[0].activeEnergyImported + _data._metrics[0].activeEnergyReturned;
894
895 // aggregate
896 _data.aggregate = _data._metrics[0];
897
898 break;
899 }
900
901 case MYCILA_JSY_MK_193: {
902 // channel 1
903 _data._metrics[0].voltage = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_VOLTAGE) * 0.01f;
904 _data._metrics[0].current = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_CURRENT) * 0.01f;
905 _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);
906 _data._metrics[0].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_ACTIVE_ENERGY_POSITIVE) * 10;
907 _data._metrics[0].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_ACTIVE_ENERGY_NEGATIVE) * 10;
908 _data._metrics[0].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_POWER_FACTOR) * 0.001f;
909 _data._metrics[0].frequency = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_FREQUENCY) * 0.01f;
910
911 // channel 2
912 _data._metrics[1].voltage = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_VOLTAGE) * 0.01f;
913 _data._metrics[1].current = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_CURRENT) * 0.01f;
914 _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);
915 _data._metrics[1].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_ACTIVE_ENERGY_POSITIVE) * 10;
916 _data._metrics[1].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_ACTIVE_ENERGY_NEGATIVE) * 10;
917 _data._metrics[1].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_POWER_FACTOR) * 0.001f;
918 _data._metrics[1].frequency = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_FREQUENCY) * 0.01f;
919
920 // calculate remaining metrics
921 // S = P / PF
922 _data._metrics[0].apparentPower = _data._metrics[0].powerFactor == 0 ? 0 : std::abs(_data._metrics[0].activePower / _data._metrics[0].powerFactor);
923 _data._metrics[1].apparentPower = _data._metrics[1].powerFactor == 0 ? 0 : std::abs(_data._metrics[1].activePower / _data._metrics[1].powerFactor);
924 // Q = std::sqrt(S^2 - P^2)
925 _data._metrics[0].reactivePower = std::sqrt(_data._metrics[0].apparentPower * _data._metrics[0].apparentPower - _data._metrics[0].activePower * _data._metrics[0].activePower);
926 _data._metrics[1].reactivePower = std::sqrt(_data._metrics[1].apparentPower * _data._metrics[1].apparentPower - _data._metrics[1].activePower * _data._metrics[1].activePower);
927 // E = Ei + Er
928 _data._metrics[0].activeEnergy = _data._metrics[0].activeEnergyImported + _data._metrics[0].activeEnergyReturned;
929 _data._metrics[1].activeEnergy = _data._metrics[1].activeEnergyImported + _data._metrics[1].activeEnergyReturned;
930
931 // aggregate
932 _data.aggregate = _data._metrics[0];
933 _data.aggregate += _data._metrics[1];
934 _data.aggregate.voltage = std::max(_data._metrics[0].voltage, _data._metrics[1].voltage);
935 _data.aggregate.frequency = std::max(_data._metrics[0].frequency, _data._metrics[1].frequency);
936
937 break;
938 }
939
940 case MYCILA_JSY_MK_194: {
941 // signs
942 // _buffer[27] is the sign of power1
943 // _buffer[28] is the sign of power2
944 // _buffer[29] unused
945 // _buffer[30] unused
946 uint8_t sign0 = _register8(_buffer, registerStart, registerSize, JSY_194_REGISTER_ACTIVE_POWER_SIGNS, 0);
947 uint8_t sign1 = _register8(_buffer, registerStart, registerSize, JSY_194_REGISTER_ACTIVE_POWER_SIGNS, 1);
948
949 // frequency
950 float frequency = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_FREQUENCY) * 0.01f;
951
952 // channel 1
953 _data._metrics[0].frequency = frequency;
954 _data._metrics[0].voltage = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH1_VOLTAGE) * 0.0001f;
955 _data._metrics[0].current = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH1_CURRENT) * 0.0001f;
956 _data._metrics[0].activePower = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH1_ACTIVE_POWER) * (sign0 ? -0.0001f : 0.0001f);
957 _data._metrics[0].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH1_ACTIVE_ENERGY_IMPORTED) * 0.1f;
958 _data._metrics[0].powerFactor = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH1_POWER_FACTOR) * 0.001f;
959 _data._metrics[0].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH1_ACTIVE_ENERGY_RETURNED) * 0.1f;
960
961 // channel 2
962 _data._metrics[1].frequency = frequency;
963 _data._metrics[1].voltage = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH2_VOLTAGE) * 0.0001f;
964 _data._metrics[1].current = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH2_CURRENT) * 0.0001f;
965 _data._metrics[1].activePower = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH2_ACTIVE_POWER) * (sign1 ? -0.0001f : 0.0001f);
966 _data._metrics[1].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH2_ACTIVE_ENERGY_IMPORTED) * 0.1f;
967 _data._metrics[1].powerFactor = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH2_POWER_FACTOR) * 0.001f;
968 _data._metrics[1].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH2_ACTIVE_ENERGY_RETURNED) * 0.1f;
969
970 // calculate remaining metrics
971 // S = P / PF
972 _data._metrics[0].apparentPower = _data._metrics[0].powerFactor == 0 ? 0 : std::abs(_data._metrics[0].activePower / _data._metrics[0].powerFactor);
973 _data._metrics[1].apparentPower = _data._metrics[1].powerFactor == 0 ? 0 : std::abs(_data._metrics[1].activePower / _data._metrics[1].powerFactor);
974 // Q = std::sqrt(S^2 - P^2)
975 _data._metrics[0].reactivePower = std::sqrt(_data._metrics[0].apparentPower * _data._metrics[0].apparentPower - _data._metrics[0].activePower * _data._metrics[0].activePower);
976 _data._metrics[1].reactivePower = std::sqrt(_data._metrics[1].apparentPower * _data._metrics[1].apparentPower - _data._metrics[1].activePower * _data._metrics[1].activePower);
977 // E = Ei + Er
978 _data._metrics[0].activeEnergy = _data._metrics[0].activeEnergyImported + _data._metrics[0].activeEnergyReturned;
979 _data._metrics[1].activeEnergy = _data._metrics[1].activeEnergyImported + _data._metrics[1].activeEnergyReturned;
980
981 // aggregate
982 _data.aggregate = _data._metrics[0];
983 _data.aggregate += _data._metrics[1];
984 _data.aggregate.frequency = frequency;
985 _data.aggregate.voltage = std::max(_data._metrics[0].voltage, _data._metrics[1].voltage);
986
987 break;
988 }
989
990 case MYCILA_JSY_MK_227:
991 case MYCILA_JSY_MK_229: {
992 // single channel
993 _data._metrics[0].voltage = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_VOLTAGE) * 0.0001f;
994 _data._metrics[0].current = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_CURRENT) * 0.0001f;
995 _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);
996 _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);
997 _data._metrics[0].apparentPower = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_APPARENT_POWER) * 0.0001f;
998 _data._metrics[0].powerFactor = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_POWER_FACTOR) * 0.001f;
999 _data._metrics[0].frequency = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_FREQUENCY) * 0.01f;
1000 _data._metrics[0].activeEnergy = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_ACTIVE_ENERGY);
1001 _data._metrics[0].reactiveEnergy = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_REACTIVE_ENERGY);
1002 _data._metrics[0].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_ACTIVE_ENERGY_POSITIVE);
1003 _data._metrics[0].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_ACTIVE_ENERGY_NEGATIVE);
1004 _data._metrics[0].reactiveEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_REACTIVE_ENERGY_POSITIVE);
1005 _data._metrics[0].reactiveEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_REACTIVE_ENERGY_NEGATIVE);
1006
1007 // aggregate
1008 _data.aggregate = _data._metrics[0];
1009
1010 break;
1011 }
1012
1013 case MYCILA_JSY_MK_333: {
1014 // signs
1015 // _buffer[203] unused
1016 // _buffer[204] bit 7: sign of total reactive power
1017 // _buffer[204] bit 6: sign of phase C reactive power
1018 // _buffer[204] bit 5: sign of phase B reactive power
1019 // _buffer[204] bit 4: sign of phase A reactive power
1020 // _buffer[204] bit 3: sign of total active power
1021 // _buffer[204] bit 2: sign of phase C active power
1022 // _buffer[204] bit 1: sign of phase B active power
1023 // _buffer[204] bit 0: sign of phase A active power
1024 uint8_t sign7 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x80;
1025 uint8_t sign6 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x40;
1026 uint8_t sign5 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x20;
1027 uint8_t sign4 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x10;
1028 uint8_t sign3 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x08;
1029 uint8_t sign2 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x04;
1030 uint8_t sign1 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x02;
1031 uint8_t sign0 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x01;
1032
1033 // frequency
1034 float frequency = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_FREQUENCY) * 0.01f;
1035
1036 // phase A
1037 _data._metrics[0].frequency = frequency;
1038 _data._metrics[0].voltage = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_VOLTAGE) * 0.01f;
1039 _data._metrics[0].current = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_CURRENT) * 0.01f;
1040 _data._metrics[0].activePower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_ACTIVE_POWER) * (sign0 ? -1.0f : 1.0f);
1041 _data._metrics[0].reactivePower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_REACTIVE_POWER) * (sign4 ? -1.0f : 1.0f);
1042 _data._metrics[0].apparentPower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_APPARENT_POWER);
1043 _data._metrics[0].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_POWER_FACTOR) * 0.001f;
1044 _data._metrics[0].activeEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_ACTIVE_ENERGY) * 10;
1045 _data._metrics[0].reactiveEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_REACTIVE_ENERGY) * 10;
1046 _data._metrics[0].apparentEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_APPARENT_ENERGY) * 10;
1047 _data._metrics[0].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_ACTIVE_ENERGY_IMPORTED) * 10;
1048 _data._metrics[0].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_ACTIVE_ENERGY_RETURNED) * 10;
1049 _data._metrics[0].reactiveEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_REACTIVE_ENERGY_IMPORTED) * 10;
1050 _data._metrics[0].reactiveEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_REACTIVE_ENERGY_RETURNED) * 10;
1051 _data._metrics[0].phaseAngleU = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_PHASE_ANGLE_U) * 0.01f;
1052 _data._metrics[0].phaseAngleI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_PHASE_ANGLE_I) * 0.01f;
1053 _data._metrics[0].phaseAngleUI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_PHASE_ANGLE_UI) * 0.01f;
1054 _data._metrics[0].thdU = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_THD_U) * 0.01f;
1055 _data._metrics[0].thdI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_THD_I) * 0.01f;
1056
1057 // phase B
1058 _data._metrics[1].frequency = frequency;
1059 _data._metrics[1].voltage = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_VOLTAGE) * 0.01f;
1060 _data._metrics[1].current = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_CURRENT) * 0.01f;
1061 _data._metrics[1].activePower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_ACTIVE_POWER) * (sign1 ? -1.0f : 1.0f);
1062 _data._metrics[1].reactivePower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_REACTIVE_POWER) * (sign5 ? -1.0f : 1.0f);
1063 _data._metrics[1].apparentPower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_APPARENT_POWER);
1064 _data._metrics[1].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_POWER_FACTOR) * 0.001f;
1065 _data._metrics[1].activeEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_ACTIVE_ENERGY) * 0.01f;
1066 _data._metrics[1].reactiveEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_REACTIVE_ENERGY) * 10;
1067 _data._metrics[1].apparentEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_APPARENT_ENERGY) * 10;
1068 _data._metrics[1].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_ACTIVE_ENERGY_IMPORTED) * 10;
1069 _data._metrics[1].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_ACTIVE_ENERGY_RETURNED) * 10;
1070 _data._metrics[1].reactiveEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_REACTIVE_ENERGY_IMPORTED) * 10;
1071 _data._metrics[1].reactiveEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_REACTIVE_ENERGY_RETURNED) * 10;
1072 _data._metrics[1].phaseAngleU = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_PHASE_ANGLE_U) * 0.01f;
1073 _data._metrics[1].phaseAngleI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_PHASE_ANGLE_I) * 0.01f;
1074 _data._metrics[1].phaseAngleUI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_PHASE_ANGLE_UI) * 0.01f;
1075 _data._metrics[1].thdU = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_THD_U) * 0.01f;
1076 _data._metrics[1].thdI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_THD_I) * 0.01f;
1077
1078 // phase C
1079 _data._metrics[2].frequency = frequency;
1080 _data._metrics[2].voltage = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_VOLTAGE) * 0.01f;
1081 _data._metrics[2].current = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_CURRENT) * 0.01f;
1082 _data._metrics[2].activePower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_ACTIVE_POWER) * (sign2 ? -1.0f : 1.0f);
1083 _data._metrics[2].reactivePower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_REACTIVE_POWER) * (sign6 ? -1.0f : 1.0f);
1084 _data._metrics[2].apparentPower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_APPARENT_POWER);
1085 _data._metrics[2].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_POWER_FACTOR) * 0.001f;
1086 _data._metrics[2].activeEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_ACTIVE_ENERGY) * 10;
1087 _data._metrics[2].reactiveEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_REACTIVE_ENERGY) * 10;
1088 _data._metrics[2].apparentEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_APPARENT_ENERGY) * 10;
1089 _data._metrics[2].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_ACTIVE_ENERGY_IMPORTED) * 10;
1090 _data._metrics[2].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_ACTIVE_ENERGY_RETURNED) * 10;
1091 _data._metrics[2].reactiveEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_REACTIVE_ENERGY_IMPORTED) * 10;
1092 _data._metrics[2].reactiveEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_REACTIVE_ENERGY_RETURNED) * 10;
1093 _data._metrics[2].phaseAngleU = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_PHASE_ANGLE_U) * 0.01f;
1094 _data._metrics[2].phaseAngleI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_PHASE_ANGLE_I) * 0.01f;
1095 _data._metrics[2].phaseAngleUI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_PHASE_ANGLE_UI) * 0.01f;
1096 _data._metrics[2].thdU = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_THD_U) * 0.01f;
1097 _data._metrics[2].thdI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_THD_I) * 0.01f;
1098
1099 // aggregate
1100 _data.aggregate.frequency = frequency;
1101 _data.aggregate.activePower = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_ACTIVE_POWER) * (sign3 ? -1.0f : 1.0f);
1102 _data.aggregate.reactivePower = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_REACTIVE_POWER) * (sign7 ? -1.0f : 1.0f);
1103 _data.aggregate.apparentPower = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_APPARENT_POWER);
1104 _data.aggregate.powerFactor = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_POWER_FACTOR) * 0.001f;
1105 _data.aggregate.activeEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_ACTIVE_ENERGY) * 10;
1106 _data.aggregate.reactiveEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_REACTIVE_ENERGY) * 10;
1107 _data.aggregate.apparentEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_APPARENT_ENERGY) * 10;
1108 _data.aggregate.activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_ACTIVE_ENERGY_IMPORTED) * 10;
1109 _data.aggregate.activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_ACTIVE_ENERGY_RETURNED) * 10;
1110 _data.aggregate.reactiveEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_REACTIVE_ENERGY_IMPORTED) * 10;
1111 _data.aggregate.reactiveEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_REACTIVE_ENERGY_RETURNED) * 10;
1112 _data.aggregate.current = _data._metrics[0].current + _data._metrics[1].current + _data._metrics[2].current;
1113 _data.aggregate.voltage = _data.aggregate.current == 0 ? NAN : _data.aggregate.apparentPower / _data.aggregate.current;
1114
1115 break;
1116 }
1117
1118 default:
1119 break;
1120 }
1121
1122 _time = millis();
1123
1124 if (_callback) {
1125 _callback(EventType::EVT_READ, _data);
1126 }
1127
1128 return true;
1129}
1130
1132// readModel
1134
1135uint16_t Mycila::JSY::readModel(const uint8_t address) {
1136 if (!_enabled)
1137 return MYCILA_JSY_MK_UNKNOWN;
1138
1139 LOGD(TAG, "readModel(0x%02X)", address);
1140
1141 std::lock_guard<std::mutex> lock(_mutex);
1142
1143#ifdef MYCILA_JSY_DEBUG
1144 Serial.printf("[JSY] readModel(0x%02X)\n", address);
1145#endif
1146
1147 memcpy(_buffer, JSY_REQUEST_READ_MODEL, JSY_REQUEST_READ_MODEL_LEN);
1148 _send(address, JSY_REQUEST_READ_MODEL_LEN);
1149 ReadResult result = _timedRead(address, JSY_RESPONSE_SIZE_READ_MODEL, _baudRate);
1150
1151 if (result != ReadResult::READ_SUCCESS) {
1152 return MYCILA_JSY_MK_UNKNOWN;
1153 }
1154
1155 return (_buffer[JSY_RESPONSE_DATA] << 8) + _buffer[JSY_RESPONSE_DATA + 1];
1156}
1157
1159// readMode / setMode
1161
1162Mycila::JSY::Mode Mycila::JSY::_readMode(const uint8_t address, const uint16_t model) {
1163 if (!_enabled)
1164 return Mode::UNKNOWN;
1165
1166 switch (model) {
1167 case MYCILA_JSY_MK_163:
1168 case MYCILA_JSY_MK_193:
1169 case MYCILA_JSY_MK_194:
1170 case MYCILA_JSY_MK_333:
1171 return Mode::AC;
1172 case MYCILA_JSY_MK_227:
1173 case MYCILA_JSY_MK_229:
1174 return Mode::DC;
1175 case MYCILA_JSY_MK_1031:
1176 break; // reads the mode just after
1177 default:
1178 return Mode::UNKNOWN;
1179 }
1180
1181 LOGD(TAG, "readMode(0x%02X)", address);
1182
1183 std::lock_guard<std::mutex> lock(_mutex);
1184
1185#ifdef MYCILA_JSY_DEBUG
1186 Serial.printf("[JSY] readMode(0x%02X)\n", address);
1187#endif
1188
1189 memcpy(_buffer, JSY_REQUEST_READ_MODE, JSY_REQUEST_READ_MODE_LEN);
1190 _send(address, JSY_REQUEST_READ_MODE_LEN);
1191 ReadResult result = _timedRead(address, JSY_RESPONSE_SIZE_READ_MODE, _baudRate);
1192
1193 if (result != ReadResult::READ_SUCCESS) {
1194 return Mode::UNKNOWN;
1195 }
1196
1197 switch (_buffer[JSY_RESPONSE_DATA]) {
1198 case JSY_MODE_AC:
1199 return Mode::AC;
1200 case JSY_MODE_DC:
1201 return Mode::DC;
1202 default:
1203 return Mode::UNKNOWN;
1204 }
1205}
1206
1207bool Mycila::JSY::_setMode(const uint8_t address, const uint16_t model, const Mode mode) {
1208 if (!_enabled)
1209 return false;
1210
1211 if (mode == Mode::UNKNOWN)
1212 return false;
1213
1214 switch (model) {
1215 case MYCILA_JSY_MK_163:
1216 case MYCILA_JSY_MK_193:
1217 case MYCILA_JSY_MK_194:
1218 case MYCILA_JSY_MK_333:
1219 return mode == Mode::AC;
1220 case MYCILA_JSY_MK_227:
1221 case MYCILA_JSY_MK_229:
1222 return mode == Mode::DC;
1223 case MYCILA_JSY_MK_1031:
1224 break; // sets the mode just after
1225 default:
1226 return false;
1227 }
1228
1229 LOGD(TAG, "setMode(0x%02X) mode=%s", address, mode == Mode::AC ? "AC" : "DC");
1230
1231 std::lock_guard<std::mutex> lock(_mutex);
1232
1233#ifdef MYCILA_JSY_DEBUG
1234 Serial.printf("[JSY] setMode(0x%02X, %s)\n", address, mode == Mode::AC ? "AC" : "DC");
1235#endif
1236
1237 memcpy(_buffer, JSY_REQUEST_SWITCH_MODE, JSY_REQUEST_SWITCH_MODE_LEN);
1238
1239 _buffer[JSY_REQUEST_SET_MODE] = mode == Mode::AC ? JSY_MODE_AC : JSY_MODE_DC;
1240
1241 _send(address, JSY_REQUEST_SWITCH_MODE_LEN);
1242 ReadResult result = _timedRead(address, JSY_RESPONSE_SIZE_SWITCH_MODE, _baudRate);
1243
1244 return result == ReadResult::READ_SUCCESS;
1245}
1246
1248// resetEnergy
1250
1251bool Mycila::JSY::resetEnergy(const uint8_t address) {
1252 if (!_enabled)
1253 return false;
1254
1255 LOGD(TAG, "resetEnergy(0x%02X)", address);
1256
1257 std::lock_guard<std::mutex> lock(_mutex);
1258
1259#ifdef MYCILA_JSY_DEBUG
1260 Serial.printf("[JSY] resetEnergy(0x%02X)\n", address);
1261#endif
1262
1263 memcpy(_buffer, JSY_REQUEST_RESET_ENERGY, JSY_REQUEST_RESET_ENERGY_LEN);
1264 _send(address, JSY_REQUEST_RESET_ENERGY_LEN);
1265 ReadResult result = _timedRead(address, JSY_RESPONSE_SIZE_RESET_ENERGY, _baudRate);
1266
1267 return result == ReadResult::READ_SUCCESS;
1268}
1269
1271// settings
1273
1274Mycila::JSY::BaudRate Mycila::JSY::getMinAvailableBaudRate() const {
1275 if (!_enabled)
1276 return BaudRate::UNKNOWN;
1277 return getMinAvailableBaudRate(_model);
1278}
1279
1280Mycila::JSY::BaudRate Mycila::JSY::getMinAvailableBaudRate(uint16_t model) {
1281 switch (model) {
1282 case MYCILA_JSY_MK_163:
1283 case MYCILA_JSY_MK_193:
1284 case MYCILA_JSY_MK_194:
1285 case MYCILA_JSY_MK_227:
1286 case MYCILA_JSY_MK_229:
1287 return BaudRate::BAUD_1200;
1288 return BaudRate::BAUD_1200;
1289 case MYCILA_JSY_MK_1031:
1290 case MYCILA_JSY_MK_333:
1291 return BaudRate::BAUD_4800;
1292 default:
1293 return BaudRate::UNKNOWN;
1294 }
1295}
1296
1297Mycila::JSY::BaudRate Mycila::JSY::getMaxAvailableBaudRate() const {
1298 if (!_enabled)
1299 return BaudRate::UNKNOWN;
1300 return getMaxAvailableBaudRate(_model);
1301}
1302
1303Mycila::JSY::BaudRate Mycila::JSY::getMaxAvailableBaudRate(uint16_t model) {
1304 switch (model) {
1305 case MYCILA_JSY_MK_163:
1306 case MYCILA_JSY_MK_227:
1307 case MYCILA_JSY_MK_229:
1308 return BaudRate::BAUD_9600;
1309 case MYCILA_JSY_MK_1031:
1310 case MYCILA_JSY_MK_333:
1311 return BaudRate::BAUD_19200;
1312 case MYCILA_JSY_MK_193:
1313 case MYCILA_JSY_MK_194:
1314 return BaudRate::BAUD_38400;
1315 default:
1316 return BaudRate::UNKNOWN;
1317 }
1318}
1319
1320bool Mycila::JSY::isBaudRateSupported(Mycila::JSY::BaudRate baudRate) const {
1321 if (!_enabled)
1322 return false;
1323 return isBaudRateSupported(_model, baudRate);
1324}
1325
1326bool Mycila::JSY::isBaudRateSupported(uint16_t model, Mycila::JSY::BaudRate baudRate) {
1327 return getMinAvailableBaudRate(model) <= baudRate && baudRate <= getMaxAvailableBaudRate(model);
1328}
1329
1330bool Mycila::JSY::setBaudRate(const uint8_t address, const BaudRate baudRate) {
1331 return _set(address, address ? address : (_lastAddress ? _lastAddress : MYCILA_JSY_ADDRESS_DEFAULT), baudRate);
1332}
1333
1334bool Mycila::JSY::setDeviceAddress(const uint8_t address, const uint8_t newAddress) {
1335 return _set(address, newAddress, _baudRate);
1336}
1337
1338bool Mycila::JSY::_set(const uint8_t address, const uint8_t newAddress, const BaudRate newBaudRate) {
1339 if (!_enabled)
1340 return false;
1341
1342 if (newBaudRate == BaudRate::UNKNOWN)
1343 return false;
1344
1345 if (newAddress == MYCILA_JSY_ADDRESS_UNKNOWN)
1346 return false;
1347
1348 LOGD(TAG, "set(0x%02X) address=0x%02X, bauds=%" PRIu32, address, newAddress, newBaudRate);
1349
1350 std::lock_guard<std::mutex> lock(_mutex);
1351
1352#ifdef MYCILA_JSY_DEBUG
1353 Serial.printf("[JSY] _set(0x%02X)\n", address);
1354#endif
1355
1356 memcpy(_buffer, JSY_REQUEST_SET_COM, JSY_REQUEST_SET_COM_LEN);
1357
1358 // set address
1359 _buffer[JSY_REQUEST_SET_ADDRESS] = newAddress;
1360
1361 // set baud rate ID
1362 switch (newBaudRate) {
1363 case BaudRate::BAUD_1200:
1364 _buffer[JSY_REQUEST_SET_BAUDS] = 0x03;
1365 break;
1366 case BaudRate::BAUD_2400:
1367 _buffer[JSY_REQUEST_SET_BAUDS] = 0x04;
1368 break;
1369 case BaudRate::BAUD_4800:
1370 _buffer[JSY_REQUEST_SET_BAUDS] = 0x05;
1371 break;
1372 case BaudRate::BAUD_9600:
1373 _buffer[JSY_REQUEST_SET_BAUDS] = 0x06;
1374 break;
1375 case BaudRate::BAUD_19200:
1376 _buffer[JSY_REQUEST_SET_BAUDS] = 0x07;
1377 break;
1378 case BaudRate::BAUD_38400:
1379 _buffer[JSY_REQUEST_SET_BAUDS] = 0x08;
1380 break;
1381 default:
1382 assert(false);
1383 break;
1384 }
1385
1386 _send(address, JSY_REQUEST_SET_COM_LEN);
1387 ReadResult result = _timedRead(address, JSY_RESPONSE_SIZE_SET_COM, _baudRate);
1388
1389 // unexpected error ?
1390 if (result != ReadResult::READ_SUCCESS && result != ReadResult::READ_ERROR_ADDRESS) {
1391 return false;
1392 }
1393
1394 // response from unexpected address ?
1395 if (result == ReadResult::READ_ERROR_ADDRESS && _lastAddress != newAddress) {
1396 return false;
1397 }
1398
1399 _openSerial(newBaudRate);
1400
1401 bool success = false;
1402 for (int i = 0; i < MYCILA_JSY_RETRY_COUNT; i++) {
1403 if (_canRead(address, newBaudRate)) {
1404 success = true;
1405 break;
1406 }
1407 }
1408
1409 if (success) {
1410 // update baud rate
1411 _baudRate = newBaudRate;
1412
1413 // update destination address if needed
1414 if (_destinationAddress != MYCILA_JSY_ADDRESS_BROADCAST && _destinationAddress == address) {
1415 _destinationAddress = newAddress;
1416 }
1417
1418 } else {
1419 LOGE(TAG, "Unable to read JSY @ 0x%02X at speed: %" PRIu32, address, newBaudRate);
1420 if (_baudRate != BaudRate::UNKNOWN) {
1421 _openSerial(_baudRate);
1422 }
1423 }
1424
1425 return success;
1426}
1427
1429// toJson
1431
1432#ifdef MYCILA_JSON_SUPPORT
1433void Mycila::JSY::toJson(const JsonObject& root) const {
1434 root["enabled"] = _enabled;
1435 root["time"] = _time;
1436 root["speed"] = _baudRate;
1437 _data.toJson(root);
1438}
1439#endif
1440
1442// I/O
1444
1445bool Mycila::JSY::_canRead(const uint8_t address, BaudRate baudRate) {
1446#ifdef MYCILA_JSY_DEBUG
1447 Serial.printf("[JSY] _canRead(0x%02X)\n", address);
1448#endif
1449 memcpy(_buffer, JSY_REQUEST_READ_MODEL, JSY_REQUEST_READ_MODEL_LEN);
1450 _send(address, JSY_REQUEST_READ_MODEL_LEN);
1451 return _timedRead(address, JSY_RESPONSE_SIZE_READ_MODEL, baudRate) == ReadResult::READ_SUCCESS;
1452}
1453
1454Mycila::JSY::ReadResult Mycila::JSY::_timedRead(const uint8_t expectedAddress, const size_t expectedLen, const BaudRate baudRate) {
1455 size_t count = 0;
1456 while (count < expectedLen) {
1457 size_t read = _serial->readBytes(_buffer + count, expectedLen - count);
1458 if (read) {
1459 count += read;
1460 } else {
1461 break;
1462 }
1463 }
1464
1465#ifdef MYCILA_JSY_DEBUG
1466 Serial.printf("[JSY] timedRead(0x%02X) %d < ", expectedAddress, count);
1467 for (size_t i = 0; i < count; i++) {
1468 Serial.printf("0x%02X ", _buffer[i]);
1469 }
1470 Serial.println();
1471#endif
1472
1473 _drop();
1474
1475 // timeout ?
1476 if (count == 0) {
1477 LOGD(TAG, "timedRead(0x%02X) timeout", expectedAddress);
1478 return ReadResult::READ_TIMEOUT;
1479 }
1480
1481 // check length
1482 if (count != expectedLen) {
1483 LOGD(TAG, "timedRead(0x%02X) error: len %d != %d", expectedAddress, count, expectedLen);
1484 return ReadResult::READ_ERROR_COUNT;
1485 }
1486
1487 // CRC check
1488 uint16_t crc = _crc16(_buffer, expectedLen - 2);
1489 if (_buffer[expectedLen - 2] != LOBYTE(crc) || _buffer[expectedLen - 1] != HIBYTE(crc)) {
1490 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));
1491 return ReadResult::READ_ERROR_CRC;
1492 }
1493
1494 _lastAddress = _buffer[JSY_RESPONSE_ADDRESS];
1495
1496 // address check
1497 if (expectedAddress != MYCILA_JSY_ADDRESS_BROADCAST && expectedAddress != _lastAddress) {
1498 LOGD(TAG, "timedRead(0x%02X) error: wrong device address 0x%02X", expectedAddress, _lastAddress);
1499 return ReadResult::READ_ERROR_ADDRESS;
1500 }
1501
1502 return ReadResult::READ_SUCCESS;
1503}
1504
1505void Mycila::JSY::_send(const uint8_t address, const size_t len) {
1506 // set destination address
1507 _buffer[JSY_REQUEST_ADDRESS] = address;
1508
1509 // crc16
1510 uint16_t crc = _crc16(_buffer, len - 2);
1511 _buffer[len - 2] = LOBYTE(crc);
1512 _buffer[len - 1] = HIBYTE(crc);
1513
1514#ifdef MYCILA_JSY_DEBUG
1515 Serial.printf("[JSY] send(0x%02X) %d > ", address, len);
1516 for (size_t i = 0; i < len; i++) {
1517 Serial.printf("0x%02X ", _buffer[i]);
1518 }
1519 Serial.println();
1520#endif
1521
1522 _serial->flush(false);
1523 _serial->write(_buffer, len);
1524}
1525
1526size_t Mycila::JSY::_drop() {
1527 size_t count = 0;
1528 if (_serial->available()) {
1529#ifdef MYCILA_JSY_DEBUG
1530 Serial.printf("[JSY] drop < ");
1531#endif
1532 while (_serial->available()) {
1533#ifdef MYCILA_JSY_DEBUG
1534 Serial.printf("0x%02X ", _serial->read());
1535#else
1536 _serial->read();
1537#endif
1538 count++;
1539 }
1540#ifdef MYCILA_JSY_DEBUG
1541 Serial.println();
1542#endif
1543 }
1544 return count;
1545}
1546
1547void Mycila::JSY::_openSerial(BaudRate baudRate) {
1548 LOGD(TAG, "openSerial(%" PRIu32 ")", baudRate);
1549 _serial->begin(baudRate, SERIAL_8N1, _pinRX, _pinTX);
1550 _serial->setTimeout(MYCILA_JSY_READ_TIMEOUT_MS);
1551 while (!_serial)
1552 yield();
1553 while (!_serial->availableForWrite())
1554 yield();
1555 _drop();
1556 _serial->flush(false);
1557}
1558
1559Mycila::JSY::BaudRate Mycila::JSY::_detectBauds(const uint8_t address) {
1560 for (int i = 0; i < AUTO_DETECT_BAUD_RATES_COUNT * 2; i++) {
1561 BaudRate baudRate = AUTO_DETECT_BAUD_RATES[i % AUTO_DETECT_BAUD_RATES_COUNT];
1562 LOGD(TAG, "find(0x%02X) %" PRIu32 " bauds", address, baudRate);
1563 _openSerial(baudRate);
1564 for (int j = 0; j < MYCILA_JSY_RETRY_COUNT; j++) {
1565 if (_canRead(address, baudRate)) {
1566 return baudRate;
1567 }
1568 }
1569 }
1570 return BaudRate::UNKNOWN;
1571}
1572
1574// static
1576
1577// For CRC: https://crccalc.com
1578// Select CRC-16/MODBUS
1579inline uint16_t Mycila::JSY::_crc16(const uint8_t* data, size_t len) {
1580 uint16_t crc = 0xFFFF;
1581 while (len--) {
1582 uint8_t temp = *(data++) ^ LOBYTE(crc);
1583 crc = (crc >> 8) ^ pgm_read_word_near(CRCTable + temp);
1584 }
1585 return crc;
1586}
1587
1588uint8_t Mycila::JSY::_register8(const uint8_t* buffer, const uint16_t registerStart, const uint16_t registerSize, const uint16_t registerAddress, uint8_t index) {
1589 return buffer[JSY_RESPONSE_DATA + (registerAddress - registerStart) * registerSize + index];
1590}
1591
1592uint16_t Mycila::JSY::_register16(const uint8_t* buffer, const uint16_t registerStart, const uint16_t registerSize, const uint16_t registerAddress) {
1593 const size_t start = JSY_RESPONSE_DATA + (registerAddress - registerStart) * registerSize;
1594 return (buffer[start] << 8) + buffer[start + 1];
1595}
1596
1597uint32_t Mycila::JSY::_register32(const uint8_t* buffer, const uint16_t registerStart, const uint16_t registerSize, const uint16_t registerAddress) {
1598 const size_t start = JSY_RESPONSE_DATA + (registerAddress - registerStart) * registerSize;
1599 return (buffer[start] << 24) +
1600 (buffer[start + 1] << 16) +
1601 (buffer[start + 2] << 8) +
1602 (buffer[start + 3]);
1603}
1604
1605const char* Mycila::JSY::getModelName(uint16_t model) {
1606 switch (model) {
1607 case MYCILA_JSY_MK_1031:
1608 return MYCILA_JSY_MK_1031_NAME;
1609 case MYCILA_JSY_MK_163:
1610 return MYCILA_JSY_MK_163_NAME;
1611 case MYCILA_JSY_MK_193:
1612 return MYCILA_JSY_MK_193_NAME;
1613 case MYCILA_JSY_MK_194:
1614 return MYCILA_JSY_MK_194_NAME;
1615 case MYCILA_JSY_MK_227:
1616 return MYCILA_JSY_MK_227_NAME;
1617 case MYCILA_JSY_MK_229:
1618 return MYCILA_JSY_MK_229_NAME;
1619 case MYCILA_JSY_MK_333:
1620 return MYCILA_JSY_MK_333_NAME;
1621 default:
1622 return emptyString.c_str();
1623 }
1624}
1625
1626void Mycila::JSY::_jsyTask(void* params) {
1627 JSY* jsy = reinterpret_cast<JSY*>(params);
1628 while (jsy->_enabled) {
1629 if (jsy->read()) {
1630 if (jsy->_pause > 0) {
1631 delay(jsy->_pause);
1632 } else {
1633 yield();
1634 }
1635 } else if (jsy->_pause > 0) {
1636 delay(jsy->_pause);
1637 } else {
1638 delay(10);
1639 }
1640 }
1641 jsy->_taskHandle = NULL;
1642 vTaskDelete(NULL);
1643}
bool read()
Read the JSY values.
Definition MycilaJSY.h:473
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
bool setDeviceAddress(uint8_t newAddress)
Set a new address for a device.
Definition MycilaJSY.h:394
bool resetEnergy()
Reset the energy counters of the JSY.
Definition MycilaJSY.h:488
bool setBaudRate(BaudRate baudRate)
Change the baud rate of the JSY.
Definition MycilaJSY.h:507
BaudRate getMaxAvailableBaudRate() const
Get the maximum available baud rate supported by the current JSY model connected.
bool isBaudRateSupported(BaudRate baudRate) const
Check if a baud rate is supported by the current JSY model connected.
void end()
Ends the JSY communication.
uint16_t readModel()
Reads the JSY model.
Definition MycilaJSY.h:411