-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathESP32 Pellet Stove wit mods.yaml
975 lines (968 loc) · 38 KB
/
ESP32 Pellet Stove wit mods.yaml
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
#----------------------------------------------------------
# TO DO:
# 1) add "lighting" flag. Don't clear it until temp hits 275F.
# then prevent P5 until flag is cleared and stove has been properly lit.
# this will reduce the stove flame-outs
# 2) add svariable for initial power level so different rooms can be accomodated.
#
# TUYA WOOD PELLET STOVE Converted to ESP32
#version; 3.1
# Add routines to shut of Auger every time it shouldn't be on.
# date: 03-21-23 bug fixes
# version; 3.0.1
# date: 03-21-23 bug fixes
# 3.0 converted to ESP32, added realtime exhaust sensor
# 2.0 added fire stoking function
# 1.0.0 added a Power Level P5 (ultra low power) that is fully configurable while in ECO2 mode.
# 1.1.0 added echo of actual display messages and status via Display's MCU UART monitoring
#
# All of these mods give a new "Ultra Low" power level which turns down the heat during IDLE mode so the stove goes thru fewer (no)
# on/off/igniter cycles and just maintains a lower heat output (for smaller rooms)
# when the room cools enuf, ECO2 mode will kick back to "Heating" which
# turns off this new Ultra Low power level, then once the room temp is +2F above set temp, stove
# goes into "Idle" mode again and new Ultra Low is active again. The cycle repeats and the stove stays lit.
# and room doesn't drastically heat up driving your wife nuts.
# These mods ***require*** replacing the Tuya chip with an ESP32 D1 Mini & some MCU control board and Display board mods.
# See Github Discussions for complete details
#
# NOTE: I am NOT a career/pro programmer. There is probably tons of opportunity to
# consolidate many of the if/then statements that check things into convoluted programmer-porn style
# Lambda evaluations, but I hate writing and debugging Lambdas,
# so my code makes way more sense for the average person and is very easy to debug.
# Your mileage may vary.
# And, more inportant, it gets the job done!
#----------------------------------------
# GPIO16 <- TX PIN ON DISPLAY BOARD
# GPIO21 -> RX PIN " " "
# GPIO27 <- MCU CONTROL BOARD SENSOR FROM MCU
# GPIO25 - > AUGER OUT TO OPTOISOLATOR
# GPIO19 -> LED ON DISPLAY FOR P5
# GPIO26 -> THUMBS UP RESET
# GPIO5 - SDA
# GPIO33 - SCL
#--------------------------------------------------------
substitutions:
device_name: "house-wood-pellet-stove"
device_description: House Pellet Stove running ESPHome
friendly_name: House Wood Pellet Stove
location: House
lighting_count: "55" #sets max number of dumps of pellets during lighting
#----------------------------------------------------------
#
esp32:
board: wemos_d1_mini32
#
api:
reboot_timeout: 0s # 0s is disabled so if we lose home assistant API keep running
#
# define 3 hardware UARTS, 1 for TuyaMCU, others for MCU Display & temps monitoring
#
uart:
#communication from TuyaMCU to tywe1s chip UART1
- id: tywe1s_uart
rx_pin: RX # Receives data from MCU, RX label on ESP32
tx_pin: TX # Transmits data to MCU, TX label on ESP32
baud_rate: 9600
#Display message decoding UART2
- id: mcu_uart_TXpin
rx_pin: GPIO16 #attach new wire to TX labled pin on DISPLAY board
baud_rate: 2400
rx_buffer_size: 80
debug:
direction: RX
dummy_receiver: true
after:
delimiter: [0xCC, 0x33]
sequence:
- lambda: |-
UARTDebug::log_hex(direction, bytes, ' ');
//Still log the data
//Some packets have 5 bytes
//might have to make this 3 if delimitar bytes get removed....
if (bytes.size()==5) {
if(bytes[0]==0xAA && bytes[1]==0x00) {
if(bytes[2]==0x20)
id(MCUrawString).publish_state("Welcome to use");
if(bytes[2]==0x6A)
id(MCUrawString).publish_state("Goodbye!");
if(bytes[2]==0x69)
id(MCUrawString).publish_state("Cooling");
if(bytes[2]==0x63)
id(MCUrawString).publish_state("Cleaning");
if(bytes[2]==0x65)
id(MCUrawString).publish_state("Lighting");
if(bytes[2]==0x66)
id(MCUrawString).publish_state("Stabilization");
if(bytes[2]==0x62)
id(MCUrawString).publish_state("Switching OFF");
//if(bytes[2]==0x??)
//id(MCUrawString).publish_state("Cooling");
}
}
//Some packets have 6 bytes
if (bytes.size()==6) {
if(bytes[1]==0x01 && bytes[2]==0x46 && bytes[3]==0x02)
id(MCUrawString).publish_state("Welcome to use");
//AA:00:59:CC:33
if(bytes[1]==0x01 && bytes[2]==0x46 && bytes[3]==0x02)
id(MCUrawString).publish_state("Switching off");
if(bytes[1]==0x01 && bytes[2]==0x49 && bytes[3]==0x80)
id(MCUrawString).publish_state("Stabilization");
if(bytes[1]==0x01 && bytes[2]==0x49 && bytes[3]==0x00)
id(MCUrawString).publish_state("Stabilization");
if(bytes[1]==0x01 && bytes[2]==0x49 && bytes[3]==0x81)
id(MCUrawString).publish_state("ECO2");
if(bytes[1]==0x01 && bytes[2]==0x48 && bytes[3]==0x80)
id(MCUrawString).publish_state("ECO1");
if(bytes[1]==0x01 && bytes[2]==0x48 && bytes[3]==0xC0)
id(MCUrawString).publish_state("Idling - ECO2");
if(bytes[1]==0x01 && bytes[2]==0x49 && bytes[3]==0x01)
id(MCUrawString).publish_state("Idling - ECO2");
if(bytes[1]==0x01 && bytes[2]==0x48 && bytes[3]==0x83)
id(MCUrawString).publish_state("ECO1");
if(bytes[1]==0x01 && bytes[2]==0x48 && bytes[3]==0xC3)
id(MCUrawString).publish_state("ECO2");
if(bytes[1]==0x01 && bytes[2]==0x49 && bytes[3]==0x81)
id(MCUrawString).publish_state("Idling - ECO2");
}
#from Display control board RX pin to MCU board RX pin UART3
#gives us real time exhaust pipe temps in middle byte
- id: mcu_uart_RXpin
rx_pin: GPIO21 #attach new wire to RX labled pin on DISPLAY board
baud_rate: 2400
rx_buffer_size: 80
debug:
direction: RX
dummy_receiver: true
after:
delimiter: [0xCC, 0x33]
sequence:
- lambda: |-
UARTDebug::log_hex(direction, bytes, ' ');
//Still log the data
//Some packets have 5 bytes
//the full string-> the 7th HEX has the value I want(B8): AA:05:4B:00:00:26:B8:26:CC:33
if (bytes.size()==10) {
// works up to approx 275 then it goes wonky somehow needing -72 instead of -36
//but who cares, we only need stable values to stoke the fire
id(pipe_converted).publish_state(((bytes[6]) * 1.8) - 4.0);
}
#
globals:
- id: heartbeat_global
type: int
restore_value: no
- id: heartbeat_max_global
type: int
restore_value: no
initial_value: '8' #auger cycles
- id: auger_count_global
type: int
restore_value: no
initial_value: '0'
- id: stoking_flag_global
type: bool
initial_value: "false"
restore_value: yes
- id: power_select_lastvalue
type: int
restore_value: yes
- id: stoking_on_time_lastvalue
type: int
restore_value: yes
#
esphome:
name: ${device_name}
platformio_options:
upload_speed: 115200
#REQUIRED FOR UARTs TO WORK
board_build.extra_flags:
- "-DARDUINO_USB_CDC_ON_BOOT=0" # Override, defaults to '-DARDUINO_USB_CDC_ON_BOOT=1'
comment: ${device_description}
on_shutdown:
priority: 1000
then:
- switch.turn_off: auger_output_to_optoisolator
on_boot:
priority: -100
then:
- switch.turn_off: auger_output_to_optoisolator
- delay: 5s
- switch.turn_off: auger_output_to_optoisolator
- lambda: id(MCUrawString).publish_state("Booting. Delay 10s");
- delay: 10s # wait for Pellet stove power on to settle down
- logger.log: "Now executing on_boot. 10s delay over, STARTING UP"
- switch.turn_off: auger_output_to_optoisolator
- select.set_index:
id: power_select
index: 2 #always select Stove Power level P3 for default or...
#!lambda return id(power_select_lastvalue);
#set to saved flash value
- if:
condition:
and:
- binary_sensor.is_on: p5z
- lambda: return !(id(new_auger_motor_power_level).is_running());
then:
- switch.turn_on: p5_indicator
- logger.log: "0 new_auger_motor_power_level"
- script.execute: new_auger_motor_power_level
else:
- switch.turn_off: p5_indicator
- lambda: id(MCUrawString).publish_state("Tuya update. Delay 10s");
- logger.log: "Tuya STARTING UP"
- delay: 10s # wait for Tuya chip internals to update & initialize vars
- lambda: id(MCUrawString).publish_state("Tuya update complete");
- if:
condition:
and:
- text_sensor.state: #Stove is OFF
id: hvac_action_tuya
state: 'OFF'
- sensor.in_range: #stove is not lit
id: stove_pipe_temp
below: 140.0
then:
- globals.set: #expose to ha
id: stoking_flag_global
value: 'false'
- lambda: id(MCUrawString).publish_state("Goodbye!");
- if:
condition:
and:
- text_sensor.state: #Stove is IDLING, not HEATING
id: hvac_action_tuya
state: 'IDLE'
- sensor.in_range: #stove is lit
id: stove_pipe_temp
above: 140.0
then: #sometimes display gets confused when esp randomly reboots
- lambda: id(MCUrawString).publish_state("Idling - ECO2");
- if:
condition:
and:
- text_sensor.state: #Stove is HEATING
id: hvac_action_tuya
state: 'HEATING'
- sensor.in_range: #stove is lit
id: stove_pipe_temp
above: 140.0 # exactly the point it switches from "lighting to heating"
then:
- lambda: id(MCUrawString).publish_state("Stabilization");
else:
#- lambda: id(MCUrawString).publish_state("ESP32 Booting...");
script:
- id: stoke_fire
#started by on_value,
#if burnpot is starting to go out, then rekindle it
mode: single
then:
- logger.log: "1script.stoke_fire. ENTERING WHILE LOOP"
- script.stop: new_auger_motor_power_level
- while:
condition:
and:
- lambda: return id(stoking_flag);
- lambda: return id(tuya_climate); # make sure stove is ON
- sensor.in_range: #**** Stove is NOT lighting
id: stove_pipe_temp
above: 125.0
then: # STOKE the fire to avoid E1 Error "fire went out"
- delay: 0.01s #prevents race conditions from occuring
- logger.log:
format: "SCRIPT.STOKING.Stoking FIRE NOW | stoking_temp= %f"
args: ['id(stoking_temp).state'] #
- switch.turn_on: auger_output_to_optoisolator
- lambda: id(heartbeat_global) = 0;
- delay: !lambda "return id(stoking_on_time).state * 1000;" # SECONDS ON
- switch.turn_off: auger_output_to_optoisolator
- logger.log: "STOKING. Auger OFF delay completed"
- delay: !lambda "return id(stoking_off_time).state * 1000;" # SECONDS OFF
#
#SCRIPT New P5 Power level Auger motor control
#Started by the following events: p5z, on_boot, MCUrawString
#
- id: new_auger_motor_power_level
mode: single
then:
- logger.log: "1script.new_auger_motor_power_level.STARTING"
- script.stop: stoke_fire
#Main loop
- while:
condition:
and:
- text_sensor.state: #Stove is idling in ECO2 mode
id: hvac_action_tuya
state: 'IDLE'
- binary_sensor.is_on: p5z #We ARE In power level 5
- binary_sensor.is_off: stoking_flag #we arent stoking the fire
- lambda: return id(MCUrawString).state != "Cleaning";
then:
- logger.log: "1script.new_auger_motor_power_level.ENTERING WHILE LOOP"
#new P5 Auger routine
- if:
condition:
- sensor.in_range: #**** Stove is lighting, exhaust is cold
id: stove_pipe_temp
below: 140
then:
# - logger.log: "2script.new_auger_motor_power_level.STOPPING SCRIPT, pipe temp < 140"
- script.stop: new_auger_motor_power_level
- switch.turn_on: auger_output_to_optoisolator
- lambda: id(heartbeat_global) = 0;
- delay: !lambda "return id(auger_on_timez).state * 1000;"
# - logger.log: "3script.newP5 active. Auger ON delay completed"
- switch.turn_off: auger_output_to_optoisolator
- logger.log: "4script.newP5 active. Auger OFF"
- delay: !lambda "return id(auger_off_timez).state * 1000;"
- logger.log: "5script.newP5 active. Auger OFF delay completed"
# - logger.log: "1script.new_auger_motor_power_level.EXITING WHILE"
#END WHILE
#
binary_sensor:
- platform: template # template sensors expose sensors to HA
name: ${location} Heartbeat Flag
id: heartbeat_ha_flag
# set it to the value of the global
lambda: |-
if (id(heartbeat_global) >= id(heartbeat_max_global)) {
return true; }
else {
return false;
}
- platform: template # template sensors expose sensors to HA
name: ${location} Stoke Fire Flag
id: stoking_flag
# set it to the value of the global
lambda: |-
if (id(stoking_flag_global) == 1) {
return true;
} else {
return false;
}
#read new P5 power mode from home assistant, toggle repurposed Timer LED on display module
- platform: homeassistant
id: p5z
entity_id: input_boolean.${location}_p5
on_state:
then:
# - logger.log: "logger: P5 button changed"
- if:
condition:
- binary_sensor.is_on: p5z #p5 is active
then:
- logger.log: "script.P5 sw = TRUE turning ON P5 indicator"
- switch.turn_on: p5_indicator
- script.execute: new_auger_motor_power_level
else:
# - logger.log: "script.p5z = FALSE, turning OFF P5 indicator"
- switch.turn_off: p5_indicator
- globals.set:
id: stoking_flag_global
value: 'false'
- script.stop: stoke_fire
- script.stop: new_auger_motor_power_level
- if:
condition:
switch.is_on: p5_indicator
then:
- logger.log: "GPIO16 sw = ON"
else:
- logger.log: "GPIO16 sw = OFF"
#
# MCU CONTROL BOARD AUGER SENSOR
- platform: gpio
pin: GPIO27 # input only pin
internal: false # this is just a flag you expose and watch in Lovelace
id: ${location}_auger_sensor_from_mcu
name: "${location} Auger Sensor from MCU" #Auger signal from MCU, goes low when Auger motor is running
filters:
- invert #flip it
on_state:
then:
# Increment Heartbeat count reset it each time actual auger signal is felt.
# if it gies over the global setting, restart either new power or stoking
- lambda: id(heartbeat_global) += 1;
- if: #check for heartbeat and restart scripts if it fails
condition: #if after 6 state changes (3 auger pulses), this means no auger of our own detected, then restart scripts
- lambda: |-
if (id(heartbeat_global) >= id(heartbeat_max_global)) {
return true; }
else {
return false;
}
then:
- if:
condition:
- binary_sensor.is_on: stoking_flag
then:
- script.execute: stoke_fire
- logger.log: "Auger.from.MCU.on_state ***HEARTBEAT stoke_fire RESET JUST OCCURED***"
else:
- script.execute: new_auger_motor_power_level
- logger.log: "Auger.from.MCU.on_state ***HEARTBEAT new_auger_motor_power_level RESET JUST OCCURED***"
#TURN OFF - HEATING not IDLING
- if:
condition:
and:
- binary_sensor.is_off: ${location}_auger_sensor_from_mcu
- text_sensor.state:
id: hvac_action_tuya
state: 'HEATING'
then:
- switch.turn_off: auger_output_to_optoisolator
- logger.log: "1GPIOI.Auger.from.MCU.sw.TURNOFF"
#TURN OFF - LIGHTING, echo Auger
- if:
condition:
and:
- binary_sensor.is_off: ${location}_auger_sensor_from_mcu
- text_sensor.state:
id: MCUrawString
state: 'Lighting'
then:
- logger.log: "2GPIOI.Auger.from.MCU.Lighting.SW.TURNOFF"
- switch.turn_off: auger_output_to_optoisolator
#TURN ON - HEATING (Stabilization), ECHO Auger
- if:
condition:
and:
- binary_sensor.is_on: ${location}_auger_sensor_from_mcu
- text_sensor.state: #Stove is HEATING, not IDLING
id: hvac_action_tuya
state: 'HEATING'
then:
- switch.turn_on: auger_output_to_optoisolator
- lambda: id(heartbeat_global) = 0;
# - logger.log: "1GPIO.AUGER TURNING"
#TURN ON - LIGHTING, ECHO Auger
- if:
condition:
and:
- binary_sensor.is_on: ${location}_auger_sensor_from_mcu
- text_sensor.state:
id: MCUrawString
state: 'Lighting'
#limit pellets in hopper on lighting. it only needs 55 turns to actually light.
#adjust this value if stove repeatedly fails to light every other time.
- lambda: |-
if (id(auger_count_global) <= ${lighting_count}) {
return true; }
else {
return false;
}
then:
- switch.turn_on: auger_output_to_optoisolator
- lambda: id(heartbeat_global) = 0;
# - logger.log: "2GPIO.AUGER TURNING"
- lambda: id(auger_count_global) += 1; #keep track of how many auger turns happen and reset if stove lights
- lambda: id(auger_counter).publish_state(id(auger_count_global)); #publish it to the sensor
#TURN ON - REGULAR ECHO OF AUGER *not* in ultra low power
- if:
condition:
and:
- binary_sensor.is_off: p5z #NOT In power level 5
- binary_sensor.is_on: ${location}_auger_sensor_from_mcu
then:
- switch.turn_on: auger_output_to_optoisolator
- lambda: id(heartbeat_global) = 0;
# - logger.log: "3GPIO18.AUGER TURNING"
- if:
condition:
and:
- binary_sensor.is_off: p5z #NOT In power level 5
- binary_sensor.is_off: ${location}_auger_sensor_from_mcu
then:
- switch.turn_off: auger_output_to_optoisolator
- logger.log: "3GPIOI.Auger.from.MCU.sw.TURNOFF"
button:
- platform: restart #allows remote reset of tywe1s chip from home assistant or internally
id: restart_esp
name: "${location} Stove Restart"
- platform: safe_mode # required to upload firmware if were low on memory. put chip in safe mode, reboot, then OTA upload new firmware.
name: "${location} SAFE Mode BUTTON"
#
# DEFINE ALL SENSORS AND SWITCHES
#
text_sensor:
- platform: debug
device:
name: "${location} Device Info"
reset_reason:
name: "${location} Reset Reason"
#
#from MCU in the display board, gives us what specific text is shown on the display
- platform: template
id: MCUrawString
name: ${location}_display_text_sensor
on_value: # When value simply changes
- logger.log: "0MCUrawString.on_value.entered DISPLAY CHANGED"
#
- platform: template
name: "${location} Uptime (formatted)"
lambda: |-
uint32_t dur = id(uptime_s).state;
int dys = dur / 86400;
dur %= 86400;
int hrs = dur / 3600;
dur %= 3600;
int mnts = dur / 60;
dur %= 60;
return str_sprintf("%ud %02uh %02um %02us", dys, hrs, mnts, dur);
icon: mdi:clock-start
update_interval: 60s
#
#This sensor is changing every 10s and the ONLY indicator of what state the stove is in
- platform: template
id: hvac_action_tuya
update_interval: 10s
name: "${location} hvac_action_tuya"
lambda: |-
switch (id(tuya_climate).action) {
case CLIMATE_ACTION_OFF:
return (std::string) "OFF";
case CLIMATE_ACTION_HEATING:
return (std::string) "HEATING";
case CLIMATE_ACTION_IDLE:
return (std::string) "IDLE";
default:
return (std::string) "UNKNOWN";
}
on_value:
then:
- lambda: ESP_LOGD("hvac_action_tuya", "id.hvac_action_tuya.UPDATED | ACTION = %s", x.c_str());
#this sensor changes from HEATING to IDLE in ECO2 mode or when we reach set temp in ECO1.
on_raw_value:
- if:
condition:
- lambda: return id(MCUrawString).state == "OFF";
then:
- globals.set: #expose to ha
id: stoking_flag_global
value: 'false'
- lambda: id(MCUrawString).publish_state("Goodbye!");
- if:
condition:
and:
- lambda: return id(MCUrawString).state != "Lighting";
- lambda: return id(MCUrawString).state != "Cleaning";
- lambda: return id(MCUrawString).state != "Switching OFF";
- text_sensor.state: #Stove is idling in ECO2 mode
id: hvac_action_tuya
state: 'IDLE'
- sensor.in_range: #stove is lit
id: stove_pipe_temp
above: 140.0
then:
- if:
condition:
- lambda: return !(id(new_auger_motor_power_level).is_running());
then:
- lambda: id(MCUrawString).publish_state("ECO2 - Idling");
- logger.log: "hvac_action_tuya -> IDLE. STARTING new_auger_motor_power_level"
- script.execute: new_auger_motor_power_level
- if:
condition:
and:
- lambda: return id(MCUrawString).state != "Switching OFF";
- lambda: return (id(new_auger_motor_power_level).is_running());
- text_sensor.state: #Stove is heating in ECO2 mode
id: hvac_action_tuya
state: 'HEATING'
then:
- logger.log: "hvac_action_tuya -> HEATING. STOPPING new_auger_motor_power_level"
- lambda: id(MCUrawString).publish_state("Stabilization");
- script.stop: new_auger_motor_power_level
- if:
condition:
- lambda: return id(hvac_action_tuya).state == "OFF";
then: # Stop everything
- logger.log: "id.hvac_action_tuya.AUGER.SW.TURNOFF+ ALL SCRIPTS"
- switch.turn_off: auger_output_to_optoisolator
- script.stop: new_auger_motor_power_level
- script.stop: stoke_fire
- if:
condition:
- lambda: return id(MCUrawString).state == "Switching OFF";
then: # Stop everything
- logger.log: "id.hvac_action_tuya.MCUrawString.AUGER.SW.TURNOFF + ALL SCRIPTS"
- switch.turn_off: auger_output_to_optoisolator
- script.stop: new_auger_motor_power_level
- script.stop: stoke_fire
#
i2c: # For time of flight sensor
sda: GPIO5
scl: GPIO33
#
sensor:
- platform: wifi_signal
name: "${location} WiFi Signal db"
update_interval: 60s
unit_of_measurement: "db"
id: wifi_signal_db
#
- platform: copy # Reports the WiFi signal strength in %
source_id: wifi_signal_db
name: "${location} WiFi Signal %"
filters:
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
unit_of_measurement: "%"
entity_category: "diagnostic"
#
- platform: debug
free:
name: "${location} Heap Free"
#THIS SHOULD ONLY CONTROL STOKING OR NOT
- platform: template
id: pipe_converted
name: "${location} UART Pipe Temp"
on_value: # When value simply changes
#**ADD TURN OFF AUGER ON TEMP >=400
then:
- if:
condition:
and:
- lambda: return id(MCUrawString).state != "Switching OFF";
- lambda: return id(MCUrawString).state != "Lighting";
- lambda: return id(tuya_climate); # make sure stove is ON
- lambda: return !(id(stoke_fire).is_running()); #stoke_fire is NOT running
- sensor.in_range: # not lighting
id: pipe_converted
above: 130.0
- sensor.in_range: #just to be safe
id: pipe_converted
below: 200.0
- binary_sensor.is_on: p5z
#Adjust the stoking_temp value in HA to keep pot from burning out
- lambda: |-
if (id(pipe_converted).state <= id(stoking_temp).state) {
return true;
} else {
return false;
}
then:
- logger.log: "0 uart pipe temp stoking conditions met "
- globals.set: #expose to ha
id: stoking_flag_global
value: 'true'
# - if:
# condition:
# - lambda: return (id(new_auger_motor_power_level).is_running());
# then:
- logger.log: "1 uart stopping new power level&starting stoking"
- script.stop: new_auger_motor_power_level
- script.execute: stoke_fire
- if:
condition:
and:
- lambda: return id(MCUrawString).state != "Switching OFF";
- lambda: return id(tuya_climate); # make sure stove is ON
- sensor.in_range: # not lighting
id: pipe_converted
above: 130.0
- sensor.in_range: #just to be safe
id: pipe_converted
below: 200.0
- binary_sensor.is_on: p5z
#Adjust the stoking_temp value in HA to keep pot from burning out
- lambda: |-
if (id(pipe_converted).state >= id(stoking_temp).state) {
return true;
} else {
return false;
}
then:
- logger.log: "2 uart idling (not stoking)"
- globals.set:
id: stoking_flag_global
value: 'false'
- if:
condition:
- lambda: return (id(stoke_fire).is_running());
then:
- logger.log: "3 uart stopping stoke script"
- script.stop: stoke_fire
- if:
condition:
- lambda: return !(id(new_auger_motor_power_level).is_running());
- text_sensor.state: #Stove is heating in ECO2 mode
id: hvac_action_tuya
state: 'IDLE'
then:
#- logger.log: "4 uart starting new auger power level script in 25s"
#give a pellet break so temp doesnt rise too quickly
#- delay: 25s
- script.execute: new_auger_motor_power_level
- if: #reset Auger counter as soon as stove starts to light
#and interrupt it in 'echo auger' at just the right level to avoid ESC1 errors from too much fuel in pot and stove burningbto hot on startup
condition:
- sensor.in_range:
id: pipe_converted
above: 200.0 #stove is lit. reset counter
then:
# - logger.log: "3MCUrawString.new_auger_motor_power_level.RESETTING AUGER COUNT = 0"
- if:
condition:
- lambda: |-
if (id(auger_count_global) > 0) {
return true; }
else {
return false;
}
then:
- lambda: id(auger_count_global) = 0;
- lambda: id(auger_counter).publish_state(id(auger_count_global));
#
# - if: # is temp falling too fast? did stoking somehow fail? fire is going out!
# condition: #make sure the routines are running while stoking
# and:
# - sensor.in_range:
# id: pipe_converted
# above: 125.0
# - binary_sensor.is_on: p5z
# - lambda: return id(tuya_climate); # make sure stove is ON
# #this should never be true if all is working right
# #unless stoking has been turned off by trend sensor "heating"
# - lambda: |-
# if (id(pipe_converted).state <= (id(stoking_temp).state) - 20) {
# return true;
# } else {
# return false;
# }
# then:
# - globals.set:
# id: stoking_flag_global
# value: 'true'
# - script.execute: stoke_fire #it should already be running. restart it if not
- platform: template
name: ${location} Auger Counter (lighting)
id: auger_counter
#get these object values from home assistant
- platform: homeassistant
id: auger_off_timez
entity_id: input_number.${location}_auger_off_time #helper in home assistant
#
- platform: homeassistant
id: auger_on_timez
entity_id: input_number.${location}_auger_on_time #helper in home assistant
#
- platform: homeassistant
id: stoking_temp
entity_id: input_number.${location}_stoking_temp #helper in home assistant
force_update: true
#
- platform: homeassistant
id: stoking_on_time
entity_id: input_number.${location}_stoking_on_time #helper in home assistant
force_update: true
#
- platform: homeassistant
id: stoking_off_time
entity_id: input_number.${location}_stoking_off_time #helper in home assistant
force_update: true
#
- platform: vl53l0x # <- MUCH more accurate and less noise than Ultrasonic sensors!
name: "${location} raw pellet level"
address: 0x29
long_range: true
timeout: 200us
update_interval: 60s
unit_of_measurement: "m"
accuracy_decimals: 2
on_value:
then:
# Replace 0.4 by the height of hopper. From the sensor to the bottom.
# https://www.dcode.fr/lagrange-interpolating-polynomial
# take measurements, top, middle and bottom of pellets w/ the TOF sensor.
# x axix is measuerment distance in meters, y is %
# or: https://www.wolframalpha.com/input?i=interpolating+polynomial+%7B0.04%2C100%7D%2C%7B0.22%2C50%7D%7B0.5%2C0%7D
# interpolating polynomial {0.03,100},{.18,50}{.34,10}{.38,0}
- sensor.template.publish:
id: ultrasonic_smoothed
# state: !lambda 'return (0.65-x)*(100/0.65);'
state: !lambda 'return (-768.049)*x*x*x + (691.244)*x*x - (448.771)*x + (112.862);'
#
- platform: template
id: ultrasonic_smoothed
update_interval: 60s
accuracy_decimals: 0
unit_of_measurement: "%"
icon: "mdi:sack-percent"
name: "${location} pellet level processed & filtered"
# filters:
# - sliding_window_moving_average:
# window_size: 30
# send_every: 15
#
- platform: "tuya"
name: "${location} Stove-Power On"
sensor_datapoint: 1
id: stove_on
- platform: "tuya"
name: "${location} Stove-Error Code"
id: error_code
sensor_datapoint: 104
#
- platform: "tuya"
name: "${location} Stove-Power Mode"
sensor_datapoint: 4
#
- platform: "tuya"
name: "${location} Stove-Set Temp"
sensor_datapoint: 106
#
- platform: "tuya"
id: current_temp
name: "${location} Stove-Current Temp"
sensor_datapoint: 107
filters:
- exponential_moving_average: #this sensor is very NOISY!
alpha: 0.07
#
- platform: "tuya"
name: "${location} Stove-Pipe Temp"
id: stove_pipe_temp
sensor_datapoint: 108
# #
- platform: "tuya"
name: "${location} Stove-Protect Temp"
sensor_datapoint: 109
#
- platform: uptime
internal: false
name: ${friendly_name} Uptime
id: uptime_s
update_interval: 600s
#
#DEFINE SELECTS
#
select:
- platform: "tuya"
tuya_id: "${location}"
name: "${location} Power Select"
id: power_select
enum_datapoint: 4
options:
0: P1-High
1: P2-Med High
2: P3-Med Low
3: P4-Low
on_value: #store lat value so it can be restored on power/boot up
- lambda: id(power_select_lastvalue) = i;
#
- platform: "tuya"
tuya_id: "${location}"
name: "${location} ECO Select"
id: eco_mode
enum_datapoint: 101
options:
0: ECO1 - Heat then OFF
1: ECO2 - Px then uP4
#
# Data points observed in Tuya iOT platform during debug mode
# 1 - Power on (Heat)
#4 - Mode P1/P2/P3P4
#101 - ECO1/ECO2
#104 - Error Code
#105 - unused
#106 - Set Temp
#107 - Current Temp
#108 - Pipe Temp
#109 - Protect Temp
#-----------------
#Datapoint 1: switch (value: OFF)
#Datapoint 105: enum (value: 0)
#Datapoint 4: enum (value: 0)
#Datapoint 101: enum (value: 0)
#Datapoint 106: int value (value: 68)
#Datapoint 107: int value (value: 64)
#Datapoint 108: int value (value: 68)
#Datapoint 109: int value (value: 72)
#Datapoint 104: bitmask (value: 0)
#
# Register the Tuya MCU connection
tuya:
id: "${location}"
uart_id: tywe1s_uart
#
switch:
#This sends the Auger signal to the optoisolator to turn on the Auger motor
- platform: gpio
pin: GPIO25 #this pin remains high at bootup and reset, no extra pellets disbursed
name: "${location} AugerOut to optoisolator +LED"
id: auger_output_to_optoisolator
inverted: yes
#FAILSAFE. IF AUGER EVER GETS STUCK on for over 30s, TURN IT OFF
# Need to rethink this. this always runs and interferes with operation
# on_turn_on:
# - delay: 30s
# - switch.turn_off: auger_output_to_optoisolator
#
#wire the time LED on front panel to GPIO1 to indicate when new P5 level is on
- platform: gpio
pin: GPIO19 #use 470ohm resistor in line w led
name: "${location} P5 indicator LED"
inverted: yes
id: p5_indicator
#
# automate resetting stove errors using this button
- platform: gpio
pin: GPIO26
name: "${location} Thumbsup RESET"
inverted: yes
id: thumbsup
on_turn_on:
- delay: 3.5s #hold time needed to reset error on display
- switch.turn_off: thumbsup
#
climate:
- platform: tuya
reports_fahrenheit: true # If set to F in stove settings, it requires this new flag as all values should originate in C.
name: "${location} Wood Pellet Stove"
id: tuya_climate
switch_datapoint: 1
target_temperature_datapoint: 106
current_temperature_datapoint: 107
visual:
min_temperature: 15.5 #celcius
max_temperature: 24.5
temperature_step: 1
on_state:
#Failsafe if auger ever gets stuck, cover ALL cases
#[08:25:52][D][climate:396]: 'House Wood Pellet Stove' - Sending state:
#[08:25:52][D][climate:399]: Mode: OFF
then:
- if:
condition:
or:
- lambda: |-
if (x.mode == CLIMATE_MODE_OFF)
{ return true;}
else
{ return false;}
- lambda: return id(MCUrawString).state == "Switching OFF";
- text_sensor.state: #Stove is idling in ECO2 mode
id: hvac_action_tuya
state: 'OFF'
then:
- logger.log: "id.tuya_climate.AUGER.SW.TURNOFF + ALL SCRIPTS"
- switch.turn_off: auger_output_to_optoisolator
- script.stop: new_auger_motor_power_level
- script.stop: stoke_fire
ota:
platform: esphome
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
power_save_mode: none
fast_connect: on
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "${location}-Wood-Pellet-Stove"
password: !secret wifi_password
#
logger:
level: debug
baud_rate: 0 #required for UART sniffer to work
debug:
update_interval: 60s