forked from dingo35/SmartEVSE-3.5
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.cpp
More file actions
2985 lines (2686 loc) · 121 KB
/
main.cpp
File metadata and controls
2985 lines (2686 loc) · 121 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* This file has shared code between SmartEVSE-3, SmartEVSE-4 and SmartEVSE-4_CH32
* #if SMARTEVSE_VERSION >=30 && SMARTEVSE_VERSION < 40 //SmartEVSEv3 code
* #if SMARTEVSE_VERSION >= 40 //SmartEVSEv4 code
* #ifndef SMARTEVSE_VERSION //CH32 code
*/
//prevent MQTT compiling on CH32
#if defined(MQTT) && !defined(ESP32)
#error "MQTT requires ESP32 to be defined!"
#endif
#include "main.h"
#include "stdio.h"
#include "stdlib.h"
#include "meter.h"
#include "modbus.h"
#include "memory.h" //for memcpy
#include <time.h>
#include "evse_bridge.h"
#include "serial_parser.h"
#include "led_color.h"
#include "session_log.h"
#include "capacity_peak.h"
#ifdef SMARTEVSE_VERSION //ESP32
#define EXT extern
#define _GLCD GLCD()
#include "esp32.h"
#include <ArduinoJson.h>
#include <SPI.h>
#include <Preferences.h>
#include <FS.h>
#include <WiFi.h>
#include "network_common.h"
#include "esp_ota_ops.h"
#include "mbedtls/md_internal.h"
#include <HTTPClient.h>
#include <ESPmDNS.h>
#include <Update.h>
#include <Logging.h>
#include <ModbusServerRTU.h> // Slave/node
#include <ModbusClientRTU.h> // Master
#include <soc/sens_reg.h>
#include <soc/sens_struct.h>
#include <driver/adc.h>
#include <esp_adc_cal.h>
#include "diag_sampler.h"
#include "diag_storage.h"
//OCPP includes
#if ENABLE_OCPP && defined(SMARTEVSE_VERSION) //run OCPP only on ESP32
#include <MicroOcpp.h>
#include <MicroOcppMongooseClient.h>
#include <MicroOcpp/Core/Configuration.h>
#include <MicroOcpp/Core/Context.h>
#include "ocpp_telemetry.h"
#endif //ENABLE_OCPP
extern Preferences preferences;
struct DelayedTimeStruct DelayedStartTime;
struct DelayedTimeStruct DelayedStopTime;
extern unsigned char RFID[8];
extern uint16_t LCDPin;
extern uint8_t PIN_SW_IN, PIN_ACTA, PIN_ACTB, PIN_RCM_FAULT; //these pins have to be assigned dynamically because of hw version v3.1
#else //CH32
#define EXT extern "C"
#define _GLCD // the GLCD doesnt have to be updated on the CH32
#include "ch32.h"
#include "utils.h"
extern "C" {
#include "ch32v003fun.h"
void RCmonCtrl(uint8_t enable);
void delay(uint32_t ms);
void testRCMON(void);
}
extern void CheckRS485Comm(void);
#endif
// Global data
#if !defined(SMARTEVSE_VERSION) || SMARTEVSE_VERSION >=40 //CH32 and v4 ESP32
#if SMARTEVSE_VERSION >= 40 //v4 ESP32
#define RETURN return;
#define CHIP "ESP32"
extern void RecomputeSoC(void);
extern uint8_t modem_state;
#include <qca.h>
#else
#define RETURN
#define CHIP "CH32"
#endif
//CALL_ON_RECEIVE(setStatePowerUnavailable) setStatePowerUnavailable() when setStatePowerUnavailable is received
#define CALL_ON_RECEIVE(X) \
ret = strstr(SerialBuf, #X);\
if (ret) {\
/* printf("@MSG: %s DEBUG CALL_ON_RECEIVE: calling %s().\n", CHIP, #X); */ \
X();\
RETURN \
}
//CALL_ON_RECEIVE_PARAM(State:, setState) calls setState(param) when State:param is received
#define CALL_ON_RECEIVE_PARAM(X,Y) \
ret = strstr(SerialBuf, #X);\
if (ret) {\
/* printf("@MSG: %s DEBUG CALL_ON_RECEIVE_PARAM: calling %s(%u).\n", CHIP, #X, atoi(ret+strlen(#X))); */ \
Y(atoi(ret+strlen(#X)));\
RETURN \
}
//SET_ON_RECEIVE(Pilot:, pilot) sets pilot=parm when Pilot:param is received
#define SET_ON_RECEIVE(X,Y) \
ret = strstr(SerialBuf, #X);\
if (ret) {\
/* printf("@MSG: %s DEBUG SET_ON_RECEIVE: setting %s to %u.\n", CHIP, #Y, atoi(ret+strlen(#X))); */ \
Y = atoi(ret+strlen(#X));\
RETURN \
}
uint8_t RCMTestCounter = 0; // nr of seconds the RCM test is allowed to take
Charging_Protocol_t Charging_Protocol = IEC; // IEC 61851-1 (low-level signaling through PWM), the others are high-level signalling via the modem
#endif
// The following data will be updated by eeprom/storage data at powerup:
uint16_t MaxMains = MAX_MAINS; // Max Mains Amps (hard limit, limited by the MAINS connection) (A)
uint16_t MaxSumMains = MAX_SUMMAINS; // Max Mains Amps summed over all 3 phases, limit used by EU capacity rate
// see https://github.com/serkri/SmartEVSE-3/issues/215
// 0 means disabled, allowed value 10 - 600 A
uint8_t MaxSumMainsTime = MAX_SUMMAINSTIME; // Number of Minutes we wait when MaxSumMains is exceeded, before we stop charging
uint16_t MaxSumMainsTimer = 0;
uint16_t GridRelayMaxSumMains = GRID_RELAY_MAX_SUMMAINS; // Max Mains Amps summed over all 3 phases, switched by relay provided by energy provider
// Meant to obey par 14a of Energy Industry Act, where the provider can switch a device
// down to 4.2kW by a relay connected to the "switch" connectors.
// you will have to set the "Switch" setting to "GridRelay",
// and connect the relay to the switch terminals
// When the relay opens its contacts, power will be reduced to 4.2kW
// The relay is only allowed on the Master
bool GridRelayOpen = false; // The read status of the relay
bool CustomButton = false; // The status of the custom button
bool MqttButtonState = false; // The status of the button send via MQTT
uint16_t MaxCurrent = MAX_CURRENT; // Max Charge current (A)
uint16_t MinCurrent = MIN_CURRENT; // Minimal current the EV is happy with (A)
uint8_t Mode = MODE; // EVSE mode (0:Normal / 1:Smart / 2:Solar)
uint32_t CurrentPWM = 0; // Current PWM duty cycle value (0 - 1024)
bool CPDutyOverride = false;
uint8_t Lock = LOCK; // Cable lock device (0:Disable / 1:Solenoid / 2:Motor)
uint8_t CableLock = CABLE_LOCK; // 0 = Disabled (default), 1 = Enabled; when enabled the cable is locked at all times, when disabled only when STATE != A
uint16_t MaxCircuit = MAX_CIRCUIT; // Max current of the EVSE circuit (A)
uint8_t Config = CONFIG; // Configuration (0:Socket / 1:Fixed Cable)
uint8_t LoadBl = LOADBL; // Load Balance Setting (0:Disable / 1:Master / 2-8:Node)
uint8_t Switch = SWITCH; // External Switch (0:Disable / 1:Access B / 2:Access S /
// 3:Smart-Solar B / 4:Smart-Solar S / 5: Grid Relay
// 6:Custom B / 7:Custom S)
// B=momentary push <B>utton, S=toggle <S>witch
uint8_t AutoUpdate = AUTOUPDATE; // Automatic Firmware Update (0:Disable / 1:Enable)
uint16_t StartCurrent = START_CURRENT;
uint16_t StopTime = STOP_TIME;
uint16_t ImportCurrent = IMPORT_CURRENT;
uint8_t Grid = GRID; // Type of Grid connected to Sensorbox (0:4Wire / 1:3Wire )
uint8_t SB2_WIFImode = SB2_WIFI_MODE; // Sensorbox-2 WiFi Mode (0:Disabled / 1:Enabled / 2:Start Portal)
uint8_t RFIDReader = RFID_READER; // RFID Reader (0:Disabled / 1:Enabled / 2:Enable One / 3:Learn / 4:Delete / 5:Delete All / 6: Remote via OCPP)
#if FAKE_RFID
uint8_t Show_RFID = 0;
#endif
EnableC2_t EnableC2 = ENABLE_C2; // CONTACT 2 menu setting, can be set to: NOT_PRESENT, ALWAYS_OFF, SOLAR_OFF, ALWAYS_ON, AUTO
uint16_t maxTemp = MAX_TEMPERATURE;
// Priority scheduling settings (Master only, when LoadBl=1)
uint8_t PrioStrategy = PRIO_MODBUS_ADDR; // Priority strategy (0:Modbus Address / 1:First Connected / 2:Last Connected)
uint16_t RotationInterval = 0; // Rotation interval in minutes (0=disabled, 30-1440)
uint16_t IdleTimeout = 60; // Idle timeout in seconds (30-300)
uint32_t ConnectedTime[NR_EVSES] = {0, 0, 0, 0, 0, 0, 0, 0}; // Uptime when each EVSE entered STATE_C
uint8_t ScheduleState[NR_EVSES] = {0, 0, 0, 0, 0, 0, 0, 0}; // Scheduling state per EVSE (0:Inactive / 1:Active / 2:Paused)
uint16_t RotationTimer = 0; // Countdown timer for rotation (seconds)
// Capacity tariff peak tracking (Belgian capaciteitstarief / German §14a)
capacity_state_t CapacityState;
int16_t CapacityHeadroom_da = INT16_MAX; // Headroom in deciamps; INT16_MAX = unconstrained
uint16_t CapacityLimit = 0; // User setting: capacity limit in watts (0 = disabled)
Meter MainsMeter(MAINS_METER, MAINS_METER_ADDRESS, COMM_TIMEOUT);
Meter EVMeter(EV_METER, EV_METER_ADDRESS, COMM_EVTIMEOUT);
Meter CircuitMeter(CIRCUIT_METER, CIRCUIT_METER_ADDRESS, COMM_TIMEOUT);
uint16_t MaxCircuitMains = MAX_CIRCUIT_MAINS; // Max current of the subpanel circuit (A), 0 = disabled
uint8_t Nr_Of_Phases_Charging = 3; // Nr of phases we are charging with. Set to 1 or 3, depending on the CONTACT 2 setting, and the MODE we are in.
Switch_Phase_t Switching_Phases_C2 = NO_SWITCH; // Switching between 1P and 3P with the second contactor output, depends on the CONTACT 2 setting, and the MODE.
uint8_t State = STATE_A;
uint8_t ErrorFlags;
uint8_t pilot;
uint16_t MaxCapacity; // Cable limit (A) (limited by the wire in the charge cable, set automatically, or manually if Config=Fixed Cable)
uint16_t ChargeCurrent; // Calculated Charge Current (Amps *10)
uint16_t OverrideCurrent = 0; // Temporary assigned current (Amps *10) (modbus)
int16_t Isum = 0; // Sum of all measured Phases (Amps *10) (can be negative)
// Load Balance variables
int16_t IsetBalanced = 0; // Max calculated current (Amps *10) available for all EVSE's
uint16_t Balanced[NR_EVSES] = {0, 0, 0, 0, 0, 0, 0, 0}; // Amps value per EVSE
#if !defined(SMARTEVSE_VERSION) || SMARTEVSE_VERSION >=30 && SMARTEVSE_VERSION < 40 //CH32 and v3 ESP32
uint16_t BalancedMax[NR_EVSES] = {0, 0, 0, 0, 0, 0, 0, 0}; // Max Amps value per EVSE
uint8_t BalancedState[NR_EVSES] = {0, 0, 0, 0, 0, 0, 0, 0}; // State of all EVSE's 0=not active (state A), 1=charge request (State B), 2= Charging (State C)
uint16_t BalancedError[NR_EVSES] = {0, 0, 0, 0, 0, 0, 0, 0}; // Error state of EVSE
Node_t Node[NR_EVSES] = { // 0: Master / 1: Node 1 ...
/* Config EV EV Min Used Charge Interval Solar * // Interval Time : last Charge time, reset when not charging
* Online, Changed, Meter, Address, Current, Phases, Timer, Timer, Timer, Mode */ // Min Current : minimal measured current per phase the EV consumes when starting to charge @ 6A (can be lower then 6A)
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Used Phases : detected nr of phases when starting to charge (works with configured EVmeter meter, and might work with sensorbox)
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }
};
void ModbusRequestLoop(void);
uint8_t Force_Single_Phase_Charging(void);
uint8_t C1Timer = 0;
uint8_t ModemStage = 0; // 0: Modem states will be executed when Modem is enabled 1: Modem stages will be skipped, as SoC is already extracted
int8_t DisconnectTimeCounter = -1; // Count for how long we're disconnected, so we can more reliably throw disconnect event. -1 means counter is disabled
uint8_t ToModemWaitStateTimer = 0; // Timer used from STATE_MODEM_REQUEST to STATE_MODEM_WAIT
uint8_t ToModemDoneStateTimer = 0; // Timer used from STATE_MODEM_WAIT to STATE_MODEM_DONE
uint8_t LeaveModemDoneStateTimer = 0; // Timer used from STATE_MODEM_DONE to other, usually STATE_B
uint8_t LeaveModemDeniedStateTimer = 0; // Timer used from STATE_MODEM_DENIED to STATE_B to re-try authentication
uint8_t ModbusRequest = 0; // Flag to request Modbus information
bool PilotDisconnected = false;
uint8_t PilotDisconnectTime = 0; // Time the Control Pilot line should be disconnected (Sec)
#endif
uint8_t AccessTimer = 0; //FIXME ESP32 vs CH32
int8_t TempEVSE = 0; // Temperature EVSE in deg C (-50 to +125)
uint8_t ButtonState = 0x07; // Holds latest push Buttons state (LSB 2:0)
uint8_t OldButtonState = 0x07; // Holds previous push Buttons state (LSB 2:0)
uint8_t LCDNav = 0;
uint8_t SubMenu = 0;
uint8_t ChargeDelay = 0; // Delays charging at least 60 seconds in case of not enough current available.
uint8_t NoCurrent = 0; // counts overcurrent situations.
uint8_t TestState = 0;
uint8_t NodeNewMode = 0;
AccessStatus_t AccessStatus = OFF; // 0: OFF, 1: ON, 2: PAUSE
uint8_t ConfigChanged = 0;
uint16_t SolarStopTimer = 0;
#ifdef SMARTEVSE_VERSION //ESP32 v3 and v4
uint8_t RCmon = RC_MON; // Residual Current Monitor (0:Disable / 1:Enable)
uint8_t DelayedRepeat; // 0 = no repeat, 1 = daily repeat
uint8_t LCDlock = LCD_LOCK; // 0 = LCD buttons operational, 1 = LCD buttons disabled
uint16_t BacklightTimer = 0; // Backlight timer (sec)
uint8_t BacklightSet = 0;
uint8_t LCDTimer = 0;
uint16_t CardOffset = CARD_OFFSET; // RFID card used in Enable One mode
uint8_t RFIDstatus = 0;
EXT hw_timer_t * timerA;
esp_adc_cal_characteristics_t * adc_chars_CP;
#endif
uint8_t ActivationMode = 0, ActivationTimer = 0;
volatile uint16_t adcsample = 0;
volatile uint16_t ADCsamples[25]; // declared volatile, as they are used in a ISR
volatile uint8_t sampleidx = 0;
char str[20];
extern volatile uint16_t ADC_CP[NUM_ADC_SAMPLES];
int phasesLastUpdate = 0;
bool phasesLastUpdateFlag = false;
int16_t IrmsOriginal[3]={0, 0, 0};
int16_t homeBatteryCurrent = 0;
time_t homeBatteryLastUpdate = 0; // Time in seconds since epoch
// set by EXTERNAL logic through MQTT/REST to indicate cheap tariffs ahead until unix time indicated
uint8_t ColorOff[3] = {0, 0, 0}; // off
uint8_t ColorNormal[3] = {0, 255, 0}; // Green
uint8_t ColorSmart[3] = {0, 255, 0}; // Green
uint8_t ColorSolar[3] = {255, 170, 0}; // Orange
uint8_t ColorCustom[3] = {0, 0, 255}; // Blue
//#define FW_UPDATE_DELAY 30 //DINGO TODO // time between detection of new version and actual update in seconds
#define FW_UPDATE_DELAY 3600 // time between detection of new version and actual update in seconds
uint16_t firmwareUpdateTimer = 0; // timer for firmware updates in seconds, max 0xffff = approx 18 hours
// 0 means timer inactive
// 0 < timer < FW_UPDATE_DELAY means we are in countdown for an actual update
// FW_UPDATE_DELAY <= timer <= 0xffff means we are in countdown for checking
// whether an update is necessary
#if ENABLE_OCPP && defined(SMARTEVSE_VERSION) //run OCPP only on ESP32
uint8_t OcppMode = OCPP_MODE; //OCPP Client mode. 0:Disable / 1:Enable
unsigned char OcppRfidUuid [7];
size_t OcppRfidUuidLen;
unsigned long OcppLastRfidUpdate;
unsigned long OcppTrackLastRfidUpdate;
bool OcppForcesLock = false;
std::shared_ptr<MicroOcpp::Configuration> OcppUnlockConnectorOnEVSideDisconnect; // OCPP Config for RFID-based transactions: if false, demand same RFID card again to unlock connector
std::shared_ptr<MicroOcpp::Transaction> OcppLockingTx; // Transaction which locks connector until same RFID card is presented again
bool OcppTrackPermitsCharge = false;
bool OcppTrackAccessBit = false;
uint8_t OcppTrackCPvoltage = PILOT_NOK; //track positive part of CP signal for OCPP transaction logic
MicroOcpp::MOcppMongooseClient *OcppWsClient;
float OcppCurrentLimit = -1.f; // Negative value: no OCPP limit defined
bool OcppWasStandalone = false; // Tracks LoadBl state at ocppInit() time for LB exclusivity check
ocpp_telemetry_t OcppTelemetry; // OCPP connection and transaction telemetry
unsigned long OcppStopReadingSyncTime; // Stop value synchronization: delay StopTransaction by a few seconds so it reports an accurate energy reading
bool OcppDefinedTxNotification;
MicroOcpp::TxNotification OcppTrackTxNotification;
unsigned long OcppLastTxNotification;
#endif //ENABLE_OCPP
EXT uint32_t elapsedmax, elapsedtime;
//functions
EXT void setup();
EXT void setState(uint8_t NewState);
EXT void setErrorFlags(uint8_t flags);
EXT int8_t TemperatureSensor();
uint8_t OneWireReadCardId();
EXT uint8_t ProximityPin();
EXT void PowerPanicCtrl(uint8_t enable);
EXT uint8_t ReadESPdata(char *buf);
extern void requestEnergyMeasurement(uint8_t Meter, uint8_t Address, bool Export);
extern bool requestPhaseEnergyMeasurement(uint8_t Meter, uint8_t Address);
extern void requestNodeConfig(uint8_t NodeNr);
extern void requestPowerMeasurement(uint8_t Meter, uint8_t Address, uint16_t PRegister);
extern void requestNodeStatus(uint8_t NodeNr);
extern uint8_t processAllNodeStates(uint8_t NodeNr);
extern void BroadcastCurrent(void);
extern void CheckRFID(void);
extern void mqttPublishData();
extern void mqttSmartEVSEPublishData();
extern void mqttPublishSolarDebug(void);
extern void mqttPublishSessionComplete(void);
extern bool MQTTclientSmartEVSE_AppConnected;
extern void DisconnectEvent(void);
extern char EVCCID[32];
extern char RequiredEVCCID[32];
extern bool CPDutyOverride;
extern uint8_t ModbusRequest;
extern unsigned char ease8InOutQuad(unsigned char i);
extern unsigned char triwave8(unsigned char in);
extern const char StrStateName[15][13] = {"A", "B", "C", "D", "COMM_B", "COMM_B_OK", "COMM_C", "COMM_C_OK", "Activate", "B1", "C1", "MODEM_REQ", "MODEM_WAIT", "MODEM_DONE", "MODEM_DENIED"}; //note that the extern is necessary here because the const will point the compiler to internal linkage; https://cplusplus.com/forum/general/81640/
extern const char StrEnableC2[5][12] = { "Not present", "Always Off", "Solar Off", "Always On", "Auto" };
//TODO perhaps move those routines from modbus to main?
extern void ReadItemValueResponse(void);
extern void WriteItemValueResponse(void);
extern void WriteMultipleItemValueResponse(void);
uint8_t ModbusRx[256]; // Modbus Receive buffer
//constructor
Button::Button(void) {
// in case of a press button, we do nothing
// in case of a toggle switch, we have to check the switch position since it might have been changed
// since last powerup
// 0 1 2 3 4 5 6 7
// "Disabled", "Access B", "Access S", "Sma-Sol B", "Sma-Sol S", "Grid Relay", "Custom B", "Custom S"
CheckSwitch(true);
}
//since in v4 ESP32 only a copy of ErrorFlags is available, we need to have functions so v4 ESP32 can set CH32 ErrorFlags
void setErrorFlags(uint8_t flags) {
#if !defined(SMARTEVSE_VERSION) || SMARTEVSE_VERSION >=30 && SMARTEVSE_VERSION < 40
evse_bridge_lock();
evse_sync_globals_to_ctx();
evse_set_error_flags(&g_evse_ctx, flags);
evse_sync_ctx_to_globals();
evse_bridge_unlock();
#else
ErrorFlags |= flags;
#endif
#if SMARTEVSE_VERSION >= 40 //v4 ESP32
Serial1.printf("@setErrorFlags:%u\n", flags);
#endif
}
void clearErrorFlags(uint8_t flags) {
#if !defined(SMARTEVSE_VERSION) || SMARTEVSE_VERSION >=30 && SMARTEVSE_VERSION < 40
evse_bridge_lock();
evse_sync_globals_to_ctx();
evse_clear_error_flags(&g_evse_ctx, flags);
evse_sync_ctx_to_globals();
evse_bridge_unlock();
#else
ErrorFlags &= ~flags;
#endif
#if SMARTEVSE_VERSION >= 40 //v4 ESP32
Serial1.printf("@clearErrorFlags:%u\n", flags);
#endif
}
// ChargeDelay owned by CH32 so ESP32 gets a copy
void setChargeDelay(uint8_t delay) {
#if SMARTEVSE_VERSION >= 40 //v4 ESP32
Serial1.printf("@ChargeDelay:%u\n", delay);
#else
ChargeDelay = delay;
#endif
}
#ifndef SMARTEVSE_VERSION //CH32 version
void Button::HandleSwitch(void) {
printf("@ExtSwitch:%u.\n", Pressed);
}
#else //v3 and v4
void Button::HandleSwitch(void)
{
if (Pressed) {
// Switch input pulled low
switch (Switch) {
case 1: // Access Button
setAccess(AccessStatus == ON ? OFF : ON); // Toggle AccessStatus OFF->ON->OFF (old behaviour) or PAUSE->ON
_LOG_I("Access: %d\n", AccessStatus);
MqttButtonState = !MqttButtonState;
break;
case 2: // Access Switch
setAccess(ON);
MqttButtonState = true;
break;
case 3: // Smart-Solar Button
MqttButtonState = true;
break;
case 4: // Smart-Solar Switch
if (Mode == MODE_SOLAR && AccessStatus == ON) {
setMode(MODE_SMART);
}
MqttButtonState = true;
break;
case 5: // Grid relay
GridRelayOpen = false;
MqttButtonState = true;
break;
case 6: // Custom button B
CustomButton = !CustomButton;
MqttButtonState = CustomButton;
break;
case 7: // Custom button S
CustomButton = true;
MqttButtonState = CustomButton;
break;
default:
if (State == STATE_C) { // Menu option Access is set to Disabled
setState(STATE_C1);
if (!TestState) setChargeDelay(15); // Keep in State B for 15 seconds, so the Charge cable can be removed.
}
break;
}
#if MQTT
MQTTclient.publish(MQTTprefix + "/CustomButton", MqttButtonState ? "On" : "Off", false, 0);
#endif
// Reset RCM error when switch is pressed/toggled
// RCM was tripped, but RCM level is back to normal
if ((ErrorFlags & RCM_TRIPPED) && (digitalRead(PIN_RCM_FAULT) == LOW || RCmon == 0)) {
clearErrorFlags(RCM_TRIPPED);
}
// Also light up the LCD backlight
BacklightTimer = BACKLIGHT; // Backlight ON
} else {
// Switch input released
uint32_t tmpMillis = millis();
switch (Switch) {
case 2: // Access Switch
setAccess(OFF);
MqttButtonState = false;
break;
case 3: // Smart-Solar Button
if ((tmpMillis < TimeOfPress + 1500) && AccessStatus == ON) { // short press
if (Mode == MODE_SMART) {
setMode(MODE_SOLAR);
} else if (Mode == MODE_SOLAR) {
setMode(MODE_SMART);
}
ErrorFlags &= ~(LESS_6A); // Clear All errors
ChargeDelay = 0; // Clear any Chargedelay
setSolarStopTimer(0); // Also make sure the SolarTimer is disabled.
MaxSumMainsTimer = 0;
LCDTimer = 0;
}
MqttButtonState = false;
break;
case 4: // Smart-Solar Switch
if (Mode == MODE_SMART && AccessStatus == ON) setMode(MODE_SOLAR);
MqttButtonState = false;
break;
case 5: // Grid relay
GridRelayOpen = true;
MqttButtonState = false;
break;
case 6: // Custom button B
break;
case 7: // Custom button S
CustomButton = false;
MqttButtonState = CustomButton;
break;
default:
break;
}
#if MQTT
MQTTclient.publish(MQTTprefix + "/CustomButton", MqttButtonState ? "On" : "Off", false, 0);
MQTTclient.publish(MQTTprefix + "/CustomButtonPressTime", (tmpMillis - TimeOfPress), false, 0);
#endif
}
}
#endif
void Button::CheckSwitch(bool force) {
#if SMARTEVSE_VERSION >=30 && SMARTEVSE_VERSION < 40
uint8_t Read = digitalRead(PIN_SW_IN);
#endif
#ifndef SMARTEVSE_VERSION //CH32
uint8_t Read = funDigitalRead(SW_IN) && funDigitalRead(BUT_SW_IN); // BUT_SW_IN = LED pushbutton, SW_IN = 12pin plug at bottom
#endif
#if !defined(SMARTEVSE_VERSION) || SMARTEVSE_VERSION >=30 && SMARTEVSE_VERSION < 40 //CH32 and v3 ESP32
static uint8_t RB2count = 0, RB2last = 2;
if (force) // force to read switch position
RB2last = 2;
if ((RB2last == 2) && (Switch == 1 || Switch == 3 || Switch == 6)) // upon initialization we want the toggle switch to be read
RB2last = 1; // but not the push buttons, because this would toggle the state
// upon reboot
// External switch changed state?
if (Read != RB2last) {
// make sure that noise on the input does not switch
if (RB2count++ > 10) {
RB2last = Read;
Pressed = !RB2last;
if (Pressed)
TimeOfPress = millis();
HandleSwitch();
RB2count = 0;
}
} else { // no change in key....
RB2count = 0;
if (Pressed && Switch == 3 && millis() > TimeOfPress + 1500) {
if (State == STATE_C) {
setState(STATE_C1);
if (!TestState) setChargeDelay(15); // Keep in State B for 15 seconds, so the Charge cable can be removed.
}
}
}
#endif
}
Button ExtSwitch;
//similar to setAccess; OverrideCurrent owned by ESP32
void setOverrideCurrent(uint16_t Current) { //c
#ifdef SMARTEVSE_VERSION //v3 and v4
OverrideCurrent = Current;
SEND_TO_CH32(OverrideCurrent)
//write_settings TODO doesnt include OverrideCurrent
#if MQTT
// Update MQTT faster
lastMqttUpdate = 10;
#endif //MQTT
#else //CH32
SEND_TO_ESP32(OverrideCurrent)
#endif //SMARTEVSE_VERSION
}
/**
* Check if we can switch to 1 or 3 phase charging, depending on the Enable C2 setting
*/
void CheckSwitchingPhases(void) {
evse_bridge_lock();
evse_sync_globals_to_ctx();
evse_check_switching_phases(&g_evse_ctx);
evse_sync_ctx_to_globals();
evse_bridge_unlock();
_LOG_D("NrPhasesCharging:%u\n",Nr_Of_Phases_Charging);
}
/**
* Set EVSE mode
*
* @param uint8_t Mode
*/
void setMode(uint8_t NewMode) {
#ifdef SMARTEVSE_VERSION //v3 and v4
if (NewMode > MODE_SOLAR) { //this should never happen
_LOG_A("ERROR: setMode tries to set Mode to %u.\n", NewMode);
return;
}
// If mainsmeter disabled we can only run in Normal Mode, unless we are a Node
if (LoadBl <2 && !MainsMeter.Type && NewMode != MODE_NORMAL)
return;
// Take care of extra conditionals/checks for custom features
setAccess(DelayedStartTime.epoch2 ? OFF : ON); //if DelayedStartTime not zero then we are Delayed Charging
if (NewMode == MODE_SOLAR) {
// Reset OverrideCurrent if mode is SOLAR
setOverrideCurrent(0);
}
// when switching modes, we just keep charging at the phases we were charging at;
// it's only the regulation algorithm that is changing...
// EXCEPT when EnableC2 == Solar Off, because we would expect C2 to be off when in Solar Mode and EnableC2 == Solar Off
// and also the other way around, multiple phases might be wanted when changing from Solar to Normal or Smart
if (EnableC2 == SOLAR_OFF) {
if ((Mode != MODE_SOLAR && NewMode == MODE_SOLAR) || (Mode == MODE_SOLAR && NewMode != MODE_SOLAR)) {
// Set State to C1 or B1 to make sure CP is disconnected for 5 seconds, before switching contactors on/off
if (State == STATE_C) setState(STATE_C1);
else if (State != STATE_C1 && State == STATE_B) setState(STATE_B1);
_LOG_A("Disconnect CP when switching C2\n");
}
}
// similar to the above, when switching between solar charging at 1P and mode change, we need to switch back to 3P
// TODO make sure that Smart 3P -> Solar 1P also disconnects
if ((EnableC2 == AUTO) && (Mode != NewMode) && (Mode == MODE_SOLAR) && (Nr_Of_Phases_Charging == 1) ) {
// Set State to C1 or B1 to make sure CP is disconnected for 5 seconds, before switching contactors on/off
if (State == STATE_C) setState(STATE_C1);
else if (State != STATE_C1 && State == STATE_B) setState(STATE_B1);
_LOG_A("AUTO Solar->Smart/Normal charging 1p->3p\n");
}
// Also check all other switching options
CheckSwitchingPhases();
#if MQTT
// Update MQTT faster
lastMqttUpdate = 10;
#endif
if (NewMode == MODE_SMART) { // the smart-solar button used to clear all those flags toggling between those modes
clearErrorFlags(LESS_6A); // Clear All errors
setSolarStopTimer(0); // Also make sure the SolarTimer is disabled.
MaxSumMainsTimer = 0;
}
setChargeDelay(0); // Clear any Chargedelay
BacklightTimer = BACKLIGHT; // Backlight ON
if (Mode != NewMode) NodeNewMode = NewMode + 1;
Mode = NewMode;
SEND_TO_CH32(Mode); //d
//make mode and start/stoptimes persistent on reboot
request_write_settings();
#else //CH32
printf("@Mode:%u.\n", NewMode); //a
_LOG_V("[<-] Mode:%u\n", NewMode);
#endif //SMARTEVSE_VERSION
}
/**
* Set the solar stop timer
*
* @param unsigned int Timer (seconds)
*/
void setSolarStopTimer(uint16_t Timer) {
if (SolarStopTimer == Timer)
return; // prevent unnecessary publishing of SolarStopTimer
SolarStopTimer = Timer;
SEND_TO_ESP32(SolarStopTimer);
SEND_TO_CH32(SolarStopTimer);
#if MQTT
MQTTclient.publish(MQTTprefix + "/SolarStopTimer", SolarStopTimer, false, 0);
#endif
}
#if !defined(SMARTEVSE_VERSION) || SMARTEVSE_VERSION >=30 && SMARTEVSE_VERSION < 40 //CH32 and v3 ESP32
/**
* Checks all parameters to determine whether
* we are going to force single phase charging
* Returns true if we are going to do single phase charging
* Returns false if we are going to do (traditional) 3 phase charging
* This is only relevant on a 3P mains and 3P car installation!
* 1P car will always charge 1P undetermined by CONTACTOR2
*/
uint8_t Force_Single_Phase_Charging() {
evse_bridge_lock();
evse_sync_globals_to_ctx();
uint8_t result = evse_force_single_phase(&g_evse_ctx);
evse_bridge_unlock();
return result;
}
#endif
// Write duty cycle to pin
// Value in range 0 (0% duty) to 1024 (100% duty) for ESP32, 1000 (100% duty) for CH32
void SetCPDuty(uint32_t DutyCycle){
#if SMARTEVSE_VERSION >= 40 //ESP32
Serial1.printf("@SetCPDuty:%u\n", DutyCycle);
#else //CH32 and v3 ESP32
#if SMARTEVSE_VERSION >=30 && SMARTEVSE_VERSION < 40 //v3 ESP32
ledcWrite(CP_CHANNEL, DutyCycle); // update PWM signal
#if DIAG_LOG
uint32_t readback = ledcRead(CP_CHANNEL);
if (readback != DutyCycle) {
_LOG_A("SetCPDuty MISMATCH: wrote %u, read %u\n", DutyCycle, readback);
}
#endif
#endif
#ifndef SMARTEVSE_VERSION //CH32
// update PWM signal
TIM1->CH1CVR = DutyCycle;
#endif
#endif //v4
CurrentPWM = DutyCycle;
}
// Set Charge Current
// Current in Amps * 10 (160 = 16A)
void SetCurrent(uint16_t current) {
#if SMARTEVSE_VERSION >= 40 //ESP32
Serial1.printf("@SetCurrent:%u\n", current);
#else
uint32_t DutyCycle;
if ((current >= (MIN_CURRENT * 10)) && (current <= 510)) DutyCycle = current / 0.6;
// calculate DutyCycle from current
else if ((current > 510) && (current <= 800)) DutyCycle = (current / 2.5) + 640;
else DutyCycle = 100; // invalid, use 6A
#if SMARTEVSE_VERSION >=30 && SMARTEVSE_VERSION < 40 //v3 ESP32
DutyCycle = DutyCycle * 1024 / 1000; // conversion to 1024 = 100%
#endif
#if DIAG_LOG
_LOG_A("SetCurrent(%u) duty=%u\n", current, DutyCycle);
#endif
SetCPDuty(DutyCycle);
#endif
}
void setStatePowerUnavailable(void) {
#if !defined(SMARTEVSE_VERSION) || SMARTEVSE_VERSION >=30 && SMARTEVSE_VERSION < 40 //CH32 and v3 ESP32
evse_bridge_lock();
evse_sync_globals_to_ctx();
evse_set_power_unavailable(&g_evse_ctx);
evse_sync_ctx_to_globals();
evse_bridge_unlock();
#else //v4 ESP32
printf("@setStatePowerUnavailable\n");
#endif
}
//this replaces old CP_OFF and CP_ON and PILOT_CONNECTED and PILOT_DISCONNECTED macros
//setPilot(true) switches the PILOT ON (CONNECT), setPilot(false) switches it OFF
void setPilot(bool On) {
if (On) {
#if SMARTEVSE_VERSION >=30 && SMARTEVSE_VERSION < 40 //ESP32 v3
digitalWrite(PIN_CPOFF, LOW);
} else
digitalWrite(PIN_CPOFF, HIGH);
#endif
#ifndef SMARTEVSE_VERSION //CH32
funDigitalWrite(CPOFF, FUN_LOW);
} else
funDigitalWrite(CPOFF, FUN_HIGH);
#endif
#if SMARTEVSE_VERSION >=40 //ESP32 v4
Serial1.printf("@setPilot:%u\n", On);
}
#endif
}
// State is owned by the CH32
// because it is highly subject to machine interaction
// and also charging is supposed to function if ESP32 is hung/rebooted
// If the CH32 wants to change that variable, it calls setState
// which sends a message to the ESP32. No other function may change State!
// If the ESP32 wants to change the State it sends a message to CH32
// and if the change is honored, the CH32 sends an update
// to the CH32 through the setState routine
// So the setState code of the CH32 is the only routine that
// is allowed to change the value of State on CH32
// All other code has to use setState
// so for v4 we need:
// a. ESP32 setState sends message to CH32 in ESP32 src/main.cpp (this file)
// b. CH32 receiver that calls local setState in CH32 src/evse.c
// c. CH32 setState full functionality in ESP32 src/main.cpp (this file) to be copied to CH32
// d. CH32 sends message to ESP32 in ESP32 src/main.cpp (this file) to be copied to CH32
// e. ESP32 receiver that sets local variable in ESP32 src/main.cpp
void setState(uint8_t NewState) { //c
#if SMARTEVSE_VERSION >= 40
if (State != NewState) {
char Str[50];
snprintf(Str, sizeof(Str), "%02d:%02d:%02d STATE %s -> %s\n",timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec, StrStateName[State], StrStateName[NewState] );
_LOG_A("%s",Str);
Serial1.printf("@State:%u\n", NewState); //a
}
#endif
#if !defined(SMARTEVSE_VERSION) || SMARTEVSE_VERSION >=30 && SMARTEVSE_VERSION < 40 //CH32 and v3 ESP32
// Core state machine logic via module; callback handles all post-actions
evse_bridge_lock();
evse_sync_globals_to_ctx();
evse_set_state(&g_evse_ctx, NewState);
evse_sync_ctx_to_globals();
evse_bridge_unlock();
#endif
}
// make it possible to call setAccess with an int parameter
void setAccess(uint8_t Access) { //c
AccessStatus_t typed = (AccessStatus_t) Access;
setAccess(typed);
}
// the Access_bit is owned by the ESP32
// because it is highly subject to human interaction
// and also its status is supposed to get saved in NVS
// so if the CH32 wants to change that variable,
// it sends a message to the ESP32
// and if the change is honored, the ESP32 sends an update
// to the CH32 through the ConfigItem routine
// So the receiving code of the CH32 is the only routine that
// is allowed to change the value of Acces_bit on CH32
// All other code has to use setAccess
// so for v4 we need:
// a. CH32 setAccess sends message to ESP32 in CH32 src/evse.c and/or in src/main.cpp (this file)
// b. ESP32 receiver that calls local setAccess in ESP32 src/main.cpp
// c. ESP32 setAccess full functionality in ESP32 src/main.cpp (this file)
// d. ESP32 sends message to CH32 in ESP32 src/main.cpp (this file)
// e. CH32 receiver that sets local variable in CH32 src/evse.c
// same for Mode/setMode
void setAccess(AccessStatus_t Access) { //c
#ifdef SMARTEVSE_VERSION //v3 and v4
#if SMARTEVSE_VERSION >= 40
Serial1.printf("@Access:%u\n", (uint8_t)Access); //d
#endif
#if SMARTEVSE_VERSION >=30 && SMARTEVSE_VERSION < 40
// Bridge to module; state transitions trigger callback automatically
evse_bridge_lock();
evse_sync_globals_to_ctx();
evse_set_access(&g_evse_ctx, Access);
evse_sync_ctx_to_globals();
evse_bridge_unlock();
#else
AccessStatus = Access;
if (Access == OFF || Access == PAUSE) {
if (State == STATE_C) setState(STATE_C1);
else if (State != STATE_C1 && (State == STATE_B || State == STATE_MODEM_REQUEST || State == STATE_MODEM_WAIT || State == STATE_MODEM_DONE || State == STATE_MODEM_DENIED)) setState(STATE_B1);
}
#endif
//make AccessStatus and CardOffset persistent on reboot
request_write_settings();
#if MQTT
// Update MQTT faster
lastMqttUpdate = 10;
#endif //MQTT
#else //CH32
SEND_TO_ESP32(Access) //a
#endif //SMARTEVSE_VERSION
}
#ifndef SMARTEVSE_VERSION //CH32
// Determine the state of the Pilot signal
//
uint8_t Pilot() {
uint16_t sample, Min = 4095, Max = 0;
uint8_t n, ret;
static uint8_t old_pilot = 255;
// calculate Min/Max of last 32 CP measurements (32 ms)
for (n=0 ; n<NUM_ADC_SAMPLES ;n++) {
sample = ADC_CP[n];
if (sample < Min) Min = sample; // store lowest value
if (sample > Max) Max = sample; // store highest value
}
//printf("@MSG: min:%u max:%u\n",Min ,Max);
// test Min/Max against fixed levels (needs testing)
ret = PILOT_NOK; // Pilot NOT ok
if (Min >= 4000 ) ret = PILOT_12V; // Pilot at 12V
if ((Min >= 3300) && (Max < 4000)) ret = PILOT_9V; // Pilot at 9V
if ((Min >= 2400) && (Max < 3300)) ret = PILOT_6V; // Pilot at 6V
if ((Min >= 2000) && (Max < 2400)) ret = PILOT_3V; // Pilot at 3V
if ((Min > 100) && (Max < 350)) ret = PILOT_DIODE; // Diode Check OK
if (ret != old_pilot) {
printf("@Pilot:%u\n", ret); //d
old_pilot = ret;
}
return ret;
}
#endif
#if defined(SMARTEVSE_VERSION) && SMARTEVSE_VERSION < 40 //ESP32 v4
// Determine the state of the Pilot signal
//
uint8_t Pilot() {
uint32_t sample, Min = 3300, Max = 0;
uint32_t voltage;
uint8_t n;
// calculate Min/Max of last 25 CP measurements
for (n=0 ; n<25 ;n++) {
sample = ADCsamples[n];
voltage = esp_adc_cal_raw_to_voltage( sample, adc_chars_CP); // convert adc reading to voltage
if (voltage < Min) Min = voltage; // store lowest value
if (voltage > Max) Max = voltage; // store highest value
}
// Diagnostic: log ADC Min/Max once per second (every 100 calls at 10ms)
#if DIAG_LOG
static uint8_t pilotLogCnt = 0;
if (++pilotLogCnt >= 100) {
pilotLogCnt = 0;
_LOG_A("CP: min=%u max=%u\n", Min, Max);
}
#endif
// test Min/Max against fixed levels
if (Min >= 3055 ) return PILOT_12V; // Pilot at 12V (min 11.0V)
if ((Min >= 2735) && (Max < 3055)) return PILOT_9V; // Pilot at 9V
if ((Min >= 2400) && (Max < 2735)) return PILOT_6V; // Pilot at 6V
if ((Min >= 2000) && (Max < 2400)) return PILOT_3V; // Pilot at 3V
if ((Min >= 1600) && (Max < 2000)) return PILOT_SHORT; // Pilot short or open
if ((Min > 100) && (Max < 300)) return PILOT_DIODE; // Diode Check OK
return PILOT_NOK; // Pilot NOT ok
}
#endif
#if !defined(SMARTEVSE_VERSION) || SMARTEVSE_VERSION >=30 && SMARTEVSE_VERSION < 40 //CH32 and v3 ESP32
// Is there at least 6A(configurable MinCurrent) available for a new EVSE?
// returns 1 if there is 6A available
// returns 0 if there is no current available
// only runs on the Master or when loadbalancing Disabled
// only runs on CH32 for SmartEVSEv4
char IsCurrentAvailable(void) {
evse_bridge_lock();
evse_sync_globals_to_ctx();
int result = evse_is_current_available(&g_evse_ctx);
evse_sync_ctx_to_globals();
evse_bridge_unlock();
return (char)result;
}
#else //v4 ESP32
bool Shadow_IsCurrentAvailable; // this is a global variable that will be kept uptodate by Timer1S on CH32
char IsCurrentAvailable(void) {
//TODO debug:
_LOG_A("Shadow_IsCurrentAvailable=%d.\n", Shadow_IsCurrentAvailable);
return Shadow_IsCurrentAvailable;
}
#endif
// Calculates Balanced PWM current for each EVSE
// mod =0 normal
// mod =1 we have a new EVSE requesting to start charging.
// only runs on the Master or when loadbalancing Disabled
void CalcBalancedCurrent(char mod) {
#if !defined(SMARTEVSE_VERSION) || SMARTEVSE_VERSION >=30 && SMARTEVSE_VERSION < 40 //CH32 and v3 ESP32
uint16_t oldSolarStopTimer = SolarStopTimer;
// Core logic via module; state transitions trigger callback automatically
evse_bridge_lock();
evse_sync_globals_to_ctx();
evse_calc_balanced_current(&g_evse_ctx, (int)mod);
evse_sync_ctx_to_globals();
evse_bridge_unlock();
// SolarStopTimer side effects (SEND_TO_ESP32, SEND_TO_CH32, MQTT)
if (SolarStopTimer != oldSolarStopTimer) {
uint16_t newVal = SolarStopTimer;
SolarStopTimer = oldSolarStopTimer;
setSolarStopTimer(newVal);
}
// Logging
_LOG_V("Checkpoint 5 Isetbalanced=%d.%d A.\n", IsetBalanced/10, abs(IsetBalanced%10));
if (LoadBl == 1) {