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) 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 // 30 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
381// clang-format off
382static constexpr uint16_t CRCTable[] = {
383 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
384 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
385 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
386 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
387 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
388 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
389 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
390 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
391 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
392 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
393 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
394 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
395 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
396 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
397 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
398 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040};
399// clang-format on
400
402// begin / end
404
405void Mycila::JSY::begin(HardwareSerial& serial,
406 const int8_t rxPin,
407 const int8_t txPin,
408 const BaudRate baudRate,
409 const uint8_t destinationAddress,
410 const uint16_t model,
411 const bool async,
412 const uint8_t core,
413 const uint32_t stackSize,
414 const uint32_t pause) {
415 if (_enabled)
416 return;
417
418 if (GPIO_IS_VALID_GPIO(rxPin)) {
419 _pinRX = (gpio_num_t)rxPin;
420 } else {
421 LOGE(TAG, "Disable JSY: Invalid Serial RX (JSY TX pin): %" PRId8, rxPin);
422 _pinRX = GPIO_NUM_NC;
423 return;
424 }
425
426 if (GPIO_IS_VALID_OUTPUT_GPIO(txPin)) {
427 _pinTX = (gpio_num_t)txPin;
428 } else {
429 LOGE(TAG, "Disable JSY: Invalid Serial TX (JSY RX pin): %" PRId8, txPin);
430 _pinTX = GPIO_NUM_NC;
431 return;
432 }
433
434 LOGI(TAG, "Enable JSY @ 0x%02X on Serial RX (JSY TX Pin): %" PRId8 " and Serial TX (JSY RX Pin): %" PRId8, destinationAddress, rxPin, txPin);
435
436 _pause = pause;
437 _serial = &serial;
438
439 if (baudRate == BaudRate::UNKNOWN) {
440 _baudRate = _detectBauds(destinationAddress);
441
442 if (_baudRate == BaudRate::UNKNOWN) {
443 if (_lastAddress == MYCILA_JSY_ADDRESS_UNKNOWN)
444 LOGE(TAG, "Unable to read any JSY @ 0x%02X at any supported speed.", destinationAddress);
445 else
446 LOGE(TAG, "Unable to read any JSY @ 0x%02X at any supported speed but found one @ 0x%02X.", destinationAddress, _lastAddress);
447
448 _serial->end();
449 return;
450 }
451
452 } else {
453 LOGW(TAG, "JSY @ 0x%02X bauds detection skipped, forcing baud rate: %" PRIu32, destinationAddress, _baudRate);
454 _openSerial(baudRate);
455
456 _baudRate = BaudRate::UNKNOWN;
457 for (int j = 0; j < MYCILA_JSY_RETRY_COUNT; j++) {
458 if (_canRead(destinationAddress, baudRate)) {
459 _baudRate = baudRate;
460 break;
461 }
462 }
463
464 if (_baudRate == BaudRate::UNKNOWN) {
465 LOGE(TAG, "Unable to read any JSY @ 0x%02X at speed: %" PRIu32, destinationAddress, baudRate);
466 _serial->end();
467 return;
468 }
469 }
470
471 _enabled = true;
472 _model = model ? model : readModel(destinationAddress);
473
474 if (_model != MYCILA_JSY_MK_1031 &&
475 _model != MYCILA_JSY_MK_163 &&
476 _model != MYCILA_JSY_MK_193 &&
477 _model != MYCILA_JSY_MK_194 &&
478 _model != MYCILA_JSY_MK_227 &&
479 _model != MYCILA_JSY_MK_229 &&
480 _model != MYCILA_JSY_MK_333) {
481 LOGE(TAG, "Unsupported JSY model: JSY-MK-%X", _model);
482 // unsupported
483 _enabled = false;
484 _serial->end();
485 return;
486 }
487
488 _destinationAddress = destinationAddress;
489 LOGI(TAG, "Detected JSY-MK-%X @ 0x%02X with speed %" PRIu32 " bauds", _model, _lastAddress, _baudRate);
490
491 assert(!async || xTaskCreateUniversal(_jsyTask, "jsyTask", stackSize, this, MYCILA_JSY_ASYNC_PRIORITY, &_taskHandle, core) == pdPASS);
492}
493
495 if (_enabled) {
496 LOGI(TAG, "Disable JSY @ 0x%02X", _destinationAddress);
497 _enabled = false;
498 while (_taskHandle != NULL) {
499 // JSY takes at least 40-160 ms to finish a read
500 delay(50);
501 }
502 std::lock_guard<std::mutex> lock(_mutex);
503 LOGD(TAG, "Closing Serial for JSY @ 0x%02X", _destinationAddress);
504 _serial->end();
505 _serial = nullptr;
506 _baudRate = BaudRate::UNKNOWN;
507 _lastAddress = MYCILA_JSY_ADDRESS_UNKNOWN;
508 _model = MYCILA_JSY_MK_UNKNOWN;
509 _data.clear();
510 }
511}
512
514// read
516
517bool Mycila::JSY::_read(const uint8_t address, uint16_t model) {
518 if (!_enabled)
519 return false;
520
521 std::lock_guard<std::mutex> lock(_mutex);
522
523#ifdef MYCILA_JSY_DEBUG
524 Serial.printf("[JSY] read(0x%02X)\n", address);
525#endif
526
527 memcpy(_buffer, JSY_REQUEST_READ_REGISTERS, JSY_REQUEST_READ_REGISTERS_LEN);
528
529 // fill the request with the registers to read
530 // this depends on the model
531 uint16_t registerStart = 0;
532 uint16_t registerCount = 0;
533 uint8_t registerSize = 0;
534
535 switch (model) {
536 case MYCILA_JSY_MK_1031:
537 registerSize = JSY_1031_REGISTER_LEN;
538 registerStart = JSY_1031_REGISTER_START;
539 registerCount = JSY_1031_REGISTER_COUNT;
540 break;
541
542 case MYCILA_JSY_MK_163:
543 registerSize = JSY_163_REGISTER_LEN;
544 registerStart = JSY_163_REGISTER_START;
545 registerCount = JSY_163_REGISTER_COUNT;
546 break;
547
548 case MYCILA_JSY_MK_193:
549 registerSize = JSY_193_REGISTER_LEN;
550 registerStart = JSY_193_REGISTER_START;
551 registerCount = JSY_193_REGISTER_COUNT;
552 break;
553
554 case MYCILA_JSY_MK_194:
555 registerSize = JSY_194_REGISTER_LEN;
556 registerStart = JSY_194_REGISTER_START;
557 registerCount = JSY_194_REGISTER_COUNT;
558 break;
559
560 case MYCILA_JSY_MK_227:
561 case MYCILA_JSY_MK_229:
562 registerSize = JSY_22x_REGISTER_LEN;
563 registerStart = JSY_22x_REGISTER_START;
564 registerCount = JSY_22x_REGISTER_COUNT;
565 break;
566
567 case MYCILA_JSY_MK_333:
568 registerSize = JSY_333_REGISTER_LEN;
569 registerStart = JSY_333_REGISTER_START;
570 registerCount = JSY_333_REGISTER_COUNT;
571 break;
572
573 default:
574 break;
575 }
576
577 _buffer[JSY_REQUEST_READ_REGISTER_ADDR_HIGH] = HIBYTE(registerStart);
578 _buffer[JSY_REQUEST_READ_REGISTER_ADDR_LOW] = LOBYTE(registerStart);
579 _buffer[JSY_REQUEST_READ_REGISTER_COUNT_HIGH] = HIBYTE(registerCount);
580 _buffer[JSY_REQUEST_READ_REGISTER_COUNT_LOW] = LOBYTE(registerCount);
581
582 _send(address, JSY_REQUEST_READ_REGISTERS_LEN);
583 ReadResult result = _timedRead(address, JSY_RESPONSE_SIZE_READ + registerCount * registerSize, _baudRate);
584
585 if (result == ReadResult::READ_TIMEOUT) {
586 // reset live values in case of read timeout
587 _data.clear();
588 if (_callback) {
589 _callback(EventType::EVT_READ_TIMEOUT, _data);
590 }
591 return false;
592 }
593
594 if (result == ReadResult::READ_ERROR_COUNT || result == ReadResult::READ_ERROR_CRC) {
595 // reset live values in case of read failure
596 _data.clear();
597 if (_callback) {
598 _callback(EventType::EVT_READ_ERROR, _data);
599 }
600 return false;
601 }
602
603 if (result == ReadResult::READ_ERROR_ADDRESS) {
604 // we have set a destination address, but we read another device
605 if (_callback) {
606 _callback(EventType::EVT_READ_ERROR, _data);
607 }
608 return false;
609 }
610
611 assert(result == ReadResult::READ_SUCCESS);
612
613 _data.address = _buffer[JSY_RESPONSE_ADDRESS];
614 _data.model = model;
615
616 switch (model) {
617 case MYCILA_JSY_MK_1031: {
618 // single channel
619 _data._metrics[0].frequency = _register16(_buffer, registerStart, registerSize, JSY_1031_REGISTER_FREQUENCY) * 0.01f;
620 _data._metrics[0].voltage = _register16(_buffer, registerStart, registerSize, JSY_1031_REGISTER_VOLTAGE) * 0.01f;
621 _data._metrics[0].current = _register32(_buffer, registerStart, registerSize, JSY_1031_REGISTER_CURRENT) * 0.0001f;
622 _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
623 _data._metrics[0].activeEnergy = _register32(_buffer, registerStart, registerSize, JSY_1031_REGISTER_ACTIVE_ENERGY) * 10;
624 _data._metrics[0].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_1031_REGISTER_POWER_FACTOR) * 0.001f;
625 _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
626 _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
627
628 // aggregate
629 _data.aggregate = _data._metrics[0];
630
631 break;
632 }
633
634 case MYCILA_JSY_MK_163: {
635 // signs
636 // _buffer[19] unused
637 // _buffer[20] is the sign of power
638 uint8_t sign = _register8(_buffer, registerStart, registerSize, JSY_163_REGISTER_ACTIVE_POWER_SIGN, 1);
639
640 // single channel
641 _data._metrics[0].frequency = _register16(_buffer, registerStart, registerSize, JSY_163_REGISTER_FREQUENCY) * 0.01f;
642 _data._metrics[0].voltage = _register16(_buffer, registerStart, registerSize, JSY_163_REGISTER_VOLTAGE) * 0.01f;
643 _data._metrics[0].current = _register16(_buffer, registerStart, registerSize, JSY_163_REGISTER_CURRENT) * 0.01f;
644 _data._metrics[0].activePower = _register16(_buffer, registerStart, registerSize, JSY_163_REGISTER_ACTIVE_POWER) * (sign ? -1.0f : 1.0f);
645 _data._metrics[0].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_163_REGISTER_ACTIVE_ENERGY_IMPORTED) * 5.0f / 16.0f;
646 _data._metrics[0].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_163_REGISTER_POWER_FACTOR) * 0.001f;
647 _data._metrics[0].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_163_REGISTER_ACTIVE_ENERGY_RETURNED) * 5.0f / 16.0f;
648
649 // calculate remaining metrics
650 // S = P / PF
651 _data._metrics[0].apparentPower = _data._metrics[0].powerFactor == 0 ? 0 : std::abs(_data._metrics[0].activePower / _data._metrics[0].powerFactor);
652 // Q = std::sqrt(S^2 - P^2)
653 _data._metrics[0].reactivePower = std::sqrt(_data._metrics[0].apparentPower * _data._metrics[0].apparentPower - _data._metrics[0].activePower * _data._metrics[0].activePower);
654 // E = Ei + Er
655 _data._metrics[0].activeEnergy = _data._metrics[0].activeEnergyImported + _data._metrics[0].activeEnergyReturned;
656
657 // aggregate
658 _data.aggregate = _data._metrics[0];
659
660 break;
661 }
662
663 case MYCILA_JSY_MK_193: {
664 // channel 1
665 _data._metrics[0].voltage = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_VOLTAGE) * 0.01f;
666 _data._metrics[0].current = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_CURRENT) * 0.01f;
667 _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);
668 _data._metrics[0].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_ACTIVE_ENERGY_POSITIVE) * 10;
669 _data._metrics[0].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_ACTIVE_ENERGY_NEGATIVE) * 10;
670 _data._metrics[0].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_POWER_FACTOR) * 0.001f;
671 _data._metrics[0].frequency = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH1_FREQUENCY) * 0.01f;
672
673 // channel 2
674 _data._metrics[1].voltage = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_VOLTAGE) * 0.01f;
675 _data._metrics[1].current = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_CURRENT) * 0.01f;
676 _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);
677 _data._metrics[1].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_ACTIVE_ENERGY_POSITIVE) * 10;
678 _data._metrics[1].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_ACTIVE_ENERGY_NEGATIVE) * 10;
679 _data._metrics[1].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_POWER_FACTOR) * 0.001f;
680 _data._metrics[1].frequency = _register16(_buffer, registerStart, registerSize, JSY_193_REGISTER_CH2_FREQUENCY) * 0.01f;
681
682 // calculate remaining metrics
683 // S = P / PF
684 _data._metrics[0].apparentPower = _data._metrics[0].powerFactor == 0 ? 0 : std::abs(_data._metrics[0].activePower / _data._metrics[0].powerFactor);
685 _data._metrics[1].apparentPower = _data._metrics[1].powerFactor == 0 ? 0 : std::abs(_data._metrics[1].activePower / _data._metrics[1].powerFactor);
686 // Q = std::sqrt(S^2 - P^2)
687 _data._metrics[0].reactivePower = std::sqrt(_data._metrics[0].apparentPower * _data._metrics[0].apparentPower - _data._metrics[0].activePower * _data._metrics[0].activePower);
688 _data._metrics[1].reactivePower = std::sqrt(_data._metrics[1].apparentPower * _data._metrics[1].apparentPower - _data._metrics[1].activePower * _data._metrics[1].activePower);
689 // E = Ei + Er
690 _data._metrics[0].activeEnergy = _data._metrics[0].activeEnergyImported + _data._metrics[0].activeEnergyReturned;
691 _data._metrics[1].activeEnergy = _data._metrics[1].activeEnergyImported + _data._metrics[1].activeEnergyReturned;
692
693 // aggregate
694 _data.aggregate = _data._metrics[0];
695 _data.aggregate += _data._metrics[1];
696 _data.aggregate.voltage = std::max(_data._metrics[0].voltage, _data._metrics[1].voltage);
697 _data.aggregate.frequency = std::max(_data._metrics[0].frequency, _data._metrics[1].frequency);
698
699 break;
700 }
701
702 case MYCILA_JSY_MK_194: {
703 // signs
704 // _buffer[27] is the sign of power1
705 // _buffer[28] is the sign of power2
706 // _buffer[29] unused
707 // _buffer[30] unused
708 uint8_t sign0 = _register8(_buffer, registerStart, registerSize, JSY_194_REGISTER_ACTIVE_POWER_SIGNS, 0);
709 uint8_t sign1 = _register8(_buffer, registerStart, registerSize, JSY_194_REGISTER_ACTIVE_POWER_SIGNS, 1);
710
711 // frequency
712 float frequency = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_FREQUENCY) * 0.01f;
713
714 // channel 1
715 _data._metrics[0].frequency = frequency;
716 _data._metrics[0].voltage = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH1_VOLTAGE) * 0.0001f;
717 _data._metrics[0].current = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH1_CURRENT) * 0.0001f;
718 _data._metrics[0].activePower = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH1_ACTIVE_POWER) * (sign0 ? -0.0001f : 0.0001f);
719 _data._metrics[0].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH1_ACTIVE_ENERGY_IMPORTED) * 0.1f;
720 _data._metrics[0].powerFactor = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH1_POWER_FACTOR) * 0.001f;
721 _data._metrics[0].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH1_ACTIVE_ENERGY_RETURNED) * 0.1f;
722
723 // channel 2
724 _data._metrics[1].frequency = frequency;
725 _data._metrics[1].voltage = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH2_VOLTAGE) * 0.0001f;
726 _data._metrics[1].current = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH2_CURRENT) * 0.0001f;
727 _data._metrics[1].activePower = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH2_ACTIVE_POWER) * (sign1 ? -0.0001f : 0.0001f);
728 _data._metrics[1].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH2_ACTIVE_ENERGY_IMPORTED) * 0.1f;
729 _data._metrics[1].powerFactor = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH2_POWER_FACTOR) * 0.001f;
730 _data._metrics[1].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_194_REGISTER_CH2_ACTIVE_ENERGY_RETURNED) * 0.1f;
731
732 // calculate remaining metrics
733 // S = P / PF
734 _data._metrics[0].apparentPower = _data._metrics[0].powerFactor == 0 ? 0 : std::abs(_data._metrics[0].activePower / _data._metrics[0].powerFactor);
735 _data._metrics[1].apparentPower = _data._metrics[1].powerFactor == 0 ? 0 : std::abs(_data._metrics[1].activePower / _data._metrics[1].powerFactor);
736 // Q = std::sqrt(S^2 - P^2)
737 _data._metrics[0].reactivePower = std::sqrt(_data._metrics[0].apparentPower * _data._metrics[0].apparentPower - _data._metrics[0].activePower * _data._metrics[0].activePower);
738 _data._metrics[1].reactivePower = std::sqrt(_data._metrics[1].apparentPower * _data._metrics[1].apparentPower - _data._metrics[1].activePower * _data._metrics[1].activePower);
739 // E = Ei + Er
740 _data._metrics[0].activeEnergy = _data._metrics[0].activeEnergyImported + _data._metrics[0].activeEnergyReturned;
741 _data._metrics[1].activeEnergy = _data._metrics[1].activeEnergyImported + _data._metrics[1].activeEnergyReturned;
742
743 // aggregate
744 _data.aggregate = _data._metrics[0];
745 _data.aggregate += _data._metrics[1];
746 _data.aggregate.frequency = frequency;
747 _data.aggregate.voltage = std::max(_data._metrics[0].voltage, _data._metrics[1].voltage);
748
749 break;
750 }
751
752 case MYCILA_JSY_MK_227:
753 case MYCILA_JSY_MK_229: {
754 // single channel
755 _data._metrics[0].voltage = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_VOLTAGE) * 0.0001f;
756 _data._metrics[0].current = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_CURRENT) * 0.0001f;
757 _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);
758 _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);
759 _data._metrics[0].apparentPower = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_APPARENT_POWER) * 0.0001f;
760 _data._metrics[0].powerFactor = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_POWER_FACTOR) * 0.001f;
761 _data._metrics[0].frequency = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_FREQUENCY) * 0.01f;
762 _data._metrics[0].activeEnergy = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_ACTIVE_ENERGY);
763 _data._metrics[0].reactiveEnergy = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_REACTIVE_ENERGY);
764 _data._metrics[0].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_ACTIVE_ENERGY_POSITIVE);
765 _data._metrics[0].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_ACTIVE_ENERGY_NEGATIVE);
766 _data._metrics[0].reactiveEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_REACTIVE_ENERGY_POSITIVE);
767 _data._metrics[0].reactiveEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_22x_REGISTER_REACTIVE_ENERGY_NEGATIVE);
768
769 // aggregate
770 _data.aggregate = _data._metrics[0];
771
772 break;
773 }
774
775 case MYCILA_JSY_MK_333: {
776 // signs
777 // _buffer[103] unused
778 // _buffer[104] bit 7: sign of total reactive power
779 // _buffer[104] bit 6: sign of phase C reactive power
780 // _buffer[104] bit 5: sign of phase B reactive power
781 // _buffer[104] bit 4: sign of phase A reactive power
782 // _buffer[104] bit 3: sign of total active power
783 // _buffer[104] bit 2: sign of phase C active power
784 // _buffer[104] bit 1: sign of phase B active power
785 // _buffer[104] bit 0: sign of phase A active power
786 uint8_t sign7 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x80;
787 uint8_t sign6 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x40;
788 uint8_t sign5 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x20;
789 uint8_t sign4 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x10;
790 uint8_t sign3 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x08;
791 uint8_t sign2 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x04;
792 uint8_t sign1 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x02;
793 uint8_t sign0 = _register8(_buffer, registerStart, registerSize, JSY_333_REGISTER_POWER_SIGNS, 1) & 0x01;
794
795 // frequency
796 float frequency = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_FREQUENCY) * 0.01f;
797
798 // phase A
799 _data._metrics[0].frequency = frequency;
800 _data._metrics[0].voltage = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_VOLTAGE) * 0.01f;
801 _data._metrics[0].current = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_CURRENT) * 0.01f;
802 _data._metrics[0].activePower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_ACTIVE_POWER) * (sign0 ? -1.0f : 1.0f);
803 _data._metrics[0].reactivePower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_REACTIVE_POWER) * (sign4 ? -1.0f : 1.0f);
804 _data._metrics[0].apparentPower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_APPARENT_POWER);
805 _data._metrics[0].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_POWER_FACTOR) * 0.001f;
806 _data._metrics[0].activeEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_ACTIVE_ENERGY) * 10;
807 _data._metrics[0].reactiveEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_REACTIVE_ENERGY) * 10;
808 _data._metrics[0].apparentEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_APPARENT_ENERGY) * 10;
809 _data._metrics[0].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_ACTIVE_ENERGY_IMPORTED) * 10;
810 _data._metrics[0].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_ACTIVE_ENERGY_RETURNED) * 10;
811 _data._metrics[0].reactiveEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_REACTIVE_ENERGY_IMPORTED) * 10;
812 _data._metrics[0].reactiveEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_REACTIVE_ENERGY_RETURNED) * 10;
813 _data._metrics[0].phaseAngleU = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_PHASE_ANGLE_U) * 0.01f;
814 _data._metrics[0].phaseAngleI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_PHASE_ANGLE_I) * 0.01f;
815 _data._metrics[0].phaseAngleUI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_PHASE_ANGLE_UI) * 0.01f;
816 _data._metrics[0].thdU = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_THD_U) * 0.01f;
817 _data._metrics[0].thdI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_A_THD_I) * 0.01f;
818
819 // phase B
820 _data._metrics[1].frequency = frequency;
821 _data._metrics[1].voltage = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_VOLTAGE) * 0.01f;
822 _data._metrics[1].current = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_CURRENT) * 0.01f;
823 _data._metrics[1].activePower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_ACTIVE_POWER) * (sign1 ? -1.0f : 1.0f);
824 _data._metrics[1].reactivePower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_REACTIVE_POWER) * (sign5 ? -1.0f : 1.0f);
825 _data._metrics[1].apparentPower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_APPARENT_POWER);
826 _data._metrics[1].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_POWER_FACTOR) * 0.001f;
827 _data._metrics[1].activeEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_ACTIVE_ENERGY) * 10;
828 _data._metrics[1].reactiveEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_REACTIVE_ENERGY) * 10;
829 _data._metrics[1].apparentEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_APPARENT_ENERGY) * 10;
830 _data._metrics[1].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_ACTIVE_ENERGY_IMPORTED) * 10;
831 _data._metrics[1].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_ACTIVE_ENERGY_RETURNED) * 10;
832 _data._metrics[1].reactiveEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_REACTIVE_ENERGY_IMPORTED) * 10;
833 _data._metrics[1].reactiveEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_REACTIVE_ENERGY_RETURNED) * 10;
834 _data._metrics[1].phaseAngleU = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_PHASE_ANGLE_U) * 0.01f;
835 _data._metrics[1].phaseAngleI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_PHASE_ANGLE_I) * 0.01f;
836 _data._metrics[1].phaseAngleUI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_PHASE_ANGLE_UI) * 0.01f;
837 _data._metrics[1].thdU = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_THD_U) * 0.01f;
838 _data._metrics[1].thdI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_B_THD_I) * 0.01f;
839
840 // phase C
841 _data._metrics[2].frequency = frequency;
842 _data._metrics[2].voltage = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_VOLTAGE) * 0.01f;
843 _data._metrics[2].current = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_CURRENT) * 0.01f;
844 _data._metrics[2].activePower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_ACTIVE_POWER) * (sign2 ? -1.0f : 1.0f);
845 _data._metrics[2].reactivePower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_REACTIVE_POWER) * (sign6 ? -1.0f : 1.0f);
846 _data._metrics[2].apparentPower = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_APPARENT_POWER);
847 _data._metrics[2].powerFactor = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_POWER_FACTOR) * 0.001f;
848 _data._metrics[2].activeEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_ACTIVE_ENERGY) * 10;
849 _data._metrics[2].reactiveEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_REACTIVE_ENERGY) * 10;
850 _data._metrics[2].apparentEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_APPARENT_ENERGY) * 10;
851 _data._metrics[2].activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_ACTIVE_ENERGY_IMPORTED) * 10;
852 _data._metrics[2].activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_ACTIVE_ENERGY_RETURNED) * 10;
853 _data._metrics[2].reactiveEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_REACTIVE_ENERGY_IMPORTED) * 10;
854 _data._metrics[2].reactiveEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_REACTIVE_ENERGY_RETURNED) * 10;
855 _data._metrics[2].phaseAngleU = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_PHASE_ANGLE_U) * 0.01f;
856 _data._metrics[2].phaseAngleI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_PHASE_ANGLE_I) * 0.01f;
857 _data._metrics[2].phaseAngleUI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_PHASE_ANGLE_UI) * 0.01f;
858 _data._metrics[2].thdU = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_THD_U) * 0.01f;
859 _data._metrics[2].thdI = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_PHASE_C_THD_I) * 0.01f;
860
861 // aggregate
862 _data.aggregate.frequency = frequency;
863 _data.aggregate.activePower = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_ACTIVE_POWER) * (sign3 ? -1.0f : 1.0f);
864 _data.aggregate.reactivePower = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_REACTIVE_POWER) * (sign7 ? -1.0f : 1.0f);
865 _data.aggregate.apparentPower = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_APPARENT_POWER);
866 _data.aggregate.powerFactor = _register16(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_POWER_FACTOR) * 0.001f;
867 _data.aggregate.activeEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_ACTIVE_ENERGY) * 10;
868 _data.aggregate.reactiveEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_REACTIVE_ENERGY) * 10;
869 _data.aggregate.apparentEnergy = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_APPARENT_ENERGY) * 10;
870 _data.aggregate.activeEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_ACTIVE_ENERGY_IMPORTED) * 10;
871 _data.aggregate.activeEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_ACTIVE_ENERGY_RETURNED) * 10;
872 _data.aggregate.reactiveEnergyImported = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_REACTIVE_ENERGY_IMPORTED) * 10;
873 _data.aggregate.reactiveEnergyReturned = _register32(_buffer, registerStart, registerSize, JSY_333_REGISTER_TOTAL_REACTIVE_ENERGY_RETURNED) * 10;
874 _data.aggregate.current = _data._metrics[0].current + _data._metrics[1].current + _data._metrics[2].current;
875 _data.aggregate.voltage = _data.aggregate.current == 0 ? NAN : _data.aggregate.apparentPower / _data.aggregate.current;
876
877 break;
878 }
879
880 default:
881 break;
882 }
883
884 _time = millis();
885
886 if (_callback) {
887 _callback(EventType::EVT_READ, _data);
888 }
889
890 return true;
891}
892
894// readModel
896
897uint16_t Mycila::JSY::readModel(const uint8_t address) {
898 if (!_enabled)
899 return MYCILA_JSY_MK_UNKNOWN;
900
901 LOGD(TAG, "readModel(0x%02X)", address);
902
903 std::lock_guard<std::mutex> lock(_mutex);
904
905#ifdef MYCILA_JSY_DEBUG
906 Serial.printf("[JSY] readModel(0x%02X)\n", address);
907#endif
908
909 memcpy(_buffer, JSY_REQUEST_READ_MODEL, JSY_REQUEST_READ_MODEL_LEN);
910 _send(address, JSY_REQUEST_READ_MODEL_LEN);
911 ReadResult result = _timedRead(address, JSY_RESPONSE_SIZE_READ_MODEL, _baudRate);
912
913 if (result != ReadResult::READ_SUCCESS) {
914 return MYCILA_JSY_MK_UNKNOWN;
915 }
916
917 return (_buffer[JSY_RESPONSE_DATA] << 8) + _buffer[JSY_RESPONSE_DATA + 1];
918}
919
921// readMode / setMode
923
924Mycila::JSY::Mode Mycila::JSY::_readMode(const uint8_t address, const uint16_t model) {
925 if (!_enabled)
926 return Mode::UNKNOWN;
927
928 switch (model) {
929 case MYCILA_JSY_MK_163:
930 case MYCILA_JSY_MK_193:
931 case MYCILA_JSY_MK_194:
932 case MYCILA_JSY_MK_333:
933 return Mode::AC;
934 case MYCILA_JSY_MK_227:
935 case MYCILA_JSY_MK_229:
936 return Mode::DC;
937 case MYCILA_JSY_MK_1031:
938 break; // reads the mode just after
939 default:
940 return Mode::UNKNOWN;
941 }
942
943 LOGD(TAG, "readMode(0x%02X)", address);
944
945 std::lock_guard<std::mutex> lock(_mutex);
946
947#ifdef MYCILA_JSY_DEBUG
948 Serial.printf("[JSY] readMode(0x%02X)\n", address);
949#endif
950
951 memcpy(_buffer, JSY_REQUEST_READ_MODE, JSY_REQUEST_READ_MODE_LEN);
952 _send(address, JSY_REQUEST_READ_MODE_LEN);
953 ReadResult result = _timedRead(address, JSY_RESPONSE_SIZE_READ_MODE, _baudRate);
954
955 if (result != ReadResult::READ_SUCCESS) {
956 return Mode::UNKNOWN;
957 }
958
959 switch (_buffer[JSY_RESPONSE_DATA]) {
960 case JSY_MODE_AC:
961 return Mode::AC;
962 case JSY_MODE_DC:
963 return Mode::DC;
964 default:
965 return Mode::UNKNOWN;
966 }
967}
968
969bool Mycila::JSY::_setMode(const uint8_t address, const uint16_t model, const Mode mode) {
970 if (!_enabled)
971 return false;
972
973 if (mode == Mode::UNKNOWN)
974 return false;
975
976 switch (model) {
977 case MYCILA_JSY_MK_163:
978 case MYCILA_JSY_MK_193:
979 case MYCILA_JSY_MK_194:
980 case MYCILA_JSY_MK_333:
981 return mode == Mode::AC;
982 case MYCILA_JSY_MK_227:
983 case MYCILA_JSY_MK_229:
984 return mode == Mode::DC;
985 case MYCILA_JSY_MK_1031:
986 break; // sets the mode just after
987 default:
988 return false;
989 }
990
991 LOGD(TAG, "setMode(0x%02X) mode=%s", address, mode == Mode::AC ? "AC" : "DC");
992
993 std::lock_guard<std::mutex> lock(_mutex);
994
995#ifdef MYCILA_JSY_DEBUG
996 Serial.printf("[JSY] setMode(0x%02X, %s)\n", address, mode == Mode::AC ? "AC" : "DC");
997#endif
998
999 memcpy(_buffer, JSY_REQUEST_SWITCH_MODE, JSY_REQUEST_SWITCH_MODE_LEN);
1000
1001 _buffer[JSY_REQUEST_SET_MODE] = mode == Mode::AC ? JSY_MODE_AC : JSY_MODE_DC;
1002
1003 _send(address, JSY_REQUEST_SWITCH_MODE_LEN);
1004 ReadResult result = _timedRead(address, JSY_RESPONSE_SIZE_SWITCH_MODE, _baudRate);
1005
1006 return result == ReadResult::READ_SUCCESS;
1007}
1008
1010// resetEnergy
1012
1013bool Mycila::JSY::resetEnergy(const uint8_t address) {
1014 if (!_enabled)
1015 return false;
1016
1017 LOGD(TAG, "resetEnergy(0x%02X)", address);
1018
1019 std::lock_guard<std::mutex> lock(_mutex);
1020
1021#ifdef MYCILA_JSY_DEBUG
1022 Serial.printf("[JSY] resetEnergy(0x%02X)\n", address);
1023#endif
1024
1025 memcpy(_buffer, JSY_REQUEST_RESET_ENERGY, JSY_REQUEST_RESET_ENERGY_LEN);
1026 _send(address, JSY_REQUEST_RESET_ENERGY_LEN);
1027 ReadResult result = _timedRead(address, JSY_RESPONSE_SIZE_RESET_ENERGY, _baudRate);
1028
1029 return result == ReadResult::READ_SUCCESS;
1030}
1031
1033// settings
1035
1036Mycila::JSY::BaudRate Mycila::JSY::getMinAvailableBaudRate() const {
1037 if (!_enabled)
1038 return BaudRate::UNKNOWN;
1039 return getMinAvailableBaudRate(_model);
1040}
1041
1042Mycila::JSY::BaudRate Mycila::JSY::getMinAvailableBaudRate(uint16_t model) {
1043 switch (model) {
1044 case MYCILA_JSY_MK_163:
1045 case MYCILA_JSY_MK_193:
1046 case MYCILA_JSY_MK_194:
1047 case MYCILA_JSY_MK_227:
1048 case MYCILA_JSY_MK_229:
1049 return BaudRate::BAUD_1200;
1050 return BaudRate::BAUD_1200;
1051 case MYCILA_JSY_MK_1031:
1052 case MYCILA_JSY_MK_333:
1053 return BaudRate::BAUD_4800;
1054 default:
1055 return BaudRate::UNKNOWN;
1056 }
1057}
1058
1059Mycila::JSY::BaudRate Mycila::JSY::getMaxAvailableBaudRate() const {
1060 if (!_enabled)
1061 return BaudRate::UNKNOWN;
1062 return getMaxAvailableBaudRate(_model);
1063}
1064
1065Mycila::JSY::BaudRate Mycila::JSY::getMaxAvailableBaudRate(uint16_t model) {
1066 switch (model) {
1067 case MYCILA_JSY_MK_163:
1068 case MYCILA_JSY_MK_227:
1069 case MYCILA_JSY_MK_229:
1070 return BaudRate::BAUD_9600;
1071 case MYCILA_JSY_MK_1031:
1072 case MYCILA_JSY_MK_333:
1073 return BaudRate::BAUD_19200;
1074 case MYCILA_JSY_MK_193:
1075 case MYCILA_JSY_MK_194:
1076 return BaudRate::BAUD_38400;
1077 default:
1078 return BaudRate::UNKNOWN;
1079 }
1080}
1081
1082bool Mycila::JSY::isBaudRateSupported(Mycila::JSY::BaudRate baudRate) const {
1083 if (!_enabled)
1084 return false;
1085 return isBaudRateSupported(_model, baudRate);
1086}
1087
1088bool Mycila::JSY::isBaudRateSupported(uint16_t model, Mycila::JSY::BaudRate baudRate) {
1089 return getMinAvailableBaudRate(model) <= baudRate && baudRate <= getMaxAvailableBaudRate(model);
1090}
1091
1092bool Mycila::JSY::setBaudRate(const uint8_t address, const BaudRate baudRate) {
1093 return _set(address, address ? address : (_lastAddress ? _lastAddress : MYCILA_JSY_ADDRESS_DEFAULT), baudRate);
1094}
1095
1096bool Mycila::JSY::setDeviceAddress(const uint8_t address, const uint8_t newAddress) {
1097 return _set(address, newAddress, _baudRate);
1098}
1099
1100bool Mycila::JSY::_set(const uint8_t address, const uint8_t newAddress, const BaudRate newBaudRate) {
1101 if (!_enabled)
1102 return false;
1103
1104 if (newBaudRate == BaudRate::UNKNOWN)
1105 return false;
1106
1107 if (newAddress == MYCILA_JSY_ADDRESS_UNKNOWN)
1108 return false;
1109
1110 LOGD(TAG, "set(0x%02X) address=0x%02X, bauds=%" PRIu32, address, newAddress, newBaudRate);
1111
1112 std::lock_guard<std::mutex> lock(_mutex);
1113
1114#ifdef MYCILA_JSY_DEBUG
1115 Serial.printf("[JSY] _set(0x%02X)\n", address);
1116#endif
1117
1118 memcpy(_buffer, JSY_REQUEST_SET_COM, JSY_REQUEST_SET_COM_LEN);
1119
1120 // set address
1121 _buffer[JSY_REQUEST_SET_ADDRESS] = newAddress;
1122
1123 // set baud rate ID
1124 switch (newBaudRate) {
1125 case BaudRate::BAUD_1200:
1126 _buffer[JSY_REQUEST_SET_BAUDS] = 0x03;
1127 break;
1128 case BaudRate::BAUD_2400:
1129 _buffer[JSY_REQUEST_SET_BAUDS] = 0x04;
1130 break;
1131 case BaudRate::BAUD_4800:
1132 _buffer[JSY_REQUEST_SET_BAUDS] = 0x05;
1133 break;
1134 case BaudRate::BAUD_9600:
1135 _buffer[JSY_REQUEST_SET_BAUDS] = 0x06;
1136 break;
1137 case BaudRate::BAUD_19200:
1138 _buffer[JSY_REQUEST_SET_BAUDS] = 0x07;
1139 break;
1140 case BaudRate::BAUD_38400:
1141 _buffer[JSY_REQUEST_SET_BAUDS] = 0x08;
1142 break;
1143 default:
1144 assert(false);
1145 break;
1146 }
1147
1148 _send(address, JSY_REQUEST_SET_COM_LEN);
1149 ReadResult result = _timedRead(address, JSY_RESPONSE_SIZE_SET_COM, _baudRate);
1150
1151 // unexpected error ?
1152 if (result != ReadResult::READ_SUCCESS && result != ReadResult::READ_ERROR_ADDRESS) {
1153 return false;
1154 }
1155
1156 // response from unexpected address ?
1157 if (result == ReadResult::READ_ERROR_ADDRESS && _lastAddress != newAddress) {
1158 return false;
1159 }
1160
1161 _openSerial(newBaudRate);
1162
1163 bool success = false;
1164 for (int i = 0; i < MYCILA_JSY_RETRY_COUNT; i++) {
1165 if (_canRead(address, newBaudRate)) {
1166 success = true;
1167 break;
1168 }
1169 }
1170
1171 if (success) {
1172 // update baud rate
1173 _baudRate = newBaudRate;
1174
1175 // update destination address if needed
1176 if (_destinationAddress != MYCILA_JSY_ADDRESS_BROADCAST && _destinationAddress == address) {
1177 _destinationAddress = newAddress;
1178 }
1179
1180 } else {
1181 LOGE(TAG, "Unable to read JSY @ 0x%02X at speed: %" PRIu32, address, newBaudRate);
1182 if (_baudRate != BaudRate::UNKNOWN) {
1183 _openSerial(_baudRate);
1184 }
1185 }
1186
1187 return success;
1188}
1189
1191// toJson
1193
1194#ifdef MYCILA_JSON_SUPPORT
1195void Mycila::JSY::toJson(const JsonObject& root) const {
1196 root["enabled"] = _enabled;
1197 root["time"] = _time;
1198 root["speed"] = _baudRate;
1199 _data.toJson(root);
1200}
1201#endif
1202
1204// I/O
1206
1207bool Mycila::JSY::_canRead(const uint8_t address, BaudRate baudRate) {
1208#ifdef MYCILA_JSY_DEBUG
1209 Serial.printf("[JSY] _canRead(0x%02X)\n", address);
1210#endif
1211 memcpy(_buffer, JSY_REQUEST_READ_MODEL, JSY_REQUEST_READ_MODEL_LEN);
1212 _send(address, JSY_REQUEST_READ_MODEL_LEN);
1213 return _timedRead(address, JSY_RESPONSE_SIZE_READ_MODEL, baudRate) == ReadResult::READ_SUCCESS;
1214}
1215
1216Mycila::JSY::ReadResult Mycila::JSY::_timedRead(const uint8_t expectedAddress, const size_t expectedLen, const BaudRate baudRate) {
1217 size_t count = 0;
1218 while (count < expectedLen) {
1219 size_t read = _serial->readBytes(_buffer + count, expectedLen - count);
1220 if (read) {
1221 count += read;
1222 } else {
1223 break;
1224 }
1225 }
1226
1227#ifdef MYCILA_JSY_DEBUG
1228 Serial.printf("[JSY] timedRead(0x%02X) %d < ", expectedAddress, count);
1229 for (size_t i = 0; i < count; i++) {
1230 Serial.printf("0x%02X ", _buffer[i]);
1231 }
1232 Serial.println();
1233#endif
1234
1235 _drop();
1236
1237 // timeout ?
1238 if (count == 0) {
1239 LOGD(TAG, "timedRead(0x%02X) timeout", expectedAddress);
1240 return ReadResult::READ_TIMEOUT;
1241 }
1242
1243 // check length
1244 if (count != expectedLen) {
1245 LOGD(TAG, "timedRead(0x%02X) error: len %d != %d", expectedAddress, count, expectedLen);
1246 return ReadResult::READ_ERROR_COUNT;
1247 }
1248
1249 // CRC check
1250 uint16_t crc = _crc16(_buffer, expectedLen - 2);
1251 if (_buffer[expectedLen - 2] != LOBYTE(crc) || _buffer[expectedLen - 1] != HIBYTE(crc)) {
1252 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));
1253 return ReadResult::READ_ERROR_CRC;
1254 }
1255
1256 _lastAddress = _buffer[JSY_RESPONSE_ADDRESS];
1257
1258 // address check
1259 if (expectedAddress != MYCILA_JSY_ADDRESS_BROADCAST && expectedAddress != _lastAddress) {
1260 LOGD(TAG, "timedRead(0x%02X) error: wrong device address 0x%02X", expectedAddress, _lastAddress);
1261 return ReadResult::READ_ERROR_ADDRESS;
1262 }
1263
1264 return ReadResult::READ_SUCCESS;
1265}
1266
1267void Mycila::JSY::_send(const uint8_t address, const size_t len) {
1268 // set destination address
1269 _buffer[JSY_REQUEST_ADDRESS] = address;
1270
1271 // crc16
1272 uint16_t crc = _crc16(_buffer, len - 2);
1273 _buffer[len - 2] = LOBYTE(crc);
1274 _buffer[len - 1] = HIBYTE(crc);
1275
1276#ifdef MYCILA_JSY_DEBUG
1277 Serial.printf("[JSY] send(0x%02X) %d > ", address, len);
1278 for (size_t i = 0; i < len; i++) {
1279 Serial.printf("0x%02X ", _buffer[i]);
1280 }
1281 Serial.println();
1282#endif
1283
1284 _serial->flush(false);
1285 _serial->write(_buffer, len);
1286}
1287
1288size_t Mycila::JSY::_drop() {
1289 size_t count = 0;
1290 if (_serial->available()) {
1291#ifdef MYCILA_JSY_DEBUG
1292 Serial.printf("[JSY] drop < ");
1293#endif
1294 while (_serial->available()) {
1295#ifdef MYCILA_JSY_DEBUG
1296 Serial.printf("0x%02X ", _serial->read());
1297#else
1298 _serial->read();
1299#endif
1300 count++;
1301 }
1302#ifdef MYCILA_JSY_DEBUG
1303 Serial.println();
1304#endif
1305 }
1306 return count;
1307}
1308
1309void Mycila::JSY::_openSerial(BaudRate baudRate) {
1310 LOGD(TAG, "openSerial(%" PRIu32 ")", baudRate);
1311 _serial->begin(baudRate, SERIAL_8N1, _pinRX, _pinTX);
1312 _serial->setTimeout(MYCILA_JSY_READ_TIMEOUT_MS);
1313 while (!_serial)
1314 yield();
1315 while (!_serial->availableForWrite())
1316 yield();
1317 _drop();
1318 _serial->flush(false);
1319}
1320
1321Mycila::JSY::BaudRate Mycila::JSY::_detectBauds(const uint8_t address) {
1322 for (int i = 0; i < AUTO_DETECT_BAUD_RATES_COUNT * 2; i++) {
1323 BaudRate baudRate = AUTO_DETECT_BAUD_RATES[i % AUTO_DETECT_BAUD_RATES_COUNT];
1324 LOGD(TAG, "find(0x%02X) %" PRIu32 " bauds", address, baudRate);
1325 _openSerial(baudRate);
1326 for (int j = 0; j < MYCILA_JSY_RETRY_COUNT; j++) {
1327 if (_canRead(address, baudRate)) {
1328 return baudRate;
1329 }
1330 }
1331 }
1332 return BaudRate::UNKNOWN;
1333}
1334
1336// static
1338
1339// For CRC: https://crccalc.com
1340// Select CRC-16/MODBUS
1341inline uint16_t Mycila::JSY::_crc16(const uint8_t* data, size_t len) {
1342 uint16_t crc = 0xFFFF;
1343 while (len--) {
1344 uint8_t temp = *(data++) ^ LOBYTE(crc);
1345 crc = (crc >> 8) ^ pgm_read_word_near(CRCTable + temp);
1346 }
1347 return crc;
1348}
1349
1350uint8_t Mycila::JSY::_register8(const uint8_t* buffer, const uint16_t registerStart, const uint16_t registerSize, const uint16_t registerAddress, uint8_t index) {
1351 return buffer[JSY_RESPONSE_DATA + (registerAddress - registerStart) * registerSize + index];
1352}
1353
1354uint16_t Mycila::JSY::_register16(const uint8_t* buffer, const uint16_t registerStart, const uint16_t registerSize, const uint16_t registerAddress) {
1355 const size_t start = JSY_RESPONSE_DATA + (registerAddress - registerStart) * registerSize;
1356 return (buffer[start] << 8) + buffer[start + 1];
1357}
1358
1359uint32_t Mycila::JSY::_register32(const uint8_t* buffer, const uint16_t registerStart, const uint16_t registerSize, const uint16_t registerAddress) {
1360 const size_t start = JSY_RESPONSE_DATA + (registerAddress - registerStart) * registerSize;
1361 return (buffer[start] << 24) +
1362 (buffer[start + 1] << 16) +
1363 (buffer[start + 2] << 8) +
1364 (buffer[start + 3]);
1365}
1366
1367const char* Mycila::JSY::getModelName(uint16_t model) {
1368 switch (model) {
1369 case MYCILA_JSY_MK_1031:
1370 return MYCILA_JSY_MK_1031_NAME;
1371 case MYCILA_JSY_MK_163:
1372 return MYCILA_JSY_MK_163_NAME;
1373 case MYCILA_JSY_MK_193:
1374 return MYCILA_JSY_MK_193_NAME;
1375 case MYCILA_JSY_MK_194:
1376 return MYCILA_JSY_MK_194_NAME;
1377 case MYCILA_JSY_MK_227:
1378 return MYCILA_JSY_MK_227_NAME;
1379 case MYCILA_JSY_MK_229:
1380 return MYCILA_JSY_MK_229_NAME;
1381 case MYCILA_JSY_MK_333:
1382 return MYCILA_JSY_MK_333_NAME;
1383 default:
1384 return emptyString.c_str();
1385 }
1386}
1387
1388void Mycila::JSY::_jsyTask(void* params) {
1389 JSY* jsy = reinterpret_cast<JSY*>(params);
1390 while (jsy->_enabled) {
1391 if (jsy->read()) {
1392 if (jsy->_pause > 0) {
1393 delay(jsy->_pause);
1394 } else {
1395 yield();
1396 }
1397 } else if (jsy->_pause > 0) {
1398 delay(jsy->_pause);
1399 } else {
1400 delay(10);
1401 }
1402 }
1403 jsy->_taskHandle = NULL;
1404 vTaskDelete(NULL);
1405}
bool read()
Read the JSY values.
Definition MycilaJSY.h:478
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:353
bool setDeviceAddress(uint8_t newAddress)
Set a new address for a device.
Definition MycilaJSY.h:399
bool resetEnergy()
Reset the energy counters of the JSY.
Definition MycilaJSY.h:493
bool setBaudRate(BaudRate baudRate)
Change the baud rate of the JSY.
Definition MycilaJSY.h:512
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:416