diff --git a/lib/Adafruit_SGP30-1.0.0.13/.github/ISSUE_TEMPLATE.md b/lib/Adafruit_SGP30-1.0.3/.github/ISSUE_TEMPLATE.md old mode 100644 new mode 100755 similarity index 100% rename from lib/Adafruit_SGP30-1.0.0.13/.github/ISSUE_TEMPLATE.md rename to lib/Adafruit_SGP30-1.0.3/.github/ISSUE_TEMPLATE.md diff --git a/lib/Adafruit_SGP30-1.0.0.13/.github/PULL_REQUEST_TEMPLATE.md b/lib/Adafruit_SGP30-1.0.3/.github/PULL_REQUEST_TEMPLATE.md old mode 100644 new mode 100755 similarity index 100% rename from lib/Adafruit_SGP30-1.0.0.13/.github/PULL_REQUEST_TEMPLATE.md rename to lib/Adafruit_SGP30-1.0.3/.github/PULL_REQUEST_TEMPLATE.md diff --git a/lib/Adafruit_SGP30-1.0.0.13/.gitignore b/lib/Adafruit_SGP30-1.0.3/.gitignore old mode 100644 new mode 100755 similarity index 100% rename from lib/Adafruit_SGP30-1.0.0.13/.gitignore rename to lib/Adafruit_SGP30-1.0.3/.gitignore diff --git a/lib/Adafruit_SGP30-1.0.0.13/.travis.yml b/lib/Adafruit_SGP30-1.0.3/.travis.yml old mode 100644 new mode 100755 similarity index 100% rename from lib/Adafruit_SGP30-1.0.0.13/.travis.yml rename to lib/Adafruit_SGP30-1.0.3/.travis.yml diff --git a/lib/Adafruit_SGP30-1.0.0.13/Adafruit_SGP30.cpp b/lib/Adafruit_SGP30-1.0.3/Adafruit_SGP30.cpp old mode 100644 new mode 100755 similarity index 83% rename from lib/Adafruit_SGP30-1.0.0.13/Adafruit_SGP30.cpp rename to lib/Adafruit_SGP30-1.0.3/Adafruit_SGP30.cpp index b2ccbe8da..ce6116863 --- a/lib/Adafruit_SGP30-1.0.0.13/Adafruit_SGP30.cpp +++ b/lib/Adafruit_SGP30-1.0.3/Adafruit_SGP30.cpp @@ -37,7 +37,7 @@ //#define I2C_DEBUG /**************************************************************************/ -/*! +/*! @brief Instantiates a new SGP30 class */ /**************************************************************************/ @@ -45,7 +45,7 @@ Adafruit_SGP30::Adafruit_SGP30() { } /**************************************************************************/ -/*! +/*! @brief Setups the hardware and detects a valid SGP30. Initializes I2C then reads the serialnumber and checks that we are talking to an SGP30 @param theWire Optional pointer to I2C interface, otherwise use Wire @@ -60,31 +60,32 @@ boolean Adafruit_SGP30::begin(TwoWire *theWire) { _i2c = theWire; } - _i2c->begin(); +// assume i2c initialized already to avoid resetting clock stretching +// _i2c->begin(); + - uint8_t command[2]; command[0] = 0x36; command[1] = 0x82; - if (! readWordFromCommand(command, 2, 10, serialnumber, 3)) + if (! readWordFromCommand(command, 2, 10, serialnumber, 3)) return false; uint16_t featureset; command[0] = 0x20; command[1] = 0x2F; - if (! readWordFromCommand(command, 2, 10, &featureset, 1)) + if (! readWordFromCommand(command, 2, 10, &featureset, 1)) return false; //Serial.print("Featureset 0x"); Serial.println(featureset, HEX); - if (featureset != SGP30_FEATURESET) + if (featureset != SGP30_FEATURESET) return false; - if (! IAQinit()) + if (! IAQinit()) return false; return true; } /**************************************************************************/ -/*! +/*! @brief Commands the sensor to begin the IAQ algorithm. Must be called after startup. @returns True if command completed successfully, false if something went wrong! */ @@ -97,7 +98,7 @@ boolean Adafruit_SGP30::IAQinit(void) { } /**************************************************************************/ -/*! +/*! @brief Commands the sensor to take a single eCO2/VOC measurement. Places results in {@link TVOC} and {@link eCO2} @returns True if command completed successfully, false if something went wrong! */ @@ -113,9 +114,9 @@ boolean Adafruit_SGP30::IAQmeasure(void) { eCO2 = reply[0]; return true; } - + /**************************************************************************/ -/*! +/*! @brief Request baseline calibration values for both CO2 and TVOC IAQ calculations. Places results in parameter memory locaitons. @param eco2_base A pointer to a uint16_t which we will save the calibration value to @param tvoc_base A pointer to a uint16_t which we will save the calibration value to @@ -135,7 +136,7 @@ boolean Adafruit_SGP30::getIAQBaseline(uint16_t *eco2_base, uint16_t *tvoc_base) } /**************************************************************************/ -/*! +/*! @brief Assign baseline calibration values for both CO2 and TVOC IAQ calculations. @param eco2_base A uint16_t which we will save the calibration value from @param tvoc_base A uint16_t which we will save the calibration value from @@ -157,7 +158,30 @@ boolean Adafruit_SGP30::setIAQBaseline(uint16_t eco2_base, uint16_t tvoc_base) { } /**************************************************************************/ -/*! +/*! + @brief Set the absolute humidity value [mg/m^3] for compensation to increase precision of TVOC and eCO2. + @param absolute_humidity A uint32_t [mg/m^3] which we will be used for compensation. If the absolute humidity is set to zero, humidity compensation will be disabled. + @returns True if command completed successfully, false if something went wrong! +*/ +/**************************************************************************/ +boolean Adafruit_SGP30::setHumidity(uint32_t absolute_humidity) { + if (absolute_humidity > 256000) { + return false; + } + + uint16_t ah_scaled = (uint16_t)(((uint64_t)absolute_humidity * 256 * 16777) >> 24); + uint8_t command[5]; + command[0] = 0x20; + command[1] = 0x61; + command[2] = ah_scaled >> 8; + command[3] = ah_scaled & 0xFF; + command[4] = generateCRC(command+2, 2); + + return readWordFromCommand(command, 5, 10); +} + +/**************************************************************************/ +/*! @brief I2C low level interfacing */ /**************************************************************************/ @@ -186,16 +210,16 @@ boolean Adafruit_SGP30::readWordFromCommand(uint8_t command[], uint8_t commandLe delay(delayms); - if (readlen == 0) + if (readlen == 0) return true; uint8_t replylen = readlen * (SGP30_WORD_LEN +1); - if (_i2c->requestFrom(_i2caddr, replylen) != replylen) + if (_i2c->requestFrom(_i2caddr, replylen) != replylen) return false; uint8_t replybuffer[replylen]; #ifdef I2C_DEBUG Serial.print("\t\t<- "); -#endif +#endif for (uint8_t i=0; iread(); #ifdef I2C_DEBUG diff --git a/lib/Adafruit_SGP30-1.0.0.13/Adafruit_SGP30.h b/lib/Adafruit_SGP30-1.0.3/Adafruit_SGP30.h old mode 100644 new mode 100755 similarity index 97% rename from lib/Adafruit_SGP30-1.0.0.13/Adafruit_SGP30.h rename to lib/Adafruit_SGP30-1.0.3/Adafruit_SGP30.h index cc95fa54b..6f27aad04 --- a/lib/Adafruit_SGP30-1.0.0.13/Adafruit_SGP30.h +++ b/lib/Adafruit_SGP30-1.0.3/Adafruit_SGP30.h @@ -42,6 +42,7 @@ class Adafruit_SGP30 { boolean getIAQBaseline(uint16_t *eco2_base, uint16_t *tvoc_base); boolean setIAQBaseline(uint16_t eco2_base, uint16_t tvoc_base); + boolean setHumidity(uint32_t absolute_humidity); /** * The last measurement of the IAQ-calculated Total Volatile Organic Compounds in ppb. This value is set when you call {@link IAQmeasure()} diff --git a/lib/Adafruit_SGP30-1.0.0.13/README.md b/lib/Adafruit_SGP30-1.0.3/README.md old mode 100644 new mode 100755 similarity index 100% rename from lib/Adafruit_SGP30-1.0.0.13/README.md rename to lib/Adafruit_SGP30-1.0.3/README.md diff --git a/lib/Adafruit_SGP30-1.0.0.13/examples/sgp30test/sgp30test.ino b/lib/Adafruit_SGP30-1.0.3/examples/sgp30test/sgp30test.ino old mode 100644 new mode 100755 similarity index 58% rename from lib/Adafruit_SGP30-1.0.0.13/examples/sgp30test/sgp30test.ino rename to lib/Adafruit_SGP30-1.0.3/examples/sgp30test/sgp30test.ino index 6b9b3ea05..b7ff8a70c --- a/lib/Adafruit_SGP30-1.0.0.13/examples/sgp30test/sgp30test.ino +++ b/lib/Adafruit_SGP30-1.0.3/examples/sgp30test/sgp30test.ino @@ -3,6 +3,17 @@ Adafruit_SGP30 sgp; +/* return absolute humidity [mg/m^3] with approximation formula +* @param temperature [°C] +* @param humidity [%RH] +*/ +uint32_t getAbsoluteHumidity(float temperature, float humidity) { + // approximation formula from Sensirion SGP30 Driver Integration chapter 3.15 + const float absoluteHumidity = 216.7f * ((humidity / 100.0f) * 6.112f * exp((17.62f * temperature) / (243.12f + temperature)) / (273.15f + temperature)); // [g/m^3] + const uint32_t absoluteHumidityScaled = static_cast(1000.0f * absoluteHumidity); // [mg/m^3] + return absoluteHumidityScaled; +} + void setup() { Serial.begin(9600); Serial.println("SGP30 test"); @@ -22,6 +33,11 @@ void setup() { int counter = 0; void loop() { + // If you have a temperature / humidity sensor, you can set the absolute humidity to enable the humditiy compensation for the air quality signals + //float temperature = 22.1; // [°C] + //float humidity = 45.2; // [%RH] + //sgp.setHumidity(getAbsoluteHumidity(temperature, humidity)); + if (! sgp.IAQmeasure()) { Serial.println("Measurement failed"); return; diff --git a/lib/Adafruit_SGP30-1.0.0.13/library.properties b/lib/Adafruit_SGP30-1.0.3/library.properties old mode 100644 new mode 100755 similarity index 95% rename from lib/Adafruit_SGP30-1.0.0.13/library.properties rename to lib/Adafruit_SGP30-1.0.3/library.properties index 3520b4d38..6c86464d1 --- a/lib/Adafruit_SGP30-1.0.0.13/library.properties +++ b/lib/Adafruit_SGP30-1.0.3/library.properties @@ -1,5 +1,5 @@ name=Adafruit SGP30 Sensor -version=1.0.2 +version=1.0.3 author=Adafruit maintainer=Adafruit sentence=This is an Arduino library for the Adafruit SGP30 Gas / Air Quality Sensor diff --git a/lib/Adafruit_SGP30-1.0.0.13/license.txt b/lib/Adafruit_SGP30-1.0.3/license.txt old mode 100644 new mode 100755 similarity index 100% rename from lib/Adafruit_SGP30-1.0.0.13/license.txt rename to lib/Adafruit_SGP30-1.0.3/license.txt diff --git a/platformio.ini b/platformio.ini old mode 100644 new mode 100755 index 57dbddc59..c2d09ace8 --- a/platformio.ini +++ b/platformio.ini @@ -65,9 +65,9 @@ build_flags = ${esp82xx_defaults.build_flags} -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH -DVTABLES_IN_FLASH -[core_2_5_1] -; *** Esp8266 core for Arduino version 2.5.1 -platform = espressif8266@~2.1.1 +[core_2_5_2] +; *** Esp8266 core for Arduino version 2.5.2 +platform = espressif8266@~2.2.0 build_flags = ${esp82xx_defaults.build_flags} -Wl,-Teagle.flash.1m.ld ; Code optimization see https://github.com/esp8266/Arduino/issues/5790#issuecomment-475672473 @@ -133,8 +133,8 @@ build_flags = ${esp82xx_defaults.build_flags} ;build_flags = ${core_2_3_0.build_flags} ;platform = ${core_2_4_2.platform} ;build_flags = ${core_2_4_2.build_flags} -platform = ${core_2_5_1.platform} -build_flags = ${core_2_5_1.build_flags} +platform = ${core_2_5_2.platform} +build_flags = ${core_2_5_2.build_flags} ;platform = ${core_stage.platform} ;build_flags = ${core_stage.build_flags} diff --git a/scripter.md b/scripter.md new file mode 100644 index 000000000..5b9ac7840 --- /dev/null +++ b/scripter.md @@ -0,0 +1,621 @@ +**Script Language for Tasmota** + +As an alternative to rules. (about 14,2k flash size, variable ram size) + +In submenu Configuration =\> edit script +1535 bytes max script size (uses rules buffer) + +to enable: +\#define USE_SCRIPT +\#undef USE_RULES + + +Up to 50 variables (45 numeric and 5 strings, maybe changed by #define) +Freely definable variable names (all names are intentionally case sensitive) +Nested if,then,else up to a level of 8 +Math operators **+,-,\*,/,%,&,|,^** +all operators may be used in the op= form e.g. **+=** +Left right evaluation with optional brackets +all numbers are float +e.g. temp=hum\*(100/37.5)+temp-(timer\*hum%10) +no spaces allowed between math operations +Comparison operators **==,!=,\>,\>=,<,<=** +**and** , **or** support + +strings support **+** and **+=** operators +string comparison **==,!=** +max string size = 19 chars (default, can be increased or decreased by optional >D parameter) + +**Comments** start with **;** + +**Sections** defined: + +>**\>D ssize** +ssize = optional max stringsize (default=19) +define and init variables here, must be the first section, no other code allowed +**p:**vname specifies permanent vars (the number of permanent vars is limited by tasmota rules space (50 bytes) +numeric var=4 bytes, string var=lenght of string+1) +**t:**vname specifies countdown timers, if >0 they are decremented in seconds until zero is reached. see example below +**i:**vname specifies auto increment counters if >=0 (in seconds) +**m:**vname specifies a median filter variable with 5 entries (for elimination of outliers) +**M:**vname specifies a moving average filter variable with 8 entries (for smoothing data) +(max 5 filters in total m+M) + +>all variable names length taken together may not exceed 256 characters, so keep variable names as short as possible. +memory is dynamically allocated as a result of the D section. +copying a string to a number or reverse is supported + +>**\>B** +executed on BOOT time + +>**\>T** +executed on teleperiod time (**SENSOR** and **STATE**), get tele vars only in this section + +>**\>S** +executed every second + +>**\>E** +executed e.g. on power change and mqtt **RESULT** + +>**\>R** +executed on restart, p vars are saved automatically after this call + + +special variables (read only): + +>**upsecs** = seconds since start +**uptime** = minutes since start +**time** = minutes since midnight +**sunrise** = sunrise minutes since midnight +**sunset** = sunset minutes since midnight +**tper** = teleperiod (may be set also) +**tstamp** = timestamp (local date and time) +**topic** = mqtt topic +**gtopic** = mqtt group topic +**prefixn** = prefix n = 1-3 +**pwr[x]** = tasmota power state (x = 1-N) +**sw[x]** = tasmota switch state (x = 1-N) +>**pin[x]** = gpio pin level (x = 0-16) +**pn[x]** = pin number for sensor code x, 99 if none +**pd[x]** = defined sensor for gpio pin nr x none=999 +**gtmp** = global temperature +**ghum** = global humidity +**gprs** = global pressure +**pow(x y)** = calculates the power of x^y +**med(n x)** = calculates a 5 value median filter of x (2 filters possible n=0,1) +**int(x)** = gets the integer part of x (like floor) +**hn(x)** = converts x (0..255) zu a hex nibble string +**mqtts** = state of mqtt disconnected=0, connected>0 +**wifis** = state of wifi disconnected=0, connected>0 + +>**hours** = hours +**mins** = mins +**secs** = seconds +**day** = day of month +**wday** = day of week +**month** = month +**year** = year + +these variables are cleared after reading true +>**chg[var]** = true if a variables value was changed (numeric vars only) +**upd[var]** = true if a variable was updated +**boot** = true on BOOT +**tinit** = true on time init +**tset** = true on time set +**mqttc** = true on mqtt connect +**mqttd** = true on mqtt disconnect +**wific** = true on wifi connect +**wifid** = true on wifi disconnect + +system vars (for debugging) +>**stack** = stack size +**heap** = heap size +**ram** = used ram size +**slen** = script length +**micros** = running microseconds +**millis** = running milliseconds +**loglvl** = loglevel of script cmds, may be set also + +remarks: +if you define a variable with the same name as a special +variable that special variable is discarded + + +**Tasmota** cmds start with **=\>** +within cmds you can replace text with variables with **%varname%** +a single percent sign must be given as **%%** + +**special** cmds: + +>**=\> print** prints to info log for debugging + +to save code space nearly no error messages are provided. However it is taken care of that at least it should not crash on syntax errors. +if a variable does not exist a **???** is given on commands +if a **SENSOR** or **STATUS** or **RESULT** message or a var does not exist the destination variable is NOT updated. + +2 possibilities for conditionals: +>**if** a==b +**and** x==y +**or** k==i +**then** => do this +**else** => do that +**endif** + +OR + +>**if** a==b +**and** x==y +**or** k==i **{** + => do this +**} else {** + => do that +**}** + +you may NOT mix both methods + +also possible e.g. + +>if var1-var2==var3*var4 +then + +remarks: +the last closing bracket must be on a single line +the condition may not be enclosed in brackets + +>**break** exits a section or terminates a for next loop +**dprecx** sets decimal precision to x (0-9) +**svars** save permanent vars +**delay(x)** pauses x milliseconds (should be as short as possible) +**spin(x m)** set gpio pin x (0-16) to value m (0,1) only the last bit is used, so even values set the pin to zero and uneven values set the pin to 1 +**spinm(x m)** set pin mode gpio pin x (0-16) to mode m (input=0,output=1) + +>**#name** names a subroutine, subroutines are called with **=#name** +**#name(param)** names a subroutines with a parameter is called with **=#name(param)** +subroutines end with the next '#' or '>' line or break, may be nested +params can be numbers or strings and on mismatch are converted + +>**for var from to inc** +**next** +specifies a for next loop, (loop count must not be less then 1) + +>**switch x** +**case a** +**case b** +**ends** +specifies a switch case selector + +**konsole script cmds** +>**script 1 or 0** switch script on or off +**script >cmdline** executes the script cmdline +can be used e.g. to set variables e.g. **script >mintmp=15** +more then one line may be executed seperated by a semicolon e.g. **script >mintmp=15;maxtemp=40** +script itself cant be set because the size would not fit the mqtt buffers + +***example script*** +meant to show some of the possibilities +(actually this code ist too large) + +**\>D** +; define all vars here +p:mintmp=10 (p:means permanent) +p:maxtmp=30 +t:timer1=30 (t:means countdown timer) +t:mt=0 +i:count=0 (i:means auto counter) +hello="hello world" +string="xxx" +url="[192.168.178.86]" +hum=0 +temp=0 +timer=0 +dimmer=0 +sw=0 +rssi=0 +param=0 + +col="" +ocol="" +chan1=0 +chan2=0 +chan3=0 + +ahum=0 +atemp=0 +tcnt=0 +hour=0 +state=1 +m:med5=0 +M:movav=0 + +**\>B** + +string=hello+"how are you?" +=\>print BOOT executed +=\>print %hello% +=\>mp3track 1 + +; list gpio pin definitions +for cnt 0 16 1 +tmp=pd[cnt] +=>print %cnt% = %tmp% +next + +; get gpio pin for relais 1 +tmp=pn[21] +=>print relais 1 is on pin %tmp% + +; pulse relais over raw gpio +spin(tmp 1) +delay(100) +spin(tmp 0) + +; raw pin level +=>print level of gpio1 %pin[1]% + +; pulse over tasmota cmd +=>power 1 +delay(100) +=>power 0 + +**\>T** + +hum=BME280#Humidity +temp=BME280#Temperature +rssi=Wifi#RSSI +string=SleepMode + +; add to median filter +median=temp +; add to moving average filter +movav=hum + +; show filtered results +=>print %median% %movav% + +if chg[rssi]>0 +then =>print rssi changed to %rssi% +endif + +if temp\>30 +and hum\>70 +then =\>print damn hot! +endif + +**\>S** + +; every second but not completely reliable time here +; use upsecs and uptime or best t: for reliable timers + +; call subrountines with parameters +=#sub1("hallo") +=#sub2(999) + +; stop timer after expired +if timer1==0 +then timer1=-1 +=>print timer1 expired +endif + +; auto counter with restart +if count>=10 +then =>print 10 seconds over +count=0 +endif + +if upsecs%5==0 +then =\>print %upsecs% (every 5 seconds) +endif + +; not recommended for reliable timers +timer+=1 +if timer\>=5 +then =\>print 5 seconds over (may be) +timer=0 +endif + +dimmer+=1 +if dimmer\>100 +then dimmer=0 +endif + +=\>dimmer %dimmer% +=\>WebSend %url% dimmer %dimmer% + +; show on display +dprec0 +=\>displaytext [c1l1f1s2p20] dimmer=%dimmer% + +=\>print %upsecs% %uptime% %time% %sunrise% %sunset% %tstamp% + +if time\>sunset +and time< sunrise +then +; night time +if pwr[1]==0 +then =\>power1 1 +endif +else +; day time +if pwr[1]\>0 +then =\>power1 0 +endif +endif + +; clr display on boot +if boot\>0 +then =\>displaytext [z] +endif + +; frost warning +if temp<0 +and mt<=0 +then =#sendmail("frost alert") +; alarm only every 5 minutes +mt=300 +=>mp3track 2 +endif + +; var has been updated +if upd[hello]>0 +then =>print %hello% +endif + +; send to Thingspeak every 60 seconds +; average data in between +if upsecs%60==0 +then +ahum/=tcnt +atemp/=tcnt +=>Websend [184.106.153.149:80]/update?key=PYUZMVWCICBW492&field1=%atemp%&field2=%ahum% +tcnt=0 +atemp=0 +ahum=0 +else +ahum+=hum +atemp+=temp +tcnt+=1 +endif + +hour=int(time/60) +if chg[hour]>0 +then +; exactly every hour +=>print full hour reached +endif + +if time>5 { +=>print more then 5 minutes after midnight +} else { +=>print less then 5 minutes after midnight +} + + +; publish abs hum every teleperiod time +if mqtts>0 +and upsecs%tper==0 +then +; calc abs humidity +tmp=pow(2.718281828 (17.67\*temp)/(temp+243.5)) +tmp=(6.112\*tmp\*hum\*18.01534)/((273.15+temp)\*8.31447215) +; publish median filtered value +=>Publish tele/%topic%/SENSOR {"Script":{"abshum":%med(0 tmp)%}} +endif + +;switch case state machine +switch state +case 1 +=>print state=%state% , start +state+=1 +case 2 +=>print state=%state% +state+=1 +case 3 +=>print state=%state% , reset +state=1 +ends + + +; subroutines +\#sub1(string) +=>print sub1: %string% +\#sub2(param) +=>print sub2: %param% + +\#sendmail(string) +=>sendmail [smtp.gmail.com:465:user:passwd:::alarm] %string% + +**\>E** +=\>print event executed! + + +; check if switch changed state +sw=sw[1] +if chg[sw]>0 +then =\>power1 %sw% +endif + +hello="event occured" + +; check for Color change (Color is a string) +col=Color +; color change needs 2 string vars +if col!=ocol +then ocol=col +=>print color changed %col% +endif + +; or check change of color channels +chan1=Channel[1] +chan2=Channel[2] +chan3=Channel[3] + +if chg[chan1]>0 +or chg[chan2]>0 +or chg[chan3]>0 +then => color has changed +endif + +; compose color string for red +col=hn(255)+hn(0)+hn(0) +=>color %col% + +**\>R** +=\>print restarting now + +**a real example** +epaper 29 with sgp30 and bme280 +some vars are set from iobroker +DisplayText substituted to save script space +\>D +hum=0 +temp=0 +press=0 +ahum=0 +tvoc=0 +eco2=0 +zwz=0 +wr1=0 +wr2=0 +wr3=0 +otmp=0 +pwl=0 +tmp=0 +DT="DisplayText" +; preset units in case they are not available +punit="hPa" +tunit="C" + +\>B +;reset auto draw +=>%DT% [zD0] +;clr display and draw a frame +=>%DT% [x0y20h296x0y40h296] + +\>T +; get tele vars +temp=BME280#Temperature +hum=BME280#Humidity +press=BME280#Pressure +tvoc=SGP30#TVOC +eco2=SGP30#eCO2 +ahum=SGP30#aHumidity +tunit=TempUnit +punit=PressureUnit + +\>S +// update display every teleperiod time +if upsecs%tper==0 +then +dprec2 +=>%DT% [f1p7x0y5]%temp% %tunit% +=>%DT% [p5x70y5]%hum% %%[x250y5t] +=>%DT% [p11x140y5]%press% %punit% +=>%DT% [p10x30y25]TVOC: %tvoc% ppb +=>%DT% [p10x160y25]eCO2: %eco2% ppm +=>%DT% [p10c26l5]ahum: %ahum% g^m3 + +dprec0 +=>%DT% [p25c1l5]WR 1 (Dach) : %wr1% W +=>%DT% [p25c1l6]WR 2 (Garage): %-wr3% W +=>%DT% [p25c1l7]WR 3 (Garten): %-wr2% W +=>%DT% [p25c1l8]Aussentemperatur: %otmp% C +=>%DT% [x170y95r120:30f2p6x185y100] %pwl% %% +; now update screen +=>%DT% [d] +endif + + +\>E + +\>R + +**another real example** +ILI 9488 color LCD Display shows various energy graphs +display switches on and off with proximity sensor +BMP280 and vl5310x +some vars are set from iobroker + +**>D** +temp=0 +press=0 +zwz=0 +wr1=0 +wr2=0 +wr3=0 +otmp=0 +pwl=0 +tmp=0 +dist=0 +DT="DisplayText" +punit="hPa" +tunit="C" +hour=0 + +**>B** +=>%DT% [z] + +// define 2 graphs, 2. has 3 tracks +=>%DT% [zCi1G2656:5:20:400:80:1440:-5000:5000:3Ci7f3x410y20]+5000 W[x410y95]-5000 W [Ci7f1x70y3] Zweirichtungsz~80hler - 24 Stunden +=>%DT% [Ci1G2657:5:120:400:80:1440:0:5000:3Ci7f3x410y120]+5000 W[x410y195]0 W [Ci7f1x70y103] Wechselrichter 1-3 - 24 Stunden +=>%DT% [Ci1G2658:5:120:400:80:1440:0:5000:16][Ci1G2659:5:120:400:80:1440:0:5000:5] +=>%DT% [f1s1b0:260:260:100:50:2:11:4:2:Rel 1:b1:370:260:100:50:2:11:4:2:Dsp off:] +=>mp3volume 100 +=>mp3track 4 + +**>T** +; get some tele vars +temp=BMP280#Temperature +press=BMP280#Pressure +tunit=TempUnit +punit=PressureUnit +dist=VL53L0X#Distance + +; check proximity sensor to switch display on/off +; to prevent burn in +if dist>300 +then +if pwr[2]>0 +then +=>power2 0 +endif +else +if pwr[2]==0 +then +=>power2 1 +endif +endif + + +**>S** +; update graph every teleperiod +if upsecs%tper==0 +then +dprec2 +=>%DT% [f1Ci3x40y260w30Ci1] +=>%DT% [Ci7x120y220t] +=>%DT% [Ci7x180y220T] +=>%DT% [Ci7p8x120y240]%temp% %tunit% +=>%DT% [Ci7x120y260]%press% %punit% +=>%DT% [Ci7x120y280]%dist% mm +dprec0 +=>%DT% [g0:%zwz%g1:%wr1%g2:%-wr2%g3:%-wr3%] +if zwz>0 +then +=>%DT% [p-8x410y55Ci2Bi0]%zwz% W +else +=>%DT% [p-8x410y55Ci3Bi0]%zwz% W +endif +=>%DT% [p-8x410y140Ci3Bi0]%wr1% W +=>%DT% [p-8x410y155Ci16Bi0]%-wr2% W +=>%DT% [p-8x410y170Ci5Bi0]%-wr3% W +endif + +; chime every full hour +hour=int(time/60) +if chg[hour]>0 +then =>mp3track 4 +endif + +**>E** + +**>R** diff --git a/sonoff/language/bg-BG.h b/sonoff/language/bg-BG.h index a1913c612..3209cc101 100644 --- a/sonoff/language/bg-BG.h +++ b/sonoff/language/bg-BG.h @@ -513,6 +513,7 @@ #define D_SENSOR_BUTTON "Бутон" // Suffix "1" #define D_SENSOR_RELAY "Реле" // Suffix "1i" #define D_SENSOR_LED "Led" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "Брояч" // Suffix "1" #define D_SENSOR_IRRECV "IRrecv" @@ -578,6 +579,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/cs-CZ.h b/sonoff/language/cs-CZ.h index fcf9b7ec5..8a919715a 100644 --- a/sonoff/language/cs-CZ.h +++ b/sonoff/language/cs-CZ.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Tlačítko" // Suffix "1" #define D_SENSOR_RELAY "Relé" // Suffix "1i" #define D_SENSOR_LED "Led" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1", #define D_SENSOR_COUNTER "Počítadlo" // Suffix "1" #define D_SENSOR_IRRECV "IRrecv" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/de-DE.h b/sonoff/language/de-DE.h index 171256aa8..efc1b737a 100644 --- a/sonoff/language/de-DE.h +++ b/sonoff/language/de-DE.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Button" // Suffix "1" #define D_SENSOR_RELAY "Relay" // Suffix "1i" #define D_SENSOR_LED "Led" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "Counter" // Suffix "1" #define D_SENSOR_IRRECV "IRrecv" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/el-GR.h b/sonoff/language/el-GR.h index 238ad7050..155bf2f4f 100644 --- a/sonoff/language/el-GR.h +++ b/sonoff/language/el-GR.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Button" // Suffix "1" #define D_SENSOR_RELAY "Relay" // Suffix "1i" #define D_SENSOR_LED "Led" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "Counter" // Suffix "1" #define D_SENSOR_IRRECV "IRrecv" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/en-GB.h b/sonoff/language/en-GB.h index d8a78cec0..8d300a711 100644 --- a/sonoff/language/en-GB.h +++ b/sonoff/language/en-GB.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Button" // Suffix "1" #define D_SENSOR_RELAY "Relay" // Suffix "1i" #define D_SENSOR_LED "Led" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "Counter" // Suffix "1" #define D_SENSOR_IRRECV "IRrecv" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/es-ES.h b/sonoff/language/es-ES.h index caddf2f06..24200b752 100644 --- a/sonoff/language/es-ES.h +++ b/sonoff/language/es-ES.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Botón" // Suffix "1" #define D_SENSOR_RELAY "Relé" // Suffix "1i" #define D_SENSOR_LED "Led" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "Contador" // Suffix "1" #define D_SENSOR_IRRECV "IR Rx" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/fr-FR.h b/sonoff/language/fr-FR.h index ac60a779e..493e8b395 100644 --- a/sonoff/language/fr-FR.h +++ b/sonoff/language/fr-FR.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Bouton" // Suffix "1" #define D_SENSOR_RELAY "Relais" // Suffix "1i" #define D_SENSOR_LED "LED" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "Compteur" // Suffix "1" #define D_SENSOR_IRRECV "RécptIR" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/he-HE.h b/sonoff/language/he-HE.h index 86811cae7..c36f050a7 100644 --- a/sonoff/language/he-HE.h +++ b/sonoff/language/he-HE.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "לחצן" // Suffix "1" #define D_SENSOR_RELAY "ממסר" // Suffix "1i" #define D_SENSOR_LED "לד" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "מונה" // Suffix "1" #define D_SENSOR_IRRECV "IRrecv" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/hu-HU.h b/sonoff/language/hu-HU.h index 50e266aa5..3b3ef7821 100644 --- a/sonoff/language/hu-HU.h +++ b/sonoff/language/hu-HU.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Gomb" // Suffix "1" #define D_SENSOR_RELAY "Relé" // Suffix "1i" #define D_SENSOR_LED "LED" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "Számláló" // Suffix "1" #define D_SENSOR_IRRECV "IR vevő" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/it-IT.h b/sonoff/language/it-IT.h index 082526cb8..9078bc81e 100644 --- a/sonoff/language/it-IT.h +++ b/sonoff/language/it-IT.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Button" // Suffix "1" #define D_SENSOR_RELAY "Relay" // Suffix "1i" #define D_SENSOR_LED "Led" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "Counter" // Suffix "1" #define D_SENSOR_IRRECV "IRrecv" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/ko-KO.h b/sonoff/language/ko-KO.h index be7c36b64..37c289963 100644 --- a/sonoff/language/ko-KO.h +++ b/sonoff/language/ko-KO.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Button" // Suffix "1" #define D_SENSOR_RELAY "Relay" // Suffix "1i" #define D_SENSOR_LED "Led" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "Counter" // Suffix "1" #define D_SENSOR_IRRECV "IRrecv" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/nl-NL.h b/sonoff/language/nl-NL.h index 565ff2164..4525ce5cb 100644 --- a/sonoff/language/nl-NL.h +++ b/sonoff/language/nl-NL.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Button" // Suffix "1" #define D_SENSOR_RELAY "Relais" // Suffix "1i" #define D_SENSOR_LED "Led" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "Teller" // Suffix "1" #define D_SENSOR_IRRECV "IRrecv" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/pl-PL.h b/sonoff/language/pl-PL.h index 6605e6867..a2ed0721c 100644 --- a/sonoff/language/pl-PL.h +++ b/sonoff/language/pl-PL.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Przyci" // Suffix "1" #define D_SENSOR_RELAY "Przek" // Suffix "1i" #define D_SENSOR_LED "Led" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "Liczni" // Suffix "1" #define D_SENSOR_IRRECV "IRrecv" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/pt-BR.h b/sonoff/language/pt-BR.h index 953fbe979..c66867178 100644 --- a/sonoff/language/pt-BR.h +++ b/sonoff/language/pt-BR.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Botão" // Suffix "1" #define D_SENSOR_RELAY "Relé" // Suffix "1i" #define D_SENSOR_LED "Led" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "Contador" // Suffix "1" #define D_SENSOR_IRRECV "IRrecv" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/pt-PT.h b/sonoff/language/pt-PT.h index 880471f69..334844a72 100644 --- a/sonoff/language/pt-PT.h +++ b/sonoff/language/pt-PT.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Botão" // Suffix "1" #define D_SENSOR_RELAY "Relé" // Suffix "1i" #define D_SENSOR_LED "Led" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "Contador" // Suffix "1" #define D_SENSOR_IRRECV "IRrecv" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/ru-RU.h b/sonoff/language/ru-RU.h index 2f56eb44f..071be1394 100644 --- a/sonoff/language/ru-RU.h +++ b/sonoff/language/ru-RU.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Кнопка" // Suffix "1" #define D_SENSOR_RELAY "Реле" // Suffix "1i" #define D_SENSOR_LED "Led" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "Счетчик" // Suffix "1" #define D_SENSOR_IRRECV "IRrecv" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/sk-SK.h b/sonoff/language/sk-SK.h index 92b551488..7cdea4177 100644 --- a/sonoff/language/sk-SK.h +++ b/sonoff/language/sk-SK.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Tlačidlo" // Suffix "1" #define D_SENSOR_RELAY "Relé" // Suffix "1i" #define D_SENSOR_LED "Led" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1", #define D_SENSOR_COUNTER "Počítadlo" // Suffix "1" #define D_SENSOR_IRRECV "IRrecv" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/sv-SE.h b/sonoff/language/sv-SE.h index e34847480..f6b0638bd 100644 --- a/sonoff/language/sv-SE.h +++ b/sonoff/language/sv-SE.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Knapp" // Suffix "1" #define D_SENSOR_RELAY "Relä" // Suffix "1i" #define D_SENSOR_LED "Led" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "Räknare" // Suffix "1" #define D_SENSOR_IRRECV "IRrecv" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/tr-TR.h b/sonoff/language/tr-TR.h index f6a7ba233..dc764d006 100755 --- a/sonoff/language/tr-TR.h +++ b/sonoff/language/tr-TR.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Button" // Suffix "1" #define D_SENSOR_RELAY "Relay" // Suffix "1i" #define D_SENSOR_LED "Led" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "Counter" // Suffix "1" #define D_SENSOR_IRRECV "IRrecv" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/uk-UK.h b/sonoff/language/uk-UK.h index 54b2bfa39..6f4d6208c 100644 --- a/sonoff/language/uk-UK.h +++ b/sonoff/language/uk-UK.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Кнопка" // Suffix "1" #define D_SENSOR_RELAY "Реле" // Suffix "1i" #define D_SENSOR_LED "Led" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "Лічильник" // Suffix "1" #define D_SENSOR_IRRECV "IRrecv" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/zh-CN.h b/sonoff/language/zh-CN.h index be8e1c91c..a1bca5a88 100644 --- a/sonoff/language/zh-CN.h +++ b/sonoff/language/zh-CN.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Button" // Suffix "1" #define D_SENSOR_RELAY "Relay" // Suffix "1i" #define D_SENSOR_LED "Led" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "Counter" // Suffix "1" #define D_SENSOR_IRRECV "IRrecv" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/language/zh-TW.h b/sonoff/language/zh-TW.h index 07d63e30a..9b87d03f3 100644 --- a/sonoff/language/zh-TW.h +++ b/sonoff/language/zh-TW.h @@ -512,6 +512,7 @@ #define D_SENSOR_BUTTON "Button" // Suffix "1" #define D_SENSOR_RELAY "Relay" // Suffix "1i" #define D_SENSOR_LED "Led" // Suffix "1i" +#define D_SENSOR_LED_LINK "LedLink" // Suffix "i" #define D_SENSOR_PWM "PWM" // Suffix "1" #define D_SENSOR_COUNTER "Counter" // Suffix "1" #define D_SENSOR_IRRECV "IRrecv" @@ -577,6 +578,7 @@ #define D_SENSOR_MY92X1_DI "MY92x1 DI" #define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI" #define D_SENSOR_ARIRFRCV "ALux IrRcv" +#define D_SENSOR_ARIRFSEL "ALux IrSel" #define D_SENSOR_TXD "Serial Tx" #define D_SENSOR_RXD "Serial Rx" #define D_SENSOR_ROTARY "Rotary" // Suffix "1A" diff --git a/sonoff/my_user_config.h b/sonoff/my_user_config.h index 16be53cac..b18949005 100644 --- a/sonoff/my_user_config.h +++ b/sonoff/my_user_config.h @@ -275,7 +275,8 @@ #define USE_WEBSERVER // Enable web server and Wifi Manager (+66k code, +8k mem) #define WEB_PORT 80 // Web server Port for User and Admin mode #define WEB_USERNAME "admin" // Web server Admin mode user name - #define USE_EMULATION // Enable Belkin WeMo and Hue Bridge emulation for Alexa (+22k code, +2k mem) + #define USE_EMULATION_HUE // Enable Hue Bridge emulation for Alexa (+14k code, +2k mem common) + #define USE_EMULATION_WEMO // Enable Belkin WeMo emulation for Alexa (+6k code, +2k mem common) // -- mDNS ---------------------------------------- #define USE_DISCOVERY // Enable mDNS for the following services (+8k code, +0.3k mem) @@ -288,8 +289,11 @@ #define USE_SUNRISE // Add support for Sunrise and sunset tools (+16k) #define SUNRISE_DAWN_ANGLE DAWN_NORMAL // Select desired Dawn Angle from (DAWN_NORMAL, DAWN_CIVIL, DAWN_NAUTIC, DAWN_ASTRONOMIC) -// -- Rules --------------------------------------- -#define USE_RULES // Add support for rules (+4k4 code) +// -- Rules or Script ---------------------------- +// Select none or only one of the below defines +#define USE_RULES // Add support for rules (+8k code) +//#define USE_SCRIPT // Add support for script (+15k code) + // #define USE_EXPRESSION // Add support for expression evaluation in rules (+3k2 code, +64 bytes mem) // #define SUPPORT_MQTT_EVENT // Support trigger event with MQTT subscriptions (+3k5 code) @@ -342,6 +346,7 @@ // #define USE_MGC3130 // Enable MGC3130 Electric Field Effect Sensor (I2C address 0x42) (+2k7 code, 0k3 mem) // #define USE_MAX44009 // Enable MAX44009 Ambient Light sensor (I2C addresses 0x4A and 0x4B) (+0k8 code) // #define USE_SCD30 // Enable Sensiron SCd30 CO2 sensor (I2C address 0x61) (+3k3 code) + #define USE_SPS30 // Enable Sensiron SPS30 particle sensor (I2C address 0x69) (+1.7 code) #define USE_ADE7953 // Enable ADE7953 Energy monitor as used on Shelly 2.5 (I2C address 0x38) (+1k5) // #define USE_DISPLAY // Add I2C Display Support (+2k code) diff --git a/sonoff/settings.h b/sonoff/settings.h index 185880a00..f28e95489 100644 --- a/sonoff/settings.h +++ b/sonoff/settings.h @@ -211,8 +211,9 @@ struct SYSCFG { uint8_t weblog_level; // 1AC uint8_t mqtt_fingerprint[2][20]; // 1AD - uint8_t free_1D5[20]; // 1D5 Free since 5.12.0e + uint8_t free_1D5[19]; // 1D5 Free since 5.12.0e + uint8_t sps30_inuse_hours; // 1E8 char mqtt_host[33]; // 1E9 - Keep together with below as being copied as one chunck with reset 6 uint16_t mqtt_port; // 20A - Keep together char mqtt_client[33]; // 20C - Keep together @@ -345,7 +346,7 @@ struct SYSCFG { uint16_t weight_max; // 7BE Total max weight in kilogram unsigned long weight_reference; // 7C0 Reference weight in gram unsigned long weight_calibration; // 7C4 - unsigned long energy_frequency_calibration; // 7C8 + unsigned long energy_frequency_calibration; // 7C8 also used by HX711 to save last weight uint16_t web_refresh; // 7CC char mems[MAX_RULE_MEMS][10]; // 7CE char rules[MAX_RULE_SETS][MAX_RULE_SIZE]; // 800 uses 512 bytes in v5.12.0m, 3 x 512 bytes in v5.14.0b diff --git a/sonoff/settings.ino b/sonoff/settings.ino index 4fee5e8cc..a8445b232 100644 --- a/sonoff/settings.ino +++ b/sonoff/settings.ino @@ -444,6 +444,7 @@ void SettingsSaveAll(void) Settings.power = 0; } XsnsCall(FUNC_SAVE_BEFORE_RESTART); + XdrvCall(FUNC_SAVE_BEFORE_RESTART); #ifdef USE_EEPROM EepromCommit(); #endif diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index d90c8ef68..c935a0325 100755 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -143,7 +143,10 @@ uint8_t backlog_pointer = 0; // Command backlog pointer uint8_t sleep; // Current copy of Settings.sleep uint8_t blinkspeed = 1; // LED blink rate uint8_t pin[GPIO_MAX]; // Possible pin configurations +uint8_t leds_present = 0; // Max number of LED supported uint8_t led_inverted = 0; // LED inverted flag (1 = (0 = On, 1 = Off)) +uint8_t led_power = 0; // LED power state +uint8_t ledlnk_inverted = 0; // Link LED inverted flag (1 = (0 = On, 1 = Off)) uint8_t pwm_inverted = 0; // PWM inverted flag (1 = inverted) uint8_t counter_no_pullup = 0; // Counter input pullup flag (1 = No pullup) uint8_t energy_flg = 0; // Energy monitor configured @@ -370,19 +373,56 @@ void SetDevicePower(power_t rpower, int source) } } +void SetLedPowerIdx(uint8_t led, uint8_t state) +{ + if ((99 == pin[GPIO_LEDLNK]) && (0 == led)) { // Legacy - LED1 is link led only if LED2 is present + if (pin[GPIO_LED2] < 99) { led = 1; } + } + if (pin[GPIO_LED1 + led] < 99) { + uint8_t mask = 1 << led; + if (state) { + state = 1; + led_power |= mask; + } else { + led_power &= (0xFF ^ mask); + } + digitalWrite(pin[GPIO_LED1 + led], bitRead(led_inverted, led) ? !state : state); + } +} + void SetLedPower(uint8_t state) { - if (state) { state = 1; } + if (99 == pin[GPIO_LEDLNK]) { // Legacy - Only use LED1 and/or LED2 + SetLedPowerIdx(0, state); + } else { + power_t mask = 1; + for (uint8_t i = 0; i < leds_present; i++) { // Map leds to power + bool tstate = (power & mask); + SetLedPowerIdx(i, tstate); + mask <<= 1; + } + } +} - uint8_t led_pin = 0; - if (pin[GPIO_LED2] < 99) { led_pin = 1; } - digitalWrite(pin[GPIO_LED1 + led_pin], (bitRead(led_inverted, led_pin)) ? !state : state); +void SetLedPowerAll(uint8_t state) +{ + for (uint8_t i = 0; i < leds_present; i++) { + SetLedPowerIdx(i, state); + } } void SetLedLink(uint8_t state) { - if (state) { state = 1; } - digitalWrite(pin[GPIO_LED1], (bitRead(led_inverted, 0)) ? !state : state); + uint8_t led_pin = pin[GPIO_LEDLNK]; + uint8_t led_inv = ledlnk_inverted; + if (99 == led_pin) { // Legacy - LED1 is status + led_pin = pin[GPIO_LED1]; + led_inv = bitRead(led_inverted, 0); + } + if (led_pin < 99) { + if (state) { state = 1; } + digitalWrite(led_pin, (led_inv) ? !state : state); + } } uint8_t GetFanspeed(void) @@ -1422,7 +1462,8 @@ void MqttDataHandler(char* topic, uint8_t* data, unsigned int data_len) } Response_P(S_JSON_COMMAND_NVALUE, command, Settings.altitude); } - else if (CMND_LEDPOWER == command_code) { + else if ((CMND_LEDPOWER == command_code) && (index > 0) && (index <= MAX_LEDS)) { +/* if ((payload >= 0) && (payload <= 2)) { Settings.ledstate &= 8; switch (payload) { @@ -1435,15 +1476,83 @@ void MqttDataHandler(char* topic, uint8_t* data, unsigned int data_len) break; } blinks = 0; - SetLedPower(Settings.ledstate &8); + SetLedPowerIdx(index -1, Settings.ledstate &8); } - Response_P(S_JSON_COMMAND_SVALUE, command, GetStateText(bitRead(Settings.ledstate, 3))); + Response_P(S_JSON_COMMAND_INDEX_SVALUE, command, index, GetStateText(bitRead(Settings.ledstate, 3))); +*/ +/* + if (99 == pin[GPIO_LEDLNK]) { + if ((payload >= 0) && (payload <= 2)) { + Settings.ledstate &= 8; + switch (payload) { + case 0: // Off + case 1: // On + Settings.ledstate = payload << 3; + break; + case 2: // Toggle + Settings.ledstate ^= 8; + break; + } + blinks = 0; + SetLedPower(Settings.ledstate &8); + } + Response_P(S_JSON_COMMAND_SVALUE, command, GetStateText(bitRead(Settings.ledstate, 3))); + } else { + if ((payload >= 0) && (payload <= 2)) { + Settings.ledstate &= 8; // Disable power control + uint8_t mask = 1 << (index -1); // Led to control + switch (payload) { + case 0: // Off + led_power &= (0xFF ^ mask); + case 1: // On + led_power |= mask; + break; + case 2: // Toggle + led_power ^= mask; + break; + } + blinks = 0; + SetLedPowerIdx(index -1, (led_power & mask)); + } + Response_P(S_JSON_COMMAND_INDEX_SVALUE, command, index, GetStateText(bitRead(led_power, index -1))); + } +*/ + if (99 == pin[GPIO_LEDLNK]) { index = 1; } + if ((payload >= 0) && (payload <= 2)) { + Settings.ledstate &= 8; // Disable power control + uint8_t mask = 1 << (index -1); // Led to control + switch (payload) { + case 0: // Off + led_power &= (0xFF ^ mask); + Settings.ledstate = 0; + break; + case 1: // On + led_power |= mask; + Settings.ledstate = 8; + break; + case 2: // Toggle + led_power ^= mask; + Settings.ledstate ^= 8; + break; + } + blinks = 0; + if (99 == pin[GPIO_LEDLNK]) { + SetLedPower(Settings.ledstate &8); + } else { + SetLedPowerIdx(index -1, (led_power & mask)); + } + } + uint8_t state = bitRead(led_power, index -1); + if (99 == pin[GPIO_LEDLNK]) { + state = bitRead(Settings.ledstate, 3); + } + Response_P(S_JSON_COMMAND_INDEX_SVALUE, command, index, GetStateText(state)); } else if (CMND_LEDSTATE == command_code) { if ((payload >= 0) && (payload < MAX_LED_OPTION)) { Settings.ledstate = payload; if (!Settings.ledstate) { - SetLedPower(0); + SetLedPowerAll(0); SetLedLink(0); } } @@ -1469,7 +1578,12 @@ void MqttDataHandler(char* topic, uint8_t* data, unsigned int data_len) Response_P(PSTR("{\"" D_JSON_COMMAND "\":\"" D_JSON_UNKNOWN "\"}")); type = (char*)topicBuf; } - if (mqtt_data[0] != '\0') { MqttPublishPrefixTopic_P(RESULT_OR_STAT, type); } + if (mqtt_data[0] != '\0') { + MqttPublishPrefixTopic_P(RESULT_OR_STAT, type); +#ifdef USE_SCRIPT + XdrvRulesProcess(); +#endif + } fallback_topic_flag = false; } @@ -1838,6 +1952,9 @@ void MqttPublishTeleState(void) mqtt_data[0] = '\0'; MqttShowState(); MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_STATE), MQTT_TELE_RETAIN); +#ifdef USE_SCRIPT + RulesTeleperiod(); // Allow rule based HA messages +#endif // USE_SCRIPT } bool MqttShowSensor(void) @@ -1922,7 +2039,7 @@ void PerformEverySecond(void) mqtt_data[0] = '\0'; if (MqttShowSensor()) { MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain); -#ifdef USE_RULES +#if defined(USE_RULES) || defined(USE_SCRIPT) RulesTeleperiod(); // Allow rule based HA messages #endif // USE_RULES } @@ -2025,8 +2142,6 @@ void Every250mSeconds(void) } } if ((!(Settings.ledstate &0x08)) && ((Settings.ledstate &0x06) || (blinks > 200) || (blinkstate))) { -// if ( (!Settings.flag.global_state && global_state.data) || ((!(Settings.ledstate &0x08)) && ((Settings.ledstate &0x06) || (blinks > 200) || (blinkstate))) ) { -// SetLedPower(blinkstate); // Set led on or off SetLedLink(blinkstate); // Set led on or off } if (!blinkstate) { @@ -2176,7 +2291,9 @@ void Every250mSeconds(void) SettingsDefault(); restart_flag = 2; } - SettingsSaveAll(); + if (2 == restart_flag) { + SettingsSaveAll(); + } restart_flag--; if (restart_flag <= 0) { AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_RESTARTING)); @@ -2449,6 +2566,10 @@ void GpioInit(void) bitSet(led_inverted, mpin - GPIO_LED1_INV); mpin -= (GPIO_LED1_INV - GPIO_LED1); } + else if (mpin == GPIO_LEDLNK_INV) { + ledlnk_inverted = 1; + mpin -= (GPIO_LEDLNK_INV - GPIO_LEDLNK); + } else if ((mpin >= GPIO_PWM1_INV) && (mpin < (GPIO_PWM1_INV + MAX_PWMS))) { bitSet(pwm_inverted, mpin - GPIO_PWM1_INV); mpin -= (GPIO_PWM1_INV - GPIO_PWM1); @@ -2560,10 +2681,24 @@ void GpioInit(void) for (uint8_t i = 0; i < MAX_LEDS; i++) { if (pin[GPIO_LED1 +i] < 99) { - pinMode(pin[GPIO_LED1 +i], OUTPUT); - digitalWrite(pin[GPIO_LED1 +i], bitRead(led_inverted, i)); +#ifdef USE_ARILUX_RF + if ((3 == i) && (leds_present < 2) && (99 == pin[GPIO_ARIRFSEL])) { + pin[GPIO_ARIRFSEL] = pin[GPIO_LED4]; // Legacy support where LED4 was Arilux RF enable + pin[GPIO_LED4] = 99; + } else { +#endif + pinMode(pin[GPIO_LED1 +i], OUTPUT); + leds_present++; + digitalWrite(pin[GPIO_LED1 +i], bitRead(led_inverted, i)); +#ifdef USE_ARILUX_RF + } +#endif } } + if (pin[GPIO_LEDLNK] < 99) { + pinMode(pin[GPIO_LEDLNK], OUTPUT); + digitalWrite(pin[GPIO_LEDLNK], ledlnk_inverted); + } ButtonInit(); SwitchInit(); @@ -2645,6 +2780,13 @@ void setup(void) sleep = Settings.sleep; #ifndef USE_EMULATION Settings.flag2.emulation = 0; +#else +#ifndef USE_EMULATION_WEMO + if (EMUL_WEMO == Settings.flag2.emulation) { Settings.flag2.emulation = 0; } +#endif +#ifndef USE_EMULATION_HUE + if (EMUL_HUE == Settings.flag2.emulation) { Settings.flag2.emulation = 0; } +#endif #endif // USE_EMULATION if (Settings.param[P_BOOT_LOOP_OFFSET]) { diff --git a/sonoff/sonoff_post.h b/sonoff/sonoff_post.h index 10a466981..6089adfa5 100644 --- a/sonoff/sonoff_post.h +++ b/sonoff/sonoff_post.h @@ -46,6 +46,13 @@ void KNX_CB_Action(message_t const &msg, void *arg); * Default global defines \*********************************************************************************************/ +#ifdef USE_EMULATION_HUE +#define USE_EMULATION +#endif +#ifdef USE_EMULATION_WEMO +#define USE_EMULATION +#endif + #ifndef MODULE #define MODULE SONOFF_BASIC // [Module] Select default model #endif diff --git a/sonoff/sonoff_template.h b/sonoff/sonoff_template.h index 071c1f420..2cf4404f6 100644 --- a/sonoff/sonoff_template.h +++ b/sonoff/sonoff_template.h @@ -171,7 +171,7 @@ enum UserSelectablePins { GPIO_DCKI, // my92x1 CLK input GPIO_CSE7766_TX, // CSE7766 Serial interface (S31 and Pow R2) GPIO_CSE7766_RX, // CSE7766 Serial interface (S31 and Pow R2) - GPIO_ARIRFRCV, // AliLux RF Receive input + GPIO_ARIRFRCV, // AriLux RF Receive input GPIO_TXD, // Serial interface GPIO_RXD, // Serial interface GPIO_ROT1A, // Rotary switch1 A Pin @@ -181,6 +181,9 @@ enum UserSelectablePins { GPIO_HRE_CLOCK, // Clock/Power line for HR-E Water Meter GPIO_HRE_DATA, // Data line for HR-E Water Meter GPIO_ADE7953_IRQ, // ADE7953 IRQ + GPIO_LEDLNK, // Link led + GPIO_LEDLNK_INV, // Inverted link led + GPIO_ARIRFSEL, // Arilux RF Receive input selected GPIO_SENSOR_END }; // Programmer selectable GPIO functionality @@ -246,6 +249,8 @@ const char kSensorNames[] PROGMEM = D_SENSOR_ROTARY "1a|" D_SENSOR_ROTARY "1b|" D_SENSOR_ROTARY "2a|" D_SENSOR_ROTARY "2b|" D_SENSOR_HRE_CLOCK "|" D_SENSOR_HRE_DATA "|" D_SENSOR_ADE7953_IRQ "|" + D_SENSOR_LED_LINK "|" D_SENSOR_LED_LINK "i|" + D_SENSOR_ARIRFSEL "|" ; // User selectable ADC0 functionality @@ -448,6 +453,8 @@ const uint8_t kGpioNiceList[] PROGMEM = { GPIO_LED3_INV, GPIO_LED4, GPIO_LED4_INV, + GPIO_LEDLNK, // Link led + GPIO_LEDLNK_INV, // Inverted link led GPIO_PWM1, // RGB Red or C Cold White GPIO_PWM1_INV, GPIO_PWM2, // RGB Green or CW Warm White @@ -616,7 +623,8 @@ const uint8_t kGpioNiceList[] PROGMEM = { GPIO_ROT2B, // Rotary switch2 B Pin #endif #ifdef USE_ARILUX_RF - GPIO_ARIRFRCV, // AliLux RF Receive input + GPIO_ARIRFRCV, // AriLux RF Receive input + GPIO_ARIRFSEL, // Arilux RF Receive input selected #endif #ifdef USE_HRE GPIO_HRE_CLOCK, @@ -1109,9 +1117,9 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { 0, 0 }, { "Huafan SS", // Hua Fan Smart Socket (ESP8266) - like Sonoff Pow - GPIO_LED1_INV, // GPIO00 Blue Led (0 = On, 1 = Off) - Link status + GPIO_LEDLNK_INV, // GPIO00 Blue Led (0 = On, 1 = Off) - Link status 0, 0, - GPIO_LED2_INV, // GPIO03 Red Led (0 = On, 1 = Off) - Power status + GPIO_LED1_INV, // GPIO03 Red Led (0 = On, 1 = Off) - Power status GPIO_KEY1, // GPIO04 Button GPIO_REL1_INV, // GPIO05 Relay (0 = On, 1 = Off) // GPIO06 (SD_CLK Flash) @@ -1304,7 +1312,7 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { GPIO_PWM3, // GPIO12 RGB LED Blue GPIO_USER, // GPIO13 RGBW LED White (optional - set to PWM4 for Cold White or Warm White as used on Arilux LC10) GPIO_PWM1, // GPIO14 RGB LED Red - GPIO_LED4_INV, // GPIO15 RF receiver control (Arilux LC10) + GPIO_ARIRFSEL, // GPIO15 RF receiver control (Arilux LC10) 0, 0 }, { "Luani HVIO", // ESP8266_HVIO @@ -1350,7 +1358,7 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { // (PwmFrequency 1111Hz) GPIO_KEY1, // GPIO00 Optional Button GPIO_USER, // GPIO01 Serial RXD and Optional sensor - GPIO_LED4_INV, // GPIO02 RF receiver control + GPIO_ARIRFSEL, // GPIO02 RF receiver control GPIO_USER, // GPIO03 Serial TXD and Optional sensor GPIO_ARIRFRCV, // GPIO04 IR or RF receiver (optional) GPIO_PWM1, // GPIO05 RGB LED Red @@ -1370,7 +1378,7 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { // (PwmFrequency 540Hz) GPIO_KEY1, // GPIO00 Optional Button GPIO_USER, // GPIO01 Serial RXD and Optional sensor - GPIO_LED4_INV, // GPIO02 RF receiver control + GPIO_ARIRFSEL, // GPIO02 RF receiver control GPIO_USER, // GPIO03 Serial TXD and Optional sensor GPIO_PWM2, // GPIO04 RGB LED Green GPIO_PWM1, // GPIO05 RGB LED Red @@ -1498,9 +1506,9 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { // https://www.amazon.de/Steckdose-Homecube-intelligente-Verbrauchsanzeige-funktioniert/dp/B076Q2LKHG/ref=sr_1_fkmr0_1 // https://www.amazon.de/Intelligente-Stromverbrauch-Fernsteurung-Schaltbare-Energieklasse/dp/B076WZQS4S/ref=sr_1_1 // https://www.aliexpress.com/store/product/BlitzWolf-BW-SHP6-EU-Plug-Metering-Version-WIFI-Smart-Socket-220V-240V-10A-Work-with-Amazon/1965360_32945504669.html - GPIO_LED2_INV, // GPIO00 Red Led (1 = On, 0 = Off) - Power status + GPIO_LED1_INV, // GPIO00 Red Led (1 = On, 0 = Off) - Power status GPIO_USER, // GPIO01 Serial RXD and Optional sensor - GPIO_LED1_INV, // GPIO02 Blue Led (1 = On, 0 = Off) - Link status + GPIO_LEDLNK_INV, // GPIO02 Blue Led (1 = On, 0 = Off) - Link status GPIO_USER, // GPIO03 Serial TXD and Optional sensor 0, GPIO_HJL_CF, // GPIO05 BL0937 or HJL-01 CF power @@ -1624,7 +1632,7 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { 0, GPIO_KEY1, // GPIO01 Serial TXD and Button 0, - GPIO_LED2_INV, // GPIO03 Serial RXD and Red Led (0 = On, 1 = Off) - Power status + GPIO_LED1_INV, // GPIO03 Serial RXD and Red Led (0 = On, 1 = Off) - Power status GPIO_HJL_CF, // GPIO04 BL0937 or HJL-01 CF power GPIO_NRG_CF1, // GPIO05 BL0937 or HJL-01 CF1 current / voltage // GPIO06 (SD_CLK Flash) @@ -1634,7 +1642,7 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { 0, // GPIO10 (SD_DATA3 Flash QIO or ESP8285) // GPIO11 (SD_CMD Flash) GPIO_NRG_SEL_INV, // GPIO12 BL0937 or HJL-01 Sel output (0 = Voltage) - GPIO_LED1_INV, // GPIO13 Blue Led (0 = On, 1 = Off) - Link status + GPIO_LEDLNK_INV, // GPIO13 Blue Led (0 = On, 1 = Off) - Link status GPIO_REL1, // GPIO14 Relay (0 = Off, 1 = On) 0, 0, 0 }, @@ -1678,7 +1686,7 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { }, { "Gosund SP1 v23", // https://www.amazon.de/gp/product/B0777BWS1P 0, - GPIO_LED1_INV, // GPIO01 Serial RXD and LED1 (blue) inv - Link status + GPIO_LEDLNK_INV, // GPIO01 Serial RXD and LED1 (blue) inv - Link status 0, GPIO_KEY1, // GPIO03 Serial TXD and Button GPIO_HJL_CF, // GPIO04 BL0937 or HJL-01 CF power @@ -1690,7 +1698,7 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { 0, // GPIO10 (SD_DATA3 Flash QIO or ESP8285) // GPIO11 (SD_CMD Flash) GPIO_NRG_SEL_INV, // GPIO12 BL0937 or HJL-01 Sel output (0 = Voltage) - GPIO_LED2_INV, // GPIO13 LED2 (red) inv - Power status + GPIO_LED1_INV, // GPIO13 LED2 (red) inv - Power status GPIO_REL1, // GPIO14 Relay (0 = Off, 1 = On) 0, 0, 0 }, @@ -1728,8 +1736,8 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { 0, // GPIO10 (SD_DATA3 Flash QIO or ESP8285) // GPIO11 (SD_CMD Flash) GPIO_NRG_SEL_INV, // GPIO12 HLW8012 CF Sel output (0 = Voltage) - GPIO_LED2_INV, // GPIO13 Red Led (0 = On, 1 = Off) - Power status - GPIO_LED1_INV, // GPIO14 Blue Led (0 = On, 1 = Off) - Link status + GPIO_LED1_INV, // GPIO13 Red Led (0 = On, 1 = Off) - Power status + GPIO_LEDLNK_INV, // GPIO14 Blue Led (0 = On, 1 = Off) - Link status GPIO_REL1, // GPIO15 Relay (0 = Off, 1 = On) 0, 0 }, @@ -1757,9 +1765,9 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { { "Teckin US", // Teckin SP20 US with Energy Monitoring // https://www.amazon.com/Outlet-Compatible-Monitoring-Function-Required/dp/B079Q5W22B // https://www.amazon.com/Outlet-ZOOZEE-Monitoring-Function-Compatible/dp/B07J2LR5KN - GPIO_LED2_INV, // GPIO00 Red Led (1 = On, 0 = Off) - Power status + GPIO_LED1_INV, // GPIO00 Red Led (1 = On, 0 = Off) - Power status 0, - GPIO_LED1_INV, // GPIO02 Blue Led (1 = On, 0 = Off) - Link status + GPIO_LEDLNK_INV, // GPIO02 Blue Led (1 = On, 0 = Off) - Link status 0, GPIO_REL1, // GPIO04 Relay (0 = Off, 1 = On) GPIO_HJL_CF, // GPIO05 BL0937 or HJL-01 CF power @@ -1809,8 +1817,8 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { 0, // GPIO09 (SD_DATA2 Flash QIO or ESP8285) 0, // GPIO10 (SD_DATA3 Flash QIO or ESP8285) // GPIO11 (SD_CMD Flash) - GPIO_LED1_INV, // GPIO12 Green LED - Link status - GPIO_LED2, // GPIO13 Red LED - Power status + GPIO_LEDLNK_INV, // GPIO12 Green LED - Link status + GPIO_LED1, // GPIO13 Red LED - Power status 0, 0, 0, 0 }, { "YTF IR Bridge", // https://www.aliexpress.com/item/Tuya-universal-Smart-IR-Hub-remote-control-Voice-Control-AC-TV-Work-With-Alexa-Google-Home/32951202513.html @@ -1854,7 +1862,7 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { }, { "KA10", // SMANERGY KA10 (ESP8285 - BL0937 Energy Monitoring) - https://www.amazon.es/dp/B07MBTCH2Y 0, // GPIO00 - GPIO_LED1_INV, // GPIO01 Blue LED - Link status + GPIO_LEDLNK_INV, // GPIO01 Blue LED - Link status 0, // GPIO02 GPIO_KEY1, // GPIO03 Button GPIO_HJL_CF, // GPIO04 BL0937 CF power @@ -1866,7 +1874,7 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { 0, // GPIO10 (SD_DATA3 Flash QIO or ESP8285) // GPIO11 (SD_CMD Flash) GPIO_NRG_SEL_INV, // GPIO12 BL0937 Sel output (1 = Voltage) - GPIO_LED2, // GPIO13 Red LED - Power status + GPIO_LED1, // GPIO13 Red LED - Power status GPIO_REL1, // GPIO14 Relay 1 0, 0, 0 }, @@ -1923,7 +1931,7 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { }, { "WAGA CHCZ02MB", // WAGA life CHCZ02MB (HJL-01 Energy Monitoring) // https://www.ebay.com/itm/332595697006 - GPIO_LED2_INV, // GPIO00 Red LED + GPIO_LED1_INV, // GPIO00 Red LED 0, // GPIO01 Serial RXD 0, // GPIO02 GPIO_NRG_SEL_INV, // GPIO03 HJL-01 Sel output (1 = Voltage) @@ -1938,7 +1946,7 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { GPIO_REL1, // GPIO12 Relay GPIO_KEY1, // GPIO13 Button GPIO_NRG_CF1, // GPIO14 HJL-01 CF1 voltage / current - GPIO_LED1_INV, // GPIO15 Blue LED - Link status + GPIO_LEDLNK_INV, // GPIO15 Blue LED - Link status 0, 0 }, { "SYF05", // Sunyesmart SYF05 (a.k.a. Fcmila) = TYWE3S + SM16726 @@ -2070,7 +2078,7 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { GPIO_PWM3, // GPIO12 RGB LED Blue GPIO_PWM4, // GPIO13 RGBW LED White GPIO_PWM1, // GPIO14 RGB LED Red - GPIO_LED4_INV, // GPIO15 RF receiver control + GPIO_ARIRFSEL, // GPIO15 RF receiver control 0, 0 } diff --git a/sonoff/sonoff_version.h b/sonoff/sonoff_version.h index ce4413124..e06fab8d0 100644 --- a/sonoff/sonoff_version.h +++ b/sonoff/sonoff_version.h @@ -20,6 +20,6 @@ #ifndef _SONOFF_VERSION_H_ #define _SONOFF_VERSION_H_ -const uint32_t VERSION = 0x0605000B; +const uint32_t VERSION = 0x0605000C; #endif // _SONOFF_VERSION_H_ diff --git a/sonoff/support.ino b/sonoff/support.ino index 317d25065..da79be0e0 100644 --- a/sonoff/support.ino +++ b/sonoff/support.ino @@ -674,6 +674,42 @@ double FastPrecisePow(double a, double b) return r * u.d; } +float FastPrecisePowf(const float x, const float y) +{ +// return (float)(pow((double)x, (double)y)); + return (float)FastPrecisePow(x, y); +} + +double TaylorLog(double x) +{ + // https://stackoverflow.com/questions/46879166/finding-the-natural-logarithm-of-a-number-using-taylor-series-in-c + + if (x <= 0.0) { return NAN; } + double z = (x + 1) / (x - 1); // We start from power -1, to make sure we get the right power in each iteration; + double step = ((x - 1) * (x - 1)) / ((x + 1) * (x + 1)); // Store step to not have to calculate it each time + double totalValue = 0; + double powe = 1; + double y; + for (int count = 0; count < 10; count++) { // Experimental number of 10 iterations + z *= step; + y = (1 / powe) * z; + totalValue = totalValue + y; + powe = powe + 2; + } + totalValue *= 2; +/* + char logxs[33]; + dtostrfd(x, 8, logxs); + double log1 = log(x); + char log1s[33]; + dtostrfd(log1, 8, log1s); + char log2s[33]; + dtostrfd(totalValue, 8, log2s); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("input %s, log %s, taylor %s"), logxs, log1s, log2s); +*/ + return totalValue; +} + uint32_t SqrtInt(uint32_t num) { if (num <= 1) { diff --git a/sonoff/support_features.ino b/sonoff/support_features.ino index c97f77c9d..9ae3df3b2 100644 --- a/sonoff/support_features.ino +++ b/sonoff/support_features.ino @@ -49,8 +49,8 @@ void GetFeatures(void) #ifdef WEBSERVER_ADVERTISE feature_drv1 |= 0x00000100; // xdrv_02_webserver.ino #endif -#ifdef USE_EMULATION - feature_drv1 |= 0x00000200; // xplg_wemohue.ino +#ifdef USE_EMULATION_HUE + feature_drv1 |= 0x00000200; // xdrv_20_hue.ino #endif #if (MQTT_LIBRARY_TYPE == MQTT_PUBSUBCLIENT) feature_drv1 |= 0x00000400; // xdrv_01_mqtt.ino @@ -180,9 +180,13 @@ void GetFeatures(void) #ifdef USE_SM16716 feature_drv2 |= 0x00040000; // xdrv_04_light.ino #endif +#ifdef USE_SCRIPT + feature_drv2 |= 0x00080000; // xdrv_10_scripter.ino +#endif +#ifdef USE_EMULATION_WEMO + feature_drv2 |= 0x00100000; // xdrv_21_wemo.ino +#endif -// feature_drv2 |= 0x00080000; -// feature_drv2 |= 0x00100000; // feature_drv2 |= 0x00200000; // feature_drv2 |= 0x00400000; diff --git a/sonoff/support_udp.ino b/sonoff/support_udp.ino new file mode 100644 index 000000000..eccc4681d --- /dev/null +++ b/sonoff/support_udp.ino @@ -0,0 +1,136 @@ +/* + support_udp.ino - Udp support for Sonoff-Tasmota + + Copyright (C) 2019 Heiko Krupp and Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_EMULATION + +#define UDP_BUFFER_SIZE 200 // Max UDP buffer size needed for M-SEARCH message +#define UDP_MSEARCH_SEND_DELAY 1500 // Delay in ms before M-Search response is send + +#include +Ticker TickerMSearch; + +IPAddress udp_remote_ip; // M-Search remote IP address +uint16_t udp_remote_port; // M-Search remote port + +bool udp_connected = false; +bool udp_response_mutex = false; // M-Search response mutex to control re-entry + +/*********************************************************************************************\ + * UPNP/SSDP search targets +\*********************************************************************************************/ + +const char URN_BELKIN_DEVICE[] PROGMEM = "urn:belkin:device:**"; +const char UPNP_ROOTDEVICE[] PROGMEM = "upnp:rootdevice"; +const char SSDPSEARCH_ALL[] PROGMEM = "ssdpsearch:all"; +const char SSDP_ALL[] PROGMEM = "ssdp:all"; + +/*********************************************************************************************\ + * UDP support routines +\*********************************************************************************************/ + +bool UdpDisconnect(void) +{ + if (udp_connected) { + PortUdp.flush(); + WiFiUDP::stopAll(); + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_MULTICAST_DISABLED)); + udp_connected = false; + } + return udp_connected; +} + +bool UdpConnect(void) +{ + if (!udp_connected) { + // Simple Service Discovery Protocol (SSDP) + if (PortUdp.beginMulticast(WiFi.localIP(), IPAddress(239,255,255,250), 1900)) { + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_REJOINED)); + udp_response_mutex = false; + udp_connected = true; + } else { + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_JOIN_FAILED)); + udp_connected = false; + } + } + return udp_connected; +} + +void PollUdp(void) +{ + if (udp_connected) { + if (PortUdp.parsePacket()) { + char packet_buffer[UDP_BUFFER_SIZE]; // buffer to hold incoming UDP/SSDP packet + + int len = PortUdp.read(packet_buffer, UDP_BUFFER_SIZE -1); + packet_buffer[len] = 0; + + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet (%d)"), len); +// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("\n%s"), packet_buffer); + + // Simple Service Discovery Protocol (SSDP) + if (devices_present && !udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) { + udp_response_mutex = true; + + udp_remote_ip = PortUdp.remoteIP(); + udp_remote_port = PortUdp.remotePort(); + +// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: M-SEARCH Packet from %s:%d\n%s"), +// udp_remote_ip.toString().c_str(), udp_remote_port, packet_buffer); + + uint32_t response_delay = UDP_MSEARCH_SEND_DELAY + ((millis() &0x7) * 100); // 1500 - 2200 msec + + LowerCase(packet_buffer, packet_buffer); + RemoveSpace(packet_buffer); + +#ifdef USE_EMULATION_WEMO + if (EMUL_WEMO == Settings.flag2.emulation) { + if (strstr_P(packet_buffer, URN_BELKIN_DEVICE) != nullptr) { // type1 echo dot 2g, echo 1g's + TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 1); + return; + } + else if ((strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) || // type2 Echo 2g (echo & echo plus) + (strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) || + (strstr_P(packet_buffer, SSDP_ALL) != nullptr)) { + TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 2); + return; + } + } +#endif // USE_EMULATION_WEMO + +#ifdef USE_EMULATION_HUE + if (EMUL_HUE == Settings.flag2.emulation) { + if ((strstr_P(packet_buffer, PSTR(":device:basic:1")) != nullptr) || + (strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) || + (strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) || + (strstr_P(packet_buffer, SSDP_ALL) != nullptr)) { + TickerMSearch.attach_ms(response_delay, HueRespondToMSearch); + return; + } + } +#endif // USE_EMULATION_HUE + + udp_response_mutex = false; + } + + } + delay(1); + } +} + +#endif // USE_EMULATION diff --git a/sonoff/support_wifi.ino b/sonoff/support_wifi.ino index 44efdac0f..8c406fad5 100644 --- a/sonoff/support_wifi.ino +++ b/sonoff/support_wifi.ino @@ -131,7 +131,7 @@ void WifiConfig(uint8_t type) { if (!wifi_config_type) { if ((WIFI_RETRY == type) || (WIFI_WAIT == type)) { return; } -#if defined(USE_WEBSERVER) && defined(USE_EMULATION) +#ifdef USE_EMULATION UdpDisconnect(); #endif // USE_EMULATION WiFi.disconnect(); // Solve possible Wifi hangs @@ -159,7 +159,7 @@ void WifiConfig(uint8_t type) #ifdef USE_SMARTCONFIG else if (WIFI_SMARTCONFIG == wifi_config_type) { AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_WCFG_1_SMARTCONFIG " " D_ACTIVE_FOR_3_MINUTES)); - WiFi.mode(WIFI_STA); // Disable AP mode + WiFi.mode(WIFI_STA); // Disable AP mode WiFi.beginSmartConfig(); } #endif // USE_SMARTCONFIG @@ -211,7 +211,7 @@ void WifiBegin(uint8_t flag, uint8_t channel) { const char kWifiPhyMode[] = " BGN"; -#if defined(USE_WEBSERVER) && defined(USE_EMULATION) +#ifdef USE_EMULATION UdpDisconnect(); #endif // USE_EMULATION @@ -565,7 +565,7 @@ void WifiCheck(uint8_t param) } else { WifiSetState(0); -#if defined(USE_WEBSERVER) && defined(USE_EMULATION) +#ifdef USE_EMULATION UdpDisconnect(); #endif // USE_EMULATION mdns_begun = 0; diff --git a/sonoff/xdrv_01_webserver.ino b/sonoff/xdrv_01_webserver.ino index f714169ab..dcca253fe 100644 --- a/sonoff/xdrv_01_webserver.ino +++ b/sonoff/xdrv_01_webserver.ino @@ -493,9 +493,6 @@ void StartWebserver(int type, IPAddress ipweb) WebServer->on("/rs", HandleRestoreConfiguration); WebServer->on("/rt", HandleResetConfiguration); WebServer->on("/in", HandleInformation); -#ifdef USE_EMULATION - HueWemoAddHandlers(); -#endif // USE_EMULATION XdrvCall(FUNC_WEB_ADD_HANDLER); XsnsCall(FUNC_WEB_ADD_HANDLER); #endif // Not FIRMWARE_MINIMAL @@ -1532,11 +1529,19 @@ void HandleOtherConfiguration(void) #ifdef USE_EMULATION WSContentSend_P(PSTR("

 " D_EMULATION " 

")); // Keep close to Friendlynames so do not use
for (uint8_t i = 0; i < EMUL_MAX; i++) { - WSContentSend_P(PSTR("%s %s
"), // Different id only used for labels - i, i, - (i == Settings.flag2.emulation) ? " checked" : "", - GetTextIndexed(stemp, sizeof(stemp), i, kEmulationOptions), - (i == EMUL_NONE) ? "" : (i == EMUL_WEMO) ? D_SINGLE_DEVICE : D_MULTI_DEVICE); +#ifndef USE_EMULATION_WEMO + if (i == EMUL_WEMO) { i++; } +#endif +#ifndef USE_EMULATION_HUE + if (i == EMUL_HUE) { i++; } +#endif + if (i < EMUL_MAX) { + WSContentSend_P(PSTR("%s %s
"), // Different id only used for labels + i, i, + (i == Settings.flag2.emulation) ? " checked" : "", + GetTextIndexed(stemp, sizeof(stemp), i, kEmulationOptions), + (i == EMUL_NONE) ? "" : (i == EMUL_WEMO) ? D_SINGLE_DEVICE : D_MULTI_DEVICE); + } } WSContentSend_P(PSTR("

")); #endif // USE_EMULATION @@ -2189,11 +2194,13 @@ void HandleNotFound(void) if (CaptivePortal()) { return; } // If captive portal redirect instead of displaying the error page. #ifdef USE_EMULATION +#ifdef USE_EMULATION_HUE String path = WebServer->uri(); if ((EMUL_HUE == Settings.flag2.emulation) && (path.startsWith("/api"))) { HandleHueApi(&path); } else -#endif // USE_EMULATION +#endif // USE_EMULATION_HUE +#endif // USE_EMULATION { WSContentBegin(404, CT_PLAIN); WSContentSend_P(PSTR(D_FILE_NOT_FOUND "\n\nURI: %s\nMethod: %s\nArguments: %d\n"), WebServer->uri().c_str(), (WebServer->method() == HTTP_GET) ? "GET" : "POST", WebServer->args()); @@ -2430,7 +2437,16 @@ bool WebCommand(void) } #ifdef USE_EMULATION else if (CMND_EMULATION == command_code) { +#if defined(USE_EMULATION_WEMO) && defined(USE_EMULATION_HUE) if ((XdrvMailbox.payload >= EMUL_NONE) && (XdrvMailbox.payload < EMUL_MAX)) { +#else +#ifndef USE_EMULATION_WEMO + if ((EMUL_NONE == XdrvMailbox.payload) || (EMUL_HUE == XdrvMailbox.payload)) { +#endif +#ifndef USE_EMULATION_HUE + if ((EMUL_NONE == XdrvMailbox.payload) || (EMUL_WEMO == XdrvMailbox.payload)) { +#endif +#endif Settings.flag2.emulation = XdrvMailbox.payload; restart_flag = 2; } diff --git a/sonoff/xdrv_02_mqtt.ino b/sonoff/xdrv_02_mqtt.ino index cc510b20c..4d60e157b 100644 --- a/sonoff/xdrv_02_mqtt.ino +++ b/sonoff/xdrv_02_mqtt.ino @@ -423,7 +423,7 @@ void MqttReconnect(void) return; } -#if defined(USE_WEBSERVER) && defined(USE_EMULATION) +#ifdef USE_EMULATION UdpDisconnect(); #endif // USE_EMULATION diff --git a/sonoff/xdrv_04_light.ino b/sonoff/xdrv_04_light.ino index c9bce585e..90b4f872e 100644 --- a/sonoff/xdrv_04_light.ino +++ b/sonoff/xdrv_04_light.ino @@ -716,6 +716,8 @@ void LightStateClass::HsToRgb(uint16_t hue, uint8_t sat, uint8_t *r_r, uint8_t * if (r_b) *r_b = b; } +#define POW FastPrecisePowf + void LightStateClass::RgbToXy(uint8_t i_r, uint8_t i_g, uint8_t i_b, float *r_x, float *r_y) { float x = 0.31271f; // default medium white float y = 0.32902f; @@ -726,9 +728,9 @@ void LightStateClass::RgbToXy(uint8_t i_r, uint8_t i_g, uint8_t i_b, float *r_x, float b = (float)i_b / 255.0f; // https://gist.github.com/popcorn245/30afa0f98eea1c2fd34d // Gamma correction - r = (r > 0.04045f) ? powf((r + 0.055f) / (1.0f + 0.055f), 2.4f) : (r / 12.92f); - g = (g > 0.04045f) ? powf((g + 0.055f) / (1.0f + 0.055f), 2.4f) : (g / 12.92f); - b = (b > 0.04045f) ? powf((b + 0.055f) / (1.0f + 0.055f), 2.4f) : (b / 12.92f); + r = (r > 0.04045f) ? POW((r + 0.055f) / (1.0f + 0.055f), 2.4f) : (r / 12.92f); + g = (g > 0.04045f) ? POW((g + 0.055f) / (1.0f + 0.055f), 2.4f) : (g / 12.92f); + b = (b > 0.04045f) ? POW((b + 0.055f) / (1.0f + 0.055f), 2.4f) : (b / 12.92f); // conversion to X, Y, Z // Y is also the Luminance @@ -762,9 +764,9 @@ void LightStateClass::XyToRgb(float x, float y, uint8_t *rr, uint8_t *rg, uint8_ r = r / max; // normalize to max == 1.0 g = g / max; b = b / max; - r = (r <= 0.0031308f) ? 12.92f * r : 1.055f * powf(r, (1.0f / 2.4f)) - 0.055f; - g = (g <= 0.0031308f) ? 12.92f * g : 1.055f * powf(g, (1.0f / 2.4f)) - 0.055f; - b = (b <= 0.0031308f) ? 12.92f * b : 1.055f * powf(b, (1.0f / 2.4f)) - 0.055f; + r = (r <= 0.0031308f) ? 12.92f * r : 1.055f * POW(r, (1.0f / 2.4f)) - 0.055f; + g = (g <= 0.0031308f) ? 12.92f * g : 1.055f * POW(g, (1.0f / 2.4f)) - 0.055f; + b = (b <= 0.0031308f) ? 12.92f * b : 1.055f * POW(b, (1.0f / 2.4f)) - 0.055f; // // AddLog_P2(LOG_LEVEL_DEBUG_MORE, "XyToRgb XZ (%s %s) rgb (%s %s %s)", // String(X,5).c_str(), String(Z,5).c_str(), @@ -1105,23 +1107,24 @@ void AriluxRfHandler(void) void AriluxRfInit(void) { - if ((pin[GPIO_ARIRFRCV] < 99) && (pin[GPIO_LED4] < 99)) { + if ((pin[GPIO_ARIRFRCV] < 99) && (pin[GPIO_ARIRFSEL] < 99)) { if (Settings.last_module != Settings.module) { Settings.rf_code[1][6] = 0; Settings.rf_code[1][7] = 0; Settings.last_module = Settings.module; } arilux_rf_received_value = 0; - digitalWrite(pin[GPIO_LED4], !bitRead(led_inverted, 3)); // Turn on RF + + digitalWrite(pin[GPIO_ARIRFSEL], 0); // Turn on RF attachInterrupt(pin[GPIO_ARIRFRCV], AriluxRfInterrupt, CHANGE); } } void AriluxRfDisable(void) { - if ((pin[GPIO_ARIRFRCV] < 99) && (pin[GPIO_LED4] < 99)) { + if ((pin[GPIO_ARIRFRCV] < 99) && (pin[GPIO_ARIRFSEL] < 99)) { detachInterrupt(pin[GPIO_ARIRFRCV]); - digitalWrite(pin[GPIO_LED4], bitRead(led_inverted, 3)); // Turn off RF + digitalWrite(pin[GPIO_ARIRFSEL], 1); // Turn off RF } } #endif // USE_ARILUX_RF @@ -1351,8 +1354,9 @@ void LightInit(void) } } if (pin[GPIO_ARIRFRCV] < 99) { - if (pin[GPIO_LED4] < 99) { - digitalWrite(pin[GPIO_LED4], bitRead(led_inverted, 3)); // Turn off RF + if (pin[GPIO_ARIRFSEL] < 99) { + pinMode(pin[GPIO_ARIRFSEL], OUTPUT); + digitalWrite(pin[GPIO_ARIRFSEL], 1); // Turn off RF } } } diff --git a/sonoff/xdrv_09_timers.ino b/sonoff/xdrv_09_timers.ino index dcd18c6f1..3f838ed7a 100644 --- a/sonoff/xdrv_09_timers.ino +++ b/sonoff/xdrv_09_timers.ino @@ -285,7 +285,7 @@ void TimerEverySecond(void) if (time == set_time) { if (xtimer.days & days) { Settings.timer[i].arm = xtimer.repeat; -#ifdef USE_RULES +#if defined(USE_RULES) || defined(USE_SCRIPT) if (3 == xtimer.power) { // Blink becomes Rule disregarding device and allowing use of Backlog commands Response_P(PSTR("{\"Clock\":{\"Timer\":%d}}"), i +1); XdrvRulesProcess(); @@ -359,7 +359,8 @@ bool TimerCommand(void) Settings.timer[index -1].data = Settings.timer[XdrvMailbox.payload -1].data; // Copy timer } } else { -#ifndef USE_RULES +//#ifndef USE_RULES +#if defined(USE_RULES)==0 && defined(USE_SCRIPT)==0 if (devices_present) { #endif StaticJsonBuffer<256> jsonBuffer; @@ -437,7 +438,8 @@ bool TimerCommand(void) index++; } -#ifndef USE_RULES +//#ifndef USE_RULES +#if defined(USE_RULES)==0 && defined(USE_SCRIPT)==0 } else { Response_P(PSTR("{\"" D_CMND_TIMER "%d\":\"" D_JSON_TIMER_NO_DEVICE "\"}"), index); // No outputs defined so nothing to control error = 1; @@ -630,7 +632,7 @@ const char HTTP_TIMER_SCRIPT5[] PROGMEM = "if(%d>0){" // Create Output and Action drop down boxes "eb('oa').innerHTML=\"" D_TIMER_OUTPUT " " D_TIMER_ACTION " \";" "o=qs('#p1');ce('" D_OFF "',o);ce('" D_ON "',o);ce('" D_TOGGLE "',o);" // Create offset direction select options -#ifdef USE_RULES +#if defined(USE_RULES) || defined(USE_SCRIPT) "ce('" D_RULE "',o);" #else "ce('" D_BLINK "',o);" @@ -768,7 +770,7 @@ bool Xdrv09(uint8_t function) #ifdef USE_WEBSERVER #ifdef USE_TIMERS_WEB case FUNC_WEB_ADD_BUTTON: -#ifdef USE_RULES +#if defined(USE_RULES) || defined(USE_SCRIPT) WSContentSend_P(HTTP_BTN_MENU_TIMER); #else if (devices_present) { WSContentSend_P(HTTP_BTN_MENU_TIMER); } diff --git a/sonoff/xdrv_10_rules.ino b/sonoff/xdrv_10_rules.ino index 3c0d05d4a..7b3e4d316 100644 --- a/sonoff/xdrv_10_rules.ino +++ b/sonoff/xdrv_10_rules.ino @@ -18,6 +18,7 @@ */ #ifdef USE_RULES +#ifndef USE_SCRIPT /*********************************************************************************************\ * Rules based heavily on ESP Easy implementation * @@ -586,6 +587,16 @@ void RulesEverySecond(void) } } +void RulesSaveBeforeRestart(void) +{ + if (Settings.rule_enabled) { // Any rule enabled + char json_event[32]; + + strncpy_P(json_event, PSTR("{\"System\":{\"Save\":1}}"), sizeof(json_event)); + RulesProcessEvent(json_event); + } +} + void RulesSetPower(void) { rules_new_power = XdrvMailbox.index; @@ -1278,6 +1289,9 @@ bool Xdrv10(uint8_t function) case FUNC_RULES_PROCESS: result = RulesProcess(); break; + case FUNC_SAVE_BEFORE_RESTART: + RulesSaveBeforeRestart(); + break; #ifdef SUPPORT_MQTT_EVENT case FUNC_MQTT_DATA: result = RulesMqttData(); @@ -1287,4 +1301,5 @@ bool Xdrv10(uint8_t function) return result; } +#endif // Do not USE_SCRIPT #endif // USE_RULES \ No newline at end of file diff --git a/sonoff/xdrv_10_scripter.ino b/sonoff/xdrv_10_scripter.ino new file mode 100644 index 000000000..9edd27a9f --- /dev/null +++ b/sonoff/xdrv_10_scripter.ino @@ -0,0 +1,2238 @@ +/* + xdrv_10_scripter.ino - script support for Sonoff-Tasmota + + Copyright (C) 2019 Gerhard Mutz and Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_SCRIPT +#ifndef USE_RULES +/*********************************************************************************************\ + +for documentation see up to date docs in file SCRIPTER.md + +uses about 14,2 k of flash +more stack could be needed for sendmail => -D CONT_STACKSIZE=4800 = +0.8k stack -0.8k heap + + +to do +optimize code for space + +remarks +goal is fast execution time, minimal use of ram and intuitive syntax +therefore => +case sensitive cmds and vars (lowercase uses time and code) +no math hierarchy (costs ram and execution time, better group with brackets, anyhow better readable for beginners) +(will probably make math hierarchy an ifdefed option) +keywords if then else endif, or, and are better readable for beginners (others may use {}) + +\*********************************************************************************************/ + +#define XDRV_10 10 + +#define SCRIPT_DEBUG 0 + +#define MAXVARS 50 +#define MAXNVARS 45 +#define MAXSVARS 5 +#define MAXFILT 5 +#define SCRIPT_SVARSIZE 20 +#define SCRIPT_MAXSSIZE 48 +#define SCRIPT_EOL '\n' +#define SCRIPT_FLOAT_PRECISION 2 +#define SCRIPT_MAXPERM (MAX_RULE_MEMS*10)-4/sizeof(float) + + +enum {OPER_EQU=1,OPER_PLS,OPER_MIN,OPER_MUL,OPER_DIV,OPER_PLSEQU,OPER_MINEQU,OPER_MULEQU,OPER_DIVEQU,OPER_EQUEQU,OPER_NOTEQU,OPER_GRTEQU,OPER_LOWEQU,OPER_GRT,OPER_LOW,OPER_PERC,OPER_XOR,OPER_AND,OPER_OR,OPER_ANDEQU,OPER_OREQU,OPER_XOREQU,OPER_PERCEQU}; +enum {SCRIPT_LOGLEVEL=1,SCRIPT_TELEPERIOD}; + +typedef union { + uint8_t data; + struct { + uint8_t is_string : 1; // string or number + uint8_t is_permanent : 1; + uint8_t is_timer : 1; + uint8_t is_autoinc : 1; + uint8_t changed : 1; + uint8_t settable : 1; + uint8_t is_filter : 1; + uint8_t constant : 1; + }; +} SCRIPT_TYPE; + +struct T_INDEX { + uint8_t index; + SCRIPT_TYPE bits; +}; + +struct M_FILT { + uint8_t numvals; + uint8_t index; + float maccu; + float rbuff[1]; +}; + +// global memory +struct SCRIPT_MEM { + float *fvars; // number var pointer + float *s_fvars; // shadow var pointer + struct T_INDEX *type; // type and index pointer + struct M_FILT *mfilt; + char *glob_vnp; // var name pointer + uint8_t *vnp_offset; + char *glob_snp; // string vars pointer + char *scriptptr; + uint8_t numvars; + void *script_mem; + uint16_t script_mem_size; + uint8_t script_dprec; + uint8_t var_not_found; + uint8_t glob_error; + uint8_t max_ssize; + uint8_t script_loglevel; +} glob_script_mem; + +uint8_t tasm_cmd_activ=0; + +uint32_t script_lastmillis; + +char *GetNumericResult(char *lp,uint8_t lastop,float *fp,JsonObject *jo); + +void ScriptEverySecond(void) { + + if (bitRead(Settings.rule_enabled, 0)) { + struct T_INDEX *vtp=glob_script_mem.type; + float delta=(millis()-script_lastmillis)/1000; + script_lastmillis=millis(); + for (uint8_t count=0; count0) { + // decrement + *fp-=delta; + if (*fp<0) *fp=0; + } + } + if (vtp[count].bits.is_autoinc) { + // increments timers + float *fp=&glob_script_mem.fvars[vtp[count].index]; + if (*fp>=0) { + *fp+=delta; + } + } + } + Run_Scripter(">S",2,0); + } +} + +void RulesTeleperiod(void) { + if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">T",2, mqtt_data); +} + +#define SCRIPT_SKIP_SPACES while (*lp==' ' || *lp=='\t') lp++; +#define SCRIPT_SKIP_EOL while (*lp==SCRIPT_EOL) lp++; + +// allocates all variable and presets them +int16_t Init_Scripter(char *script) { + // scan lines for >DEF + uint16_t lines=0,nvars=0,svars=0,vars=0; + char *lp=script; + char vnames[MAXVARS*10]; + char *vnames_p=vnames; + char *vnp[MAXVARS]; + char **vnp_p=vnp; + char strings[MAXSVARS*SCRIPT_MAXSSIZE]; + struct M_FILT mfilt[MAXFILT]; + + char *strings_p=strings; + char *snp[MAXSVARS]; + char **snp_p=snp; + uint8_t numperm=0,numflt=0,count; + + glob_script_mem.max_ssize=SCRIPT_SVARSIZE; + glob_script_mem.scriptptr=0; + if (!*script) return -999; + + float fvalues[MAXVARS]; + struct T_INDEX vtypes[MAXVARS]; + char init=0; + while (1) { + // check line + // skip leading spaces + SCRIPT_SKIP_SPACES + // skip empty line + if (*lp=='\n' || *lp=='\r') goto next_line; + // skip comment + if (*lp==';') goto next_line; + if (init) { + // init section + if (*lp=='>') { + init=0; + break; + } + char *op=strchr(lp,'='); + if (op) { + vtypes[vars].bits.data=0; + // found variable definition + if (*lp=='p' && *(lp+1)==':') { + lp+=2; + if (numpermMAXFILT) { + return -6; + } + } else { + vtypes[vars].bits.is_filter=0; + } + *vnp_p++=vnames_p; + while (lpMAXNVARS) { + return -1; + } + } else { + // string vars + op++; + *snp_p++=strings_p; + while (*op!='\"') { + if (*op==SCRIPT_EOL) break; + *strings_p++=*op++; + } + *strings_p++=0; + vtypes[vars].bits.is_string=1; + vtypes[vars].index=svars; + svars++; + if (svars>MAXSVARS) { + return -2; + } + } + vars++; + if (vars>MAXVARS) { + return -3; + } + } + } else { + if (!strncmp(lp,">D",2)) { + lp+=2; + SCRIPT_SKIP_SPACES + if (isdigit(*lp)) { + uint8_t ssize=atoi(lp)+1; + if (ssize<10 || ssize>SCRIPT_MAXSSIZE) ssize=SCRIPT_MAXSSIZE; + glob_script_mem.max_ssize=ssize; + } + init=1; + } + } + // next line + next_line: + lp = strchr(lp, SCRIPT_EOL); + if (!lp) break; + lp++; + } + + uint16_t fsize=0; + for (count=0; countnumvals=mfilt[count].numvals; + mp+=sizeof(struct M_FILT)+((mfilt[count].numvals&0x7f)-1)*sizeof(float); + } + + glob_script_mem.numvars=vars; + glob_script_mem.script_dprec=SCRIPT_FLOAT_PRECISION; + glob_script_mem.script_loglevel=LOG_LEVEL_INFO; + + +#if SCRIPT_DEBUG>2 + struct T_INDEX *dvtp=glob_script_mem.type; + for (uint8_t count=0; count0 + ClaimSerial(); + SetSerialBaudrate(9600); +#endif + + // store start of actual program here + glob_script_mem.scriptptr=lp-1; + return 0; + +} + +#define NUM_RES 0xfe +#define STR_RES 0xfd +#define VAR_NV 0xff + +#define NTYPE 0 +#define STYPE 0x80 + + +//Settings.seriallog_level +//Settings.weblog_level + +float Get_MFilter(uint8_t index) { + uint8_t *mp=(uint8_t*)glob_script_mem.mfilt; + for (uint8_t count=0; countnumvals&0x80) { + // moving average + return mflp->maccu/(mflp->numvals&0x7f); + } else { + // median, sort array + float tbuff[mflp->numvals],tmp; + uint8_t flag; + memmove(tbuff,mflp->rbuff,sizeof(tbuff)); + for (uint8_t ocnt=0; ocntnumvals; ocnt++) { + flag=0; + for (uint8_t count=0; countnumvals-1; count++) { + if (tbuff[count]>tbuff[count+1]) { + tmp=tbuff[count]; + tbuff[count]=tbuff[count+1]; + tbuff[count+1]=tmp; + flag=1; + } + } + if (!flag) break; + } + return mflp->rbuff[mflp->numvals/2]; + } + } + mp+=sizeof(struct M_FILT)+((mflp->numvals&0x7f)-1)*sizeof(float); + } + return 0; +} + +void Set_MFilter(uint8_t index, float invar) { + uint8_t *mp=(uint8_t*)glob_script_mem.mfilt; + for (uint8_t count=0; countnumvals&0x80) { + // moving average + mflp->maccu-=mflp->rbuff[mflp->index]; + mflp->maccu+=invar; + mflp->rbuff[mflp->index]=invar; + mflp->index++; + if (mflp->index>=(mflp->numvals&0x7f)) mflp->index=0; + } else { + // median + mflp->rbuff[mflp->index]=invar; + mflp->index++; + if (mflp->index>=mflp->numvals) mflp->index=0; + } + break; + } + mp+=sizeof(struct M_FILT)+((mflp->numvals&0x7f)-1)*sizeof(float); + } +} + +#define MEDIAN_SIZE 5 +#define MEDIAN_FILTER_NUM 2 + +struct MEDIAN_FILTER { +float buffer[MEDIAN_SIZE]; +int8_t index; +} script_mf[MEDIAN_FILTER_NUM]; + +float DoMedian5(uint8_t index, float in) { + + if (index>=MEDIAN_FILTER_NUM) index=0; + + struct MEDIAN_FILTER* mf=&script_mf[index]; + + float tbuff[MEDIAN_SIZE],tmp; + uint8_t flag; + mf->buffer[mf->index]=in; + mf->index++; + if (mf->index>=MEDIAN_SIZE) mf->index=0; + // sort list and take median + memmove(tbuff,mf->buffer,sizeof(tbuff)); + for (uint8_t ocnt=0; ocnttbuff[count+1]) { + tmp=tbuff[count]; + tbuff[count]=tbuff[count+1]; + tbuff[count+1]=tmp; + flag=1; + } + } + if (!flag) break; + } + return tbuff[MEDIAN_SIZE/2]; +} + + +// vtype => ff=nothing found, fe=constant number,fd = constant string else bit 7 => 80 = string, 0 = number +// no flash strings here for performance reasons!!! +char *isvar(char *lp, uint8_t *vtype,struct T_INDEX *tind,float *fp,char *sp,JsonObject *jo) { + uint16_t count,len=0; + uint8_t nres=0; + char vname[32]; + float fvar=0; + tind->index=0; + tind->bits.data=0; + + if (isdigit(*lp) || (*lp=='-' && isdigit(*(lp+1))) || *lp=='.') { + // isnumber + if (fp) *fp=CharToDouble(lp); + if (*lp=='-') lp++; + while (isdigit(*lp) || *lp=='.') { + if (*lp==0 || *lp==SCRIPT_EOL) break; + lp++; + } + tind->bits.constant=1; + tind->bits.is_string=0; + *vtype=NUM_RES; + return lp; + } + if (*lp=='"') { + lp++; + while (*lp!='"') { + if (*lp==0 || *lp==SCRIPT_EOL) break; + if (sp) *sp++=*lp; + lp++; + } + if (sp) *sp=0; + *vtype=STR_RES; + tind->bits.constant=1; + tind->bits.is_string=1; + return lp+1; + } + + if (*lp=='-') { + // inverted var + nres=1; + lp++; + } + + const char *term="\n\r ])=+-/*%>index=VAR_NV; + glob_script_mem.var_not_found=1; + return lp; + } + + struct T_INDEX *vtp=glob_script_mem.type; + for (count=0; countindex=count; // overwrite with global var index + if (vtp[count].bits.is_string==0) { + *vtype=NTYPE|index; + if (vtp[count].bits.is_filter) { + fvar=Get_MFilter(index); + } else { + fvar=glob_script_mem.fvars[index]; + } + if (nres) fvar=-fvar; + if (fp) *fp=fvar; + } else { + *vtype=STYPE|index; + if (sp) strlcpy(sp,glob_script_mem.glob_snp+(index*glob_script_mem.max_ssize),SCRIPT_MAXSSIZE); + } + return lp+len; + } + } + } + + if (jo) { + // look for json input + const char* str_value; + uint8_t aindex; + String vn; + char *ja=strchr(vname,'['); + if (ja) { + // json array + *ja=0; + ja++; + // fetch array index + float fvar; + GetNumericResult(ja,OPER_EQU,&fvar,0); + aindex=fvar; + if (aindex<1 || aindex>6) aindex=1; + aindex--; + } + if (jo->success()) { + char *subtype=strchr(vname,'#'); + if (subtype) { + *subtype=0; + subtype++; + } + vn=vname; + str_value = (*jo)[vn]; + if ((*jo)[vn].success()) { + if (subtype) { + JsonObject &jobj1=(*jo)[vn]; + if (jobj1.success()) { + vn=subtype; + jo=&jobj1; + str_value = (*jo)[vn]; + if ((*jo)[vn].success()) { + goto skip; + } + } else { + goto chknext; + } + } + skip: + if (ja) { + // json array + str_value = (*jo)[vn][aindex]; + } + if (str_value && *str_value) { + if ((*jo).is(vn)) { + if (!strncmp(str_value,"ON",2)) { + if (fp) *fp=1; + } else if (!strncmp(str_value,"OFF",3)) { + if (fp) *fp=0; + } else { + *vtype=STR_RES; + tind->bits.constant=1; + tind->bits.is_string=1; + if (sp) strlcpy(sp,str_value,SCRIPT_MAXSSIZE); + return lp+len; + } + } else { + if (fp) *fp=CharToDouble((char*)str_value); + *vtype=NUM_RES; + tind->bits.constant=1; + tind->bits.is_string=0; + return lp+len; + } + } + } + } + } + +chknext: + switch (vname[0]) { + case 'b': + if (!strncmp(vname,"boot",4)) { + if (rules_flag.system_boot) { + rules_flag.system_boot=0; + fvar=1; + } + goto exit; + } + break; + case 'c': + if (!strncmp(vname,"chg[",4)) { + // var changed + struct T_INDEX ind; + uint8_t vtype; + isvar(vname+4,&vtype,&ind,0,0,0); + if (!ind.bits.constant) { + uint8_t index=glob_script_mem.type[ind.index].index; + if (glob_script_mem.fvars[index]!=glob_script_mem.s_fvars[index]) { + // var has changed + glob_script_mem.s_fvars[index]=glob_script_mem.fvars[index]; + fvar=1; + len++; + goto exit; + } else { + fvar=0; + len++; + goto exit; + } + } + } + break; + case 'd': + if (!strncmp(vname,"day",3)) { + fvar=RtcTime.day_of_month; + goto exit; + } + break; + case 'g': + if (!strncmp(vname,"gtmp",4)) { + fvar=global_temperature; + goto exit; + } + if (!strncmp(vname,"ghum",4)) { + fvar=global_humidity; + goto exit; + } + if (!strncmp(vname,"gprs",4)) { + fvar=global_pressure; + goto exit; + } + if (!strncmp(vname,"gtopic",6)) { + if (sp) strlcpy(sp,Settings.mqtt_grptopic,glob_script_mem.max_ssize); + goto strexit; + } + break; + case 'h': + if (!strncmp(vname,"hours",5)) { + fvar=RtcTime.hour; + goto exit; + } + if (!strncmp(vname,"heap",4)) { + fvar=ESP.getFreeHeap(); + goto exit; + } + if (!strncmp(vname,"hn(",3)) { + lp=GetNumericResult(lp+3,OPER_EQU,&fvar,0); + if (fvar<0 || fvar>255) fvar=0; + lp++; + len=0; + if (sp) { + sprintf(sp,"%02x",(uint8_t)fvar); + } + goto strexit; + } + break; + case 'i': + if (!strncmp(vname,"int(",4)) { + lp=GetNumericResult(lp+4,OPER_EQU,&fvar,0); + fvar=floor(fvar); + lp++; + len=0; + goto exit; + } + break; + case 'l': + if (!strncmp(vname,"loglvl",6)) { + fvar=glob_script_mem.script_loglevel; + tind->index=SCRIPT_LOGLEVEL; + exit_settable: + if (fp) *fp=fvar; + *vtype=NTYPE; + tind->bits.settable=1; + tind->bits.is_string=0; + return lp+len; + } + break; + case 'm': + if (!strncmp(vname,"med(",4)) { + float fvar1; + lp=GetNumericResult(lp+4,OPER_EQU,&fvar1,0); + SCRIPT_SKIP_SPACES + // arg2 + float fvar2; + lp=GetNumericResult(lp,OPER_EQU,&fvar2,0); + fvar=DoMedian5(fvar1,fvar2); + lp++; + len=0; + goto exit; + } + if (!strncmp(vname,"micros",6)) { + fvar=micros(); + goto exit; + } + if (!strncmp(vname,"millis",6)) { + fvar=millis(); + goto exit; + } + if (!strncmp(vname,"mins",4)) { + fvar=RtcTime.minute; + goto exit; + } + if (!strncmp(vname,"month",5)) { + fvar=RtcTime.month; + goto exit; + } + if (!strncmp(vname,"mqttc",5)) { + if (rules_flag.mqtt_connected) { + rules_flag.mqtt_connected=0; + fvar=1; + } + goto exit; + } + if (!strncmp(vname,"mqttd",5)) { + if (rules_flag.mqtt_disconnected) { + rules_flag.mqtt_disconnected=0; + fvar=1; + } + goto exit; + } + if (!strncmp(vname,"mqtts",5)) { + fvar=!global_state.mqtt_down; + goto exit; + } + break; + case 'p': + if (!strncmp(vname,"pin[",4)) { + // raw pin level + GetNumericResult(vname+4,OPER_EQU,&fvar,0); + fvar=digitalRead((uint8_t)fvar); + // skip ] bracket + len++; + goto exit; + } + if (!strncmp(vname,"pn[",3)) { + GetNumericResult(vname+3,OPER_EQU,&fvar,0); + fvar=pin[(uint8_t)fvar]; + // skip ] bracket + len++; + goto exit; + } + if (!strncmp(vname,"pd[",3)) { + GetNumericResult(vname+3,OPER_EQU,&fvar,0); + uint8_t gpiopin=fvar; + for (uint8_t i=0;iindex=SCRIPT_TELEPERIOD; + goto exit_settable; + } + if (!strncmp(vname,"tinit",5)) { + if (rules_flag.time_init) { + rules_flag.time_init=0; + fvar=1; + } + goto exit; + } + if (!strncmp(vname,"tset",4)) { + if (rules_flag.time_set) { + rules_flag.time_set=0; + fvar=1; + } + goto exit; + } + if (!strncmp(vname,"tstamp",6)) { + if (sp) strlcpy(sp,GetDateAndTime(DT_LOCAL).c_str(),glob_script_mem.max_ssize); + goto strexit; + } + if (!strncmp(vname,"topic",5)) { + if (sp) strlcpy(sp,Settings.mqtt_topic,glob_script_mem.max_ssize); + goto strexit; + } + break; + case 'u': + if (!strncmp(vname,"uptime",6)) { + fvar=MinutesUptime(); + goto exit; + } + if (!strncmp(vname,"upsecs",6)) { + fvar=uptime; + goto exit; + } + if (!strncmp(vname,"upd[",4)) { + // var was updated + struct T_INDEX ind; + uint8_t vtype; + isvar(vname+4,&vtype,&ind,0,0,0); + if (!ind.bits.constant) { + if (!ind.bits.changed) { + fvar=0; + len++; + goto exit; + } else { + glob_script_mem.type[ind.index].bits.changed=0; + fvar=1; + len++; + goto exit; + } + } + goto notfound; + } + break; + case 'w': + if (!strncmp(vname,"wday",4)) { + fvar=RtcTime.day_of_week; + goto exit; + } + if (!strncmp(vname,"wific",5)) { + if (rules_flag.wifi_connected) { + rules_flag.wifi_connected=0; + fvar=1; + } + goto exit; + } + if (!strncmp(vname,"wifid",5)) { + if (rules_flag.wifi_disconnected) { + rules_flag.wifi_disconnected=0; + fvar=1; + } + goto exit; + } + if (!strncmp(vname,"wifis",5)) { + fvar=!global_state.wifi_down; + goto exit; + } + break; + case 'y': + if (!strncmp(vname,"year",4)) { + fvar=RtcTime.year; + goto exit; + } + break; + default: + break; + } + // nothing valid found +notfound: + if (fp) *fp=0; + *vtype=VAR_NV; + tind->index=VAR_NV; + glob_script_mem.var_not_found=1; + return lp; + // return constant numbers +exit: + if (fp) *fp=fvar; + *vtype=NUM_RES; + tind->bits.constant=1; + tind->bits.is_string=0; + return lp+len; + // return constant strings +strexit: + *vtype=STYPE; + tind->bits.constant=1; + tind->bits.is_string=1; + return lp+len; +} + + + +char *getop(char *lp, uint8_t *operand) { + switch (*lp) { + case '=': + if (*(lp+1)=='=') { + *operand=OPER_EQUEQU; + return lp+2; + } else { + *operand=OPER_EQU; + return lp+1; + } + break; + case '+': + if (*(lp+1)=='=') { + *operand=OPER_PLSEQU; + return lp+2; + } else { + *operand=OPER_PLS; + return lp+1; + } + break; + case '-': + if (*(lp+1)=='=') { + *operand=OPER_MINEQU; + return lp+2; + } else { + *operand=OPER_MIN; + return lp+1; + } + break; + case '*': + if (*(lp+1)=='=') { + *operand=OPER_MULEQU; + return lp+2; + } else { + *operand=OPER_MUL; + return lp+1; + } + break; + case '/': + if (*(lp+1)=='=') { + *operand=OPER_DIVEQU; + return lp+2; + } else { + *operand=OPER_DIV; + return lp+1; + } + break; + case '!': + if (*(lp+1)=='=') { + *operand=OPER_NOTEQU; + return lp+2; + } + break; + case '>': + if (*(lp+1)=='=') { + *operand=OPER_GRTEQU; + return lp+2; + } else { + *operand=OPER_GRT; + return lp+1; + + } + break; + case '<': + if (*(lp+1)=='=') { + *operand=OPER_LOWEQU; + return lp+2; + } else { + *operand=OPER_LOW; + return lp+1; + } + break; + case '%': + if (*(lp+1)=='=') { + *operand=OPER_PERCEQU; + return lp+2; + } else { + *operand=OPER_PERC; + return lp+1; + } + break; + case '^': + if (*(lp+1)=='=') { + *operand=OPER_XOREQU; + return lp+2; + } else { + *operand=OPER_XOR; + return lp+1; + } + break; + case '&': + if (*(lp+1)=='=') { + *operand=OPER_ANDEQU; + return lp+2; + } else { + *operand=OPER_AND; + return lp+1; + } + break; + case '|': + if (*(lp+1)=='=') { + *operand=OPER_OREQU; + return lp+2; + } else { + *operand=OPER_OR; + return lp+1; + } + break; + } + *operand=0; + return lp; +} + + +#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) +// All version before core 2.4.2 +// https://github.com/esp8266/Arduino/issues/2557 +extern "C" { +#include + extern cont_t g_cont; +} +uint16_t GetStack(void) { + register uint32_t *sp asm("a1"); + return (4 * (sp - g_pcont.stack)); +} + +#else +extern "C" { +#include + extern cont_t* g_pcont; +} +uint16_t GetStack(void) { + register uint32_t *sp asm("a1"); + return (4 * (sp - g_pcont->stack)); +} +#endif + +char *GetStringResult(char *lp,uint8_t lastop,char *cp,JsonObject *jo) { + uint8_t operand=0; + uint8_t vtype; + char *slp; + struct T_INDEX ind; + char str[SCRIPT_MAXSSIZE],str1[SCRIPT_MAXSSIZE]; + while (1) { + lp=isvar(lp,&vtype,&ind,0,str1,jo); + switch (lastop) { + case OPER_EQU: + strlcpy(str,str1,sizeof(str)); + break; + case OPER_PLS: + strlcat(str,str1,sizeof(str)); + break; + } + slp=lp; + lp=getop(lp,&operand); + switch (operand) { + case OPER_EQUEQU: + case OPER_NOTEQU: + case OPER_LOW: + case OPER_LOWEQU: + case OPER_GRT: + case OPER_GRTEQU: + lp=slp; + strcpy(cp,str); + return lp; + break; + default: + break; + } + lastop=operand; + if (!operand) { + strcpy(cp,str); + return lp; + } + } +} + +char *GetNumericResult(char *lp,uint8_t lastop,float *fp,JsonObject *jo) { +uint8_t operand=0; +float fvar1,fvar; +char *slp; +uint8_t vtype; +struct T_INDEX ind; + while (1) { + // get 1. value + if (*lp=='(') { + lp++; + lp=GetNumericResult(lp,OPER_EQU,&fvar1,jo); + lp++; + //if (*lp==')') lp++; + } else { + lp=isvar(lp,&vtype,&ind,&fvar1,0,jo); + if (vtype!=NUM_RES && vtype&STYPE) { + // string type + glob_script_mem.glob_error=1; + } + } + switch (lastop) { + case OPER_EQU: + fvar=fvar1; + break; + case OPER_PLS: + fvar+=fvar1; + break; + case OPER_MIN: + fvar-=fvar1; + break; + case OPER_MUL: + fvar*=fvar1; + break; + case OPER_DIV: + fvar/=fvar1; + break; + case OPER_PERC: + fvar=fmod(fvar,fvar1); + break; + case OPER_XOR: + fvar=(uint32_t)fvar^(uint32_t)fvar1; + break; + case OPER_AND: + fvar=(uint32_t)fvar&(uint32_t)fvar1; + break; + case OPER_OR: + fvar=(uint32_t)fvar|(uint32_t)fvar1; + break; + default: + break; + } + slp=lp; + lp=getop(lp,&operand); + switch (operand) { + case OPER_EQUEQU: + case OPER_NOTEQU: + case OPER_LOW: + case OPER_LOWEQU: + case OPER_GRT: + case OPER_GRTEQU: + lp=slp; + *fp=fvar; + return lp; + break; + default: + break; + } + lastop=operand; + if (!operand) { + *fp=fvar; + return lp; + } + } +} + + +// replace vars in cmd %var% +void Replace_Cmd_Vars(char *srcbuf,char *dstbuf,uint16_t dstsize) { + char *cp; + uint16_t count; + uint8_t vtype; + float fvar; + cp=srcbuf; + struct T_INDEX ind; + char string[SCRIPT_MAXSSIZE]; + for (count=0;count=sizeof(str)) len=len>=sizeof(str); + strlcpy(str,cp,len); + toSLog(str); +} + +void toLogEOL(const char *s1,const char *str) { + if (!str) return; + uint8_t index=0; + char *cp=log_data; + strcpy(cp,s1); + cp+=strlen(s1); + while (*str) { + if (*str==SCRIPT_EOL) break; + *cp++=*str++; + } + *cp=0; + AddLog(LOG_LEVEL_INFO); +} + + +void toSLog(const char *str) { + if (!str) return; +#if SCRIPT_DEBUG>0 + while (*str) { + Serial.write(*str); + str++; + } +#endif +} + +#define IF_NEST 8 +// execute section of scripter +int16_t Run_Scripter(const char *type, uint8_t tlen, char *js) { + uint8_t vtype=0,sindex,xflg,floop=0,globvindex; + struct T_INDEX ind; + uint8_t operand,lastop,numeric=1,if_state[IF_NEST],if_result[IF_NEST],and_or,ifstck=0,s_ifstck=0; + if_state[ifstck]=0; + if_result[ifstck]=0; + char cmpstr[SCRIPT_MAXSSIZE]; + + if (tasm_cmd_activ) return 0; + + + float *dfvar,*cv_count,cv_max,cv_inc; + char *cv_ptr; + float fvar=0,fvar1,sysvar,swvar; + uint8_t section=0,sysv_type=0,swflg=0; + + if (!glob_script_mem.scriptptr) { + return -99; + } + + DynamicJsonBuffer jsonBuffer; // on heap + JsonObject &jobj=jsonBuffer.parseObject(js); + JsonObject *jo; + if (js) jo=&jobj; + else jo=0; + + char *lp=glob_script_mem.scriptptr; + + while (1) { + // check line + // skip leading spaces + startline: + SCRIPT_SKIP_SPACES + // skip empty line + SCRIPT_SKIP_EOL + // skip comment + if (*lp==';') goto next_line; + if (!*lp) break; + + if (section) { + // we are in section + if (*lp=='>') { + section=0; + break; + } + if (*lp=='#') { + section=0; + break; + } + glob_script_mem.var_not_found=0; + +#if SCRIPT_DEBUG>0 + char tbuff[128]; + sprintf(tbuff,"stack=%d,state=%d,cmpres=%d line: ",ifstck,if_state[ifstck],if_result[ifstck]); + toLogEOL(tbuff,lp); +#endif + + if (!strncmp(lp,"if",2)) { + lp+=2; + if (if_state[ifstck]>0) { + if (ifstck=2) { + lp+=5; + if_state[ifstck]=0; + if (ifstck>0) { + ifstck--; + } + s_ifstck=ifstck; // >>>>> + goto next_line; + } else if (!strncmp(lp,"or",2) && if_state[ifstck]==1) { + lp+=2; + and_or=1; + } else if (!strncmp(lp,"and",3) && if_state[ifstck]==1) { + lp+=3; + and_or=2; + } + + if (*lp=='{' && if_state[ifstck]==1) { + lp+=1; // then + if_state[ifstck]=2; + } else if (*lp=='{' && if_state[ifstck]==3) { + lp+=1; // after else + //if_state[ifstck]=3; + } else if (*lp=='}' && if_state[ifstck]>=2) { + lp++; // must check for else + char *slp=lp; + uint8_t iselse=0; + for (uint8_t count=0; count<8;count++) { + if (*lp=='}') { + // must be endif + break; + } + if (!strncmp(lp,"else",4)) { + // is before else, no endif + if_state[ifstck]=3; + lp+=4; + iselse=1; + break; + } + lp++; + } + if (!iselse) { + lp=slp; + // endif + if_state[ifstck]=0; + if (ifstck>0) { + ifstck--; + } + s_ifstck=ifstck; // >>>>> + } + } + + if (!strncmp(lp,"for",3)) { + // start for next loop, fetch 3 params + // simple implementation, zero loop count not supported + lp+=3; + SCRIPT_SKIP_SPACES + lp=isvar(lp,&vtype,&ind,0,0,0); + if ((vtype!=VAR_NV) && (vtype&STYPE)==0) { + // numeric var + uint8_t index=glob_script_mem.type[ind.index].index; + cv_count=&glob_script_mem.fvars[index]; + SCRIPT_SKIP_SPACES + lp=GetNumericResult(lp,OPER_EQU,cv_count,0); + SCRIPT_SKIP_SPACES + lp=GetNumericResult(lp,OPER_EQU,&cv_max,0); + SCRIPT_SKIP_SPACES + lp=GetNumericResult(lp,OPER_EQU,&cv_inc,0); + SCRIPT_SKIP_EOL + cv_ptr=lp; + floop=1; + } else { + // error + toLogEOL("for error",lp); + } + } else if (!strncmp(lp,"next",4) && floop>0) { + // for next loop + *cv_count+=cv_inc; + if (*cv_count<=cv_max) { + lp=cv_ptr; + } else { + lp+=4; + floop=0; + } + } + + if (!strncmp(lp,"switch",6)) { + lp+=6; + SCRIPT_SKIP_SPACES + lp=GetNumericResult(lp,OPER_EQU,&swvar,0); + swflg=1; + } else if (!strncmp(lp,"case",4) && swflg>0) { + lp+=4; + SCRIPT_SKIP_SPACES + float cvar; + lp=GetNumericResult(lp,OPER_EQU,&cvar,0); + if (swvar!=cvar) { + swflg=2; + } else { + swflg=1; + } + } else if (!strncmp(lp,"ends",4) && swflg>0) { + lp+=4; + swflg=0; + } + + if (swflg==2) goto next_line; + + + SCRIPT_SKIP_SPACES + //SCRIPT_SKIP_EOL + if (*lp==SCRIPT_EOL) { + goto next_line; + } + //toLogN(lp,16); + if (if_state[s_ifstck]==3 && if_result[s_ifstck]) goto next_line; + if (if_state[s_ifstck]==2 && !if_result[s_ifstck]) goto next_line; + + s_ifstck=ifstck; + + if (!strncmp(lp,"break",5)) { + if (floop) { + // should break loop + floop=0; + } else { + section=0; + } + break; + } else if (!strncmp(lp,"dprec",5)) { + lp+=5; + // number precision + glob_script_mem.script_dprec=atoi(lp); + goto next_line; + } else if (!strncmp(lp,"delay(",6)) { + lp+=5; + // delay + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + delay(fvar); + goto next_line; + } else if (!strncmp(lp,"spinm(",6)) { + lp+=6; + // set pin mode + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + int8_t pinnr=fvar; + SCRIPT_SKIP_SPACES + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + int8_t mode=fvar; + pinMode(pinnr,mode&1); + goto next_line; + } else if (!strncmp(lp,"spin(",5)) { + lp+=5; + // set pin mode + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + int8_t pinnr=fvar; + SCRIPT_SKIP_SPACES + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + int8_t mode=fvar; + digitalWrite(pinnr,mode&1); + goto next_line; + } else if (!strncmp(lp,"svars(",5)) { + lp+=5; + // save vars + Scripter_save_pvars(); + goto next_line; + } + + else if (!strncmp(lp,"=>",2)) { + // execute cmd + lp+=2; + SCRIPT_SKIP_SPACES + #define SCRIPT_CMDMEM 512 + char *cmdmem=(char*)malloc(SCRIPT_CMDMEM); + if (cmdmem) { + char *cmd=cmdmem; + short count; + for (count=0; countfvar1); + break; + case OPER_GRTEQU: + res=(*dfvar>=fvar1); + break; + default: + // error + break; + } + + if (!and_or) { + if_result[s_ifstck]=res; + } else if (and_or==1) { + if_result[s_ifstck]|=res; + } else { + if_result[s_ifstck]&=res; + } +#if SCRIPT_DEBUG>0 + char tbuff[128]; + sprintf(tbuff,"p1=%d,p2=%d,cmpres=%d line: ",(int32_t)*dfvar,(int32_t)fvar1,if_result[s_ifstck]); + toLogEOL(tbuff,lp); +#endif + + } else { + // compare string + char str[SCRIPT_MAXSSIZE]; + lp=GetStringResult(lp,OPER_EQU,str,0); + if (lastop==OPER_EQUEQU || lastop==OPER_NOTEQU) { + uint8_t res=0; + res=strcmp(cmpstr,str); + if (lastop==OPER_EQUEQU) res=!res; + if (!and_or) { + if_result[s_ifstck]=res; + } else if (and_or==1) { + if_result[s_ifstck]|=res; + } else { + if_result[s_ifstck]&=res; + } + } + } + SCRIPT_SKIP_SPACES + if (*lp=='{' && if_state[ifstck]==1) { + lp+=1; // then + if_state[ifstck]=2; + } + goto next_line; + } else { + if (numeric) { + char *slp=lp; + glob_script_mem.glob_error=0; + lp=GetNumericResult(lp,OPER_EQU,&fvar,jo); + if (glob_script_mem.glob_error==1) { + // mismatch was string, not number + // get the string and convert to number + lp=isvar(slp,&vtype,&ind,0,cmpstr,jo); + fvar=CharToDouble(cmpstr); + } + switch (lastop) { + case OPER_EQU: + if (glob_script_mem.var_not_found) { + if (!js) toLog("var not found\n"); + goto next_line; + } + *dfvar=fvar; + break; + case OPER_PLSEQU: + *dfvar+=fvar; + break; + case OPER_MINEQU: + *dfvar-=fvar; + break; + case OPER_MULEQU: + *dfvar*=fvar; + break; + case OPER_DIVEQU: + *dfvar/=fvar; + break; + case OPER_PERCEQU: + *dfvar=fmod(*dfvar,fvar); + break; + case OPER_ANDEQU: + *dfvar=(uint32_t)*dfvar&(uint32_t)fvar; + break; + case OPER_OREQU: + *dfvar=(uint32_t)*dfvar|(uint32_t)fvar; + break; + case OPER_XOREQU: + *dfvar=(uint32_t)*dfvar^(uint32_t)fvar; + break; + default: + // error + break; + } + // var was changed + glob_script_mem.type[globvindex].bits.changed=1; + if (glob_script_mem.type[globvindex].bits.is_filter) { + Set_MFilter(glob_script_mem.type[globvindex].index,*dfvar); + } + + if (sysv_type) { + switch (sysv_type) { + case SCRIPT_LOGLEVEL: + glob_script_mem.script_loglevel=*dfvar; + break; + case SCRIPT_TELEPERIOD: + if (*dfvar<10) *dfvar=10; + if (*dfvar>300) *dfvar=300; + Settings.tele_period=*dfvar; + break; + } + sysv_type=0; + } + + } else { + // string result + char str[SCRIPT_MAXSSIZE]; + char *slp=lp; + lp=GetStringResult(lp,OPER_EQU,str,jo); + if (!js && glob_script_mem.var_not_found) { + // mismatch + lp=GetNumericResult(slp,OPER_EQU,&fvar,0); + dtostrfd(fvar,6,str); + glob_script_mem.var_not_found=0; + } + + if (!glob_script_mem.var_not_found) { + // var was changed + glob_script_mem.type[globvindex].bits.changed=1; + if (lastop==OPER_EQU) { + strlcpy(glob_script_mem.glob_snp+(sindex*glob_script_mem.max_ssize),str,glob_script_mem.max_ssize); + } else if (lastop==OPER_PLSEQU) { + strlcat(glob_script_mem.glob_snp+(sindex*glob_script_mem.max_ssize),str,glob_script_mem.max_ssize); + } + } + } + SCRIPT_SKIP_SPACES + if (*lp=='{' && if_state[ifstck]==3) { + lp+=1; // else + //if_state[ifstck]=3; + } + goto next_line; + } + } else { + // decode line + if (*lp=='>' && tlen==1) { + // called from cmdline + lp++; + section=1; + goto startline; + } + if (!strncmp(lp,type,tlen)) { + // found section + section=1; + // check for subroutine + if (*type=='#') { + // check for parameter + type+=tlen; + if (*type=='(') { + float fparam; + numeric=1; + glob_script_mem.glob_error=0; + GetNumericResult((char*)type,OPER_EQU,&fparam,0); + if (glob_script_mem.glob_error==1) { + // was string, not number + numeric=0; + // get the string + GetStringResult((char*)type+1,OPER_EQU,cmpstr,0); + } + lp+=tlen; + if (*lp=='(') { + // fetch destination + lp++; + lp=isvar(lp,&vtype,&ind,0,0,0); + if (vtype!=VAR_NV) { + // found variable as result + uint8_t index=glob_script_mem.type[ind.index].index; + if ((vtype&STYPE)==0) { + // numeric result + dfvar=&glob_script_mem.fvars[index]; + if (numeric) { + *dfvar=fparam; + } else { + // mismatch + *dfvar=CharToDouble(cmpstr); + } + } else { + // string result + sindex=index; + if (!numeric) { + strlcpy(glob_script_mem.glob_snp+(sindex*glob_script_mem.max_ssize),cmpstr,glob_script_mem.max_ssize); + } else { + // mismatch + dtostrfd(fparam,6,glob_script_mem.glob_snp+(sindex*glob_script_mem.max_ssize)); + } + } + } + } + } + } + } + } + // next line + next_line: + if (*lp==SCRIPT_EOL) { + lp++; + } else { + lp = strchr(lp, SCRIPT_EOL); + if (!lp) break; + lp++; + } + } +} + +uint8_t script_xsns_index = 0; + + +void ScripterEvery100ms(void) +{ + if (Settings.rule_enabled && (uptime > 4)) { + mqtt_data[0] = '\0'; + uint16_t script_tele_period_save = tele_period; + tele_period = 2; + XsnsNextCall(FUNC_JSON_APPEND, script_xsns_index); + tele_period = script_tele_period_save; + if (strlen(mqtt_data)) { + mqtt_data[0] = '{'; + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data); + Run_Scripter(">T",2, mqtt_data); + } + } +} + +//mems[MAX_RULE_MEMS] is 50 bytes in 6.5 +// can hold 11 floats or floats + strings +// should report overflow later +void Scripter_save_pvars(void) { + uint32_t lptr=(uint32_t)Settings.mems[0]; + int16_t mlen=0; + lptr&=0xfffffffc; + float *fp=(float*)lptr; + fp++; + mlen+=sizeof(float); + struct T_INDEX *vtp=glob_script_mem.type; + for (uint8_t count=0; countMAX_RULE_MEMS*10) { + vtp[count].bits.is_permanent=0; + return; + } + *fp++=glob_script_mem.fvars[index]; + } + } + char *cp=(char*)fp; + for (uint8_t count=0; countMAX_RULE_MEMS*10) { + vtp[count].bits.is_permanent=0; + return; + } + strcpy(cp,sp); + cp+=slen+1; + } + } +} + +// works only with webserver +#ifdef USE_WEBSERVER + +#define WEB_HANDLE_SCRIPT "s10" +#define D_CONFIGURE_SCRIPT "Edit script" +#define D_RULEVARS "edit script" + +const char S_CONFIGURE_SCRIPT[] PROGMEM = D_CONFIGURE_SCRIPT; + +const char HTTP_BTN_MENU_RULES[] PROGMEM = + "

"; + + +const char HTTP_FORM_SCRIPT[] PROGMEM = + "
 " D_RULEVARS " " + "
"; + +const char HTTP_FORM_SCRIPT1[] PROGMEM = + "
script enable
" + "
"; + +void HandleScriptConfiguration(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_SCRIPT); + + if (WebServer->hasArg("save")) { + ScriptSaveSettings(); + HandleConfiguration(); + return; + } + + WSContentStart_P(S_CONFIGURE_SCRIPT); + WSContentSendStyle(); + WSContentSend_P(HTTP_FORM_SCRIPT); + WSContentSend_P(HTTP_FORM_SCRIPT1,1,1,bitRead(Settings.rule_enabled,0) ? " checked" : "",1,1,MAX_RULE_SIZE*3); + + // script is to larg for WSContentSend_P + if (Settings.rules[0][0]) { + _WSContentSend(Settings.rules[0]); + } + WSContentSend_P(HTTP_FORM_SCRIPT1b); + WSContentSend_P(HTTP_FORM_END); + WSContentSpaceButton(BUTTON_CONFIGURATION); + WSContentStop(); + } + + +void strrepl_inplace(char *str, const char *a, const char *b) { + for (char *cursor=str; (cursor=strstr(cursor,a)) != NULL;) { + memmove(cursor+strlen(b),cursor+strlen(a),strlen(cursor)-strlen(a)+1); + for (int i=0; b[i]!='\0'; i++) { + cursor[i] = b[i]; + } + cursor += strlen(b); + } +} + +#define MAX_SCRIPT_SIZE MAX_RULE_SIZE*3 + +void ScriptSaveSettings(void) { + + if (WebServer->hasArg("c1")) { + bitWrite(Settings.rule_enabled,0,1); + } else { + bitWrite(Settings.rule_enabled,0,0); + } + + String str = WebServer->arg("t1"); + + if (*str.c_str()) { +#if 1 + strrepl_inplace((char*)str.c_str(),"\r\n","\n"); + strrepl_inplace((char*)str.c_str(),"\r","\n"); +#else + str.replace("\r\n","\n"); + str.replace("\r","\n"); +#endif + strlcpy(Settings.rules[0],str.c_str(), MAX_RULE_SIZE*3); + } + + if (glob_script_mem.script_mem) { + Scripter_save_pvars(); + free(glob_script_mem.script_mem); + glob_script_mem.script_mem=0; + glob_script_mem.script_mem_size=0; + } + + if (bitRead(Settings.rule_enabled, 0)) { + int16_t res=Init_Scripter(Settings.rules[0]); + if (res) { + snprintf_P(log_data, sizeof(log_data), PSTR("script init error: %d"),res); + AddLog(LOG_LEVEL_INFO); + return; + } + Run_Scripter(">B",2,0); + } +} + +#endif + +void execute_script(char *script) { +char *svd_sp=glob_script_mem.scriptptr; + strcat(script,"\n#"); + glob_script_mem.scriptptr=script; + Run_Scripter(">",1,0); + glob_script_mem.scriptptr=svd_sp; + Scripter_save_pvars(); +} + +enum ScriptCommands { CMND_SCRIPT }; +const char kScriptCommands[] PROGMEM = "Script"; + +bool ScriptCommand(void) { + char command[CMDSZ]; + bool serviced = true; + uint8_t index = XdrvMailbox.index; + + int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic, kScriptCommands); + if (-1 == command_code) { + serviced = false; // Unknown command + } + else if ((CMND_SCRIPT == command_code) && (index > 0)) { + + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 2)) { + switch (XdrvMailbox.payload) { + case 0: // Off + case 1: // On + bitWrite(Settings.rule_enabled, index -1, XdrvMailbox.payload); + } + } else { + if ('>' == XdrvMailbox.data[0]) { + // execute script + for (uint8_t count=0; countB",2,0); + break; + case FUNC_EVERY_100_MSECOND: + ScripterEvery100ms(); + break; + case FUNC_EVERY_SECOND: + ScriptEverySecond(); + break; + case FUNC_COMMAND: + result = ScriptCommand(); + break; + case FUNC_SET_POWER: + case FUNC_RULES_PROCESS: + if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">E",2,mqtt_data); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_ADD_BUTTON: + WSContentSend_P(HTTP_BTN_MENU_RULES); + break; + case FUNC_WEB_ADD_HANDLER: + WebServer->on("/" WEB_HANDLE_SCRIPT, HandleScriptConfiguration); + break; +#endif // USE_WEBSERVER + case FUNC_SAVE_BEFORE_RESTART: + if (bitRead(Settings.rule_enabled, 0)) { + Run_Scripter(">R",2,0); + Scripter_save_pvars(); + } + break; + } + return result; +} + +#endif // Do not USE_RULES +#endif // USE_SCRIPT diff --git a/sonoff/xplg_wemohue.ino b/sonoff/xdrv_20_hue.ino similarity index 64% rename from sonoff/xplg_wemohue.ino rename to sonoff/xdrv_20_hue.ino index b4ca72124..d54becd66 100644 --- a/sonoff/xplg_wemohue.ino +++ b/sonoff/xdrv_20_hue.ino @@ -1,5 +1,5 @@ /* - xplg_wemohue.ino - wemo and hue support for Sonoff-Tasmota + xdrv_20_hue.ino - Philips Hue support for Sonoff-Tasmota Copyright (C) 2019 Heiko Krupp and Theo Arends @@ -17,93 +17,10 @@ along with this program. If not, see . */ -#if defined(USE_WEBSERVER) && defined(USE_EMULATION) -/*********************************************************************************************\ - * Belkin WeMo and Philips Hue bridge emulation -\*********************************************************************************************/ - -#define UDP_BUFFER_SIZE 200 // Max UDP buffer size needed for M-SEARCH message -#define UDP_MSEARCH_SEND_DELAY 1500 // Delay in ms before M-Search response is send - -#include -Ticker TickerMSearch; - -IPAddress udp_remote_ip; // M-Search remote IP address -uint16_t udp_remote_port; // M-Search remote port - -bool udp_connected = false; -bool udp_response_mutex = false; // M-Search response mutex to control re-entry - -/*********************************************************************************************\ - * UPNP search targets -\*********************************************************************************************/ - -const char URN_BELKIN_DEVICE[] PROGMEM = "urn:belkin:device:**"; -const char UPNP_ROOTDEVICE[] PROGMEM = "upnp:rootdevice"; -const char SSDPSEARCH_ALL[] PROGMEM = "ssdpsearch:all"; -const char SSDP_ALL[] PROGMEM = "ssdp:all"; - -/*********************************************************************************************\ - * WeMo UPNP support routines -\*********************************************************************************************/ - -const char WEMO_MSEARCH[] PROGMEM = - "HTTP/1.1 200 OK\r\n" - "CACHE-CONTROL: max-age=86400\r\n" - "DATE: Fri, 15 Apr 2016 04:56:29 GMT\r\n" - "EXT:\r\n" - "LOCATION: http://%s:80/setup.xml\r\n" - "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" - "01-NLS: b9200ebb-736d-4b93-bf03-835149d13983\r\n" - "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n" - "ST: %s\r\n" // type1 = urn:Belkin:device:**, type2 = upnp:rootdevice - "USN: uuid:%s::%s\r\n" // type1 = urn:Belkin:device:**, type2 = upnp:rootdevice - "X-User-Agent: redsonic\r\n" - "\r\n"; - -String WemoSerialnumber(void) -{ - char serial[16]; - - snprintf_P(serial, sizeof(serial), PSTR("201612K%08X"), ESP.getChipId()); - return String(serial); -} - -String WemoUuid(void) -{ - char uuid[27]; - - snprintf_P(uuid, sizeof(uuid), PSTR("Socket-1_0-%s"), WemoSerialnumber().c_str()); - return String(uuid); -} - -void WemoRespondToMSearch(int echo_type) -{ - char message[TOPSZ]; - - TickerMSearch.detach(); - if (PortUdp.beginPacket(udp_remote_ip, udp_remote_port)) { - char type[24]; - if (1 == echo_type) { // type1 echo 1g & dot 2g - strcpy_P(type, URN_BELKIN_DEVICE); - } else { // type2 echo 2g (echo, plus, show) - strcpy_P(type, UPNP_ROOTDEVICE); - } - char response[400]; - snprintf_P(response, sizeof(response), WEMO_MSEARCH, WiFi.localIP().toString().c_str(), type, WemoUuid().c_str(), type); - PortUdp.write(response); - PortUdp.endPacket(); - snprintf_P(message, sizeof(message), PSTR(D_RESPONSE_SENT)); - } else { - snprintf_P(message, sizeof(message), PSTR(D_FAILED_TO_SEND_RESPONSE)); - } - AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_WEMO " " D_JSON_TYPE " %d, %s " D_TO " %s:%d"), - echo_type, message, udp_remote_ip.toString().c_str(), udp_remote_port); - - udp_response_mutex = false; -} - +#if defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined(USE_EMULATION_HUE) /*********************************************************************************************\ + * Philips Hue bridge emulation + * * Hue Bridge UPNP support routines * Need to send 3 response packets with varying ST and USN * @@ -111,6 +28,8 @@ void WemoRespondToMSearch(int echo_type) * Philips Lighting is 00:17:88:00:00:00 \*********************************************************************************************/ +#define XDRV_20 20 + const char HUE_RESPONSE[] PROGMEM = "HTTP/1.1 200 OK\r\n" "HOST: 239.255.255.250:1900\r\n" @@ -187,256 +106,6 @@ void HueRespondToMSearch(void) udp_response_mutex = false; } -/*********************************************************************************************\ - * Belkin WeMo and Philips Hue bridge UDP multicast support -\*********************************************************************************************/ - -bool UdpDisconnect(void) -{ - if (udp_connected) { - PortUdp.flush(); - WiFiUDP::stopAll(); - AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_MULTICAST_DISABLED)); - udp_connected = false; - } - return udp_connected; -} - -bool UdpConnect(void) -{ - if (!udp_connected) { - // Simple Service Discovery Protocol (SSDP) - if (PortUdp.beginMulticast(WiFi.localIP(), IPAddress(239,255,255,250), 1900)) { - AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_REJOINED)); - udp_response_mutex = false; - udp_connected = true; - } else { - AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_JOIN_FAILED)); - udp_connected = false; - } - } - return udp_connected; -} - -void PollUdp(void) -{ - if (udp_connected) { - if (PortUdp.parsePacket()) { - char packet_buffer[UDP_BUFFER_SIZE]; // buffer to hold incoming UDP/SSDP packet - - int len = PortUdp.read(packet_buffer, UDP_BUFFER_SIZE -1); - packet_buffer[len] = 0; - - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet (%d)"), len); -// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("\n%s"), packet_buffer); - - if (devices_present && !udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) { - udp_response_mutex = true; - - udp_remote_ip = PortUdp.remoteIP(); - udp_remote_port = PortUdp.remotePort(); - -// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: M-SEARCH Packet from %s:%d\n%s"), -// udp_remote_ip.toString().c_str(), udp_remote_port, packet_buffer); - - uint32_t response_delay = UDP_MSEARCH_SEND_DELAY + ((millis() &0x7) * 100); // 1500 - 2200 msec - - LowerCase(packet_buffer, packet_buffer); - RemoveSpace(packet_buffer); - if (EMUL_WEMO == Settings.flag2.emulation) { - if (strstr_P(packet_buffer, URN_BELKIN_DEVICE) != nullptr) { // type1 echo dot 2g, echo 1g's - TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 1); - return; - } - else if ((strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) || // type2 Echo 2g (echo & echo plus) - (strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) || - (strstr_P(packet_buffer, SSDP_ALL) != nullptr)) { - TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 2); - return; - } - } else { - if ((strstr_P(packet_buffer, PSTR(":device:basic:1")) != nullptr) || - (strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) || - (strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) || - (strstr_P(packet_buffer, SSDP_ALL) != nullptr)) { - TickerMSearch.attach_ms(response_delay, HueRespondToMSearch); - return; - } - } - udp_response_mutex = false; - } - } - delay(1); - } -} - -/*********************************************************************************************\ - * Wemo web server additions -\*********************************************************************************************/ - -const char WEMO_EVENTSERVICE_XML[] PROGMEM = - "" - "" - "" - "SetBinaryState" - "" - "" - "" - "BinaryState" - "BinaryState" - "in" - "" - "" - "" - "" - "GetBinaryState" - "" - "" - "" - "BinaryState" - "BinaryState" - "out" - "" - "" - "" - "" - "" - "" - "BinaryState" - "bool" - "0" - "" - "" - "level" - "string" - "0" - "" - "" - "\r\n\r\n"; - -const char WEMO_METASERVICE_XML[] PROGMEM = - "" - "" - "1" - "0" - "" - "" - "" - "GetMetaInfo" - "" - "" - "GetMetaInfo" - "MetaInfo" - "in" - "" - "" - "" - "" - "" - "MetaInfo" - "string" - "0" - "" - "" - "\r\n\r\n"; - -const char WEMO_RESPONSE_STATE_SOAP[] PROGMEM = - "" - "" - "" - "%d" - "" - "" - "\r\n"; - -const char WEMO_SETUP_XML[] PROGMEM = - "" - "" - "" - "urn:Belkin:device:controllee:1" - "{x1" - "Belkin International Inc." - "Socket" - "3.1415" - "uuid:{x2" - "{x3" - "0" - "" - "" - "urn:Belkin:service:basicevent:1" - "urn:Belkin:serviceId:basicevent1" - "/upnp/control/basicevent1" - "/upnp/event/basicevent1" - "/eventservice.xml" - "" - "" - "urn:Belkin:service:metainfo:1" - "urn:Belkin:serviceId:metainfo1" - "/upnp/control/metainfo1" - "/upnp/event/metainfo1" - "/metainfoservice.xml" - "" - "" - "" - "\r\n"; - -/********************************************************************************************/ - -void HandleUpnpEvent(void) -{ - AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_WEMO_BASIC_EVENT)); - - char event[500]; - strlcpy(event, WebServer->arg(0).c_str(), sizeof(event)); - -// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("\n%s"), event); - - //differentiate get and set state - char state = 'G'; - if (strstr_P(event, PSTR("SetBinaryState")) != nullptr) { - state = 'S'; - uint8_t power = POWER_TOGGLE; - if (strstr_P(event, PSTR("State>10on("/upnp/control/basicevent1", HTTP_POST, HandleUpnpEvent); - WebServer->on("/eventservice.xml", HandleUpnpService); - WebServer->on("/metainfoservice.xml", HandleUpnpMetaService); - WebServer->on("/setup.xml", HandleUpnpSetupWemo); - } - if (EMUL_HUE == Settings.flag2.emulation) { - WebServer->on("/description.xml", HandleUpnpSetupHue); + bool result = false; + + if (devices_present && (EMUL_HUE == Settings.flag2.emulation)) { + switch (function) { + case FUNC_WEB_ADD_HANDLER: + WebServer->on("/description.xml", HandleUpnpSetupHue); + break; } } + return result; } -#endif // USE_WEBSERVER && USE_EMULATION +#endif // USE_WEBSERVER && USE_EMULATION && USE_EMULATION_HUE diff --git a/sonoff/xdrv_21_wemo.ino b/sonoff/xdrv_21_wemo.ino new file mode 100644 index 000000000..6d5a41715 --- /dev/null +++ b/sonoff/xdrv_21_wemo.ino @@ -0,0 +1,271 @@ +/* + xdrv_21_wemo.ino - wemo support for Sonoff-Tasmota + + Copyright (C) 2019 Heiko Krupp and Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined (USE_EMULATION_WEMO) +/*********************************************************************************************\ + * Belkin WeMo emulation +\*********************************************************************************************/ + +#define XDRV_21 21 + +const char WEMO_MSEARCH[] PROGMEM = + "HTTP/1.1 200 OK\r\n" + "CACHE-CONTROL: max-age=86400\r\n" + "DATE: Fri, 15 Apr 2016 04:56:29 GMT\r\n" + "EXT:\r\n" + "LOCATION: http://%s:80/setup.xml\r\n" + "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" + "01-NLS: b9200ebb-736d-4b93-bf03-835149d13983\r\n" + "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n" + "ST: %s\r\n" // type1 = urn:Belkin:device:**, type2 = upnp:rootdevice + "USN: uuid:%s::%s\r\n" // type1 = urn:Belkin:device:**, type2 = upnp:rootdevice + "X-User-Agent: redsonic\r\n" + "\r\n"; + +String WemoSerialnumber(void) +{ + char serial[16]; + + snprintf_P(serial, sizeof(serial), PSTR("201612K%08X"), ESP.getChipId()); + return String(serial); +} + +String WemoUuid(void) +{ + char uuid[27]; + + snprintf_P(uuid, sizeof(uuid), PSTR("Socket-1_0-%s"), WemoSerialnumber().c_str()); + return String(uuid); +} + +void WemoRespondToMSearch(int echo_type) +{ + char message[TOPSZ]; + + TickerMSearch.detach(); + if (PortUdp.beginPacket(udp_remote_ip, udp_remote_port)) { + char type[24]; + if (1 == echo_type) { // type1 echo 1g & dot 2g + strcpy_P(type, URN_BELKIN_DEVICE); + } else { // type2 echo 2g (echo, plus, show) + strcpy_P(type, UPNP_ROOTDEVICE); + } + char response[400]; + snprintf_P(response, sizeof(response), WEMO_MSEARCH, WiFi.localIP().toString().c_str(), type, WemoUuid().c_str(), type); + PortUdp.write(response); + PortUdp.endPacket(); + snprintf_P(message, sizeof(message), PSTR(D_RESPONSE_SENT)); + } else { + snprintf_P(message, sizeof(message), PSTR(D_FAILED_TO_SEND_RESPONSE)); + } + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_WEMO " " D_JSON_TYPE " %d, %s " D_TO " %s:%d"), + echo_type, message, udp_remote_ip.toString().c_str(), udp_remote_port); + + udp_response_mutex = false; +} + +/*********************************************************************************************\ + * Wemo web server additions +\*********************************************************************************************/ + +const char WEMO_EVENTSERVICE_XML[] PROGMEM = + "" + "" + "" + "SetBinaryState" + "" + "" + "" + "BinaryState" + "BinaryState" + "in" + "" + "" + "" + "" + "GetBinaryState" + "" + "" + "" + "BinaryState" + "BinaryState" + "out" + "" + "" + "" + "" + "" + "" + "BinaryState" + "bool" + "0" + "" + "" + "level" + "string" + "0" + "" + "" + "\r\n\r\n"; + +const char WEMO_METASERVICE_XML[] PROGMEM = + "" + "" + "1" + "0" + "" + "" + "" + "GetMetaInfo" + "" + "" + "GetMetaInfo" + "MetaInfo" + "in" + "" + "" + "" + "" + "" + "MetaInfo" + "string" + "0" + "" + "" + "\r\n\r\n"; + +const char WEMO_RESPONSE_STATE_SOAP[] PROGMEM = + "" + "" + "" + "%d" + "" + "" + "\r\n"; + +const char WEMO_SETUP_XML[] PROGMEM = + "" + "" + "" + "urn:Belkin:device:controllee:1" + "{x1" + "Belkin International Inc." + "Socket" + "3.1415" + "uuid:{x2" + "{x3" + "0" + "" + "" + "urn:Belkin:service:basicevent:1" + "urn:Belkin:serviceId:basicevent1" + "/upnp/control/basicevent1" + "/upnp/event/basicevent1" + "/eventservice.xml" + "" + "" + "urn:Belkin:service:metainfo:1" + "urn:Belkin:serviceId:metainfo1" + "/upnp/control/metainfo1" + "/upnp/event/metainfo1" + "/metainfoservice.xml" + "" + "" + "" + "\r\n"; + +/********************************************************************************************/ + +void HandleUpnpEvent(void) +{ + AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_WEMO_BASIC_EVENT)); + + char event[500]; + strlcpy(event, WebServer->arg(0).c_str(), sizeof(event)); + +// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("\n%s"), event); + + //differentiate get and set state + char state = 'G'; + if (strstr_P(event, PSTR("SetBinaryState")) != nullptr) { + state = 'S'; + uint8_t power = POWER_TOGGLE; + if (strstr_P(event, PSTR("State>10on("/upnp/control/basicevent1", HTTP_POST, HandleUpnpEvent); + WebServer->on("/eventservice.xml", HandleUpnpService); + WebServer->on("/metainfoservice.xml", HandleUpnpMetaService); + WebServer->on("/setup.xml", HandleUpnpSetupWemo); + break; + } + } + return result; +} + +#endif // USE_WEBSERVER && USE_EMULATION && USE_EMULATION_WEMO diff --git a/sonoff/xnrg_01_hlw8012.ino b/sonoff/xnrg_01_hlw8012.ino index 67eab0fee..d2da0e3a7 100644 --- a/sonoff/xnrg_01_hlw8012.ino +++ b/sonoff/xnrg_01_hlw8012.ino @@ -233,26 +233,27 @@ void HlwSnsInit(void) void HlwDrvInit(void) { if (!energy_flg) { - hlw_model_type = 0; // HLW8012 + hlw_model_type = 0; // HLW8012 if (pin[GPIO_HJL_CF] < 99) { pin[GPIO_HLW_CF] = pin[GPIO_HJL_CF]; pin[GPIO_HJL_CF] = 99; - hlw_model_type = 1; // HJL-01/BL0937 + hlw_model_type = 1; // HJL-01/BL0937 } - if (pin[GPIO_HLW_CF] < 99) { // HLW8012 or HJL-01 based device + if (pin[GPIO_HLW_CF] < 99) { // HLW8012 or HJL-01 based device Power monitor - hlw_ui_flag = 1; // Voltage on high + hlw_ui_flag = 1; // Voltage on high if (pin[GPIO_NRG_SEL_INV] < 99) { pin[GPIO_NRG_SEL] = pin[GPIO_NRG_SEL_INV]; pin[GPIO_NRG_SEL_INV] = 99; - hlw_ui_flag = 0; // Voltage on low + hlw_ui_flag = 0; // Voltage on low } - if (99 == pin[GPIO_NRG_SEL]) { - energy_current_available = false; - } - if (99 == pin[GPIO_NRG_CF1]) { + if (pin[GPIO_NRG_CF1] < 99) { // Voltage and/or Current monitor + if (99 == pin[GPIO_NRG_SEL]) { // Voltage and/or Current selector + energy_current_available = false; // Assume Voltage + } + } else { energy_current_available = false; energy_voltage_available = false; } diff --git a/sonoff/xnrg_02_cse7766.ino b/sonoff/xnrg_02_cse7766.ino index 078ff3d6e..55d87a163 100644 --- a/sonoff/xnrg_02_cse7766.ino +++ b/sonoff/xnrg_02_cse7766.ino @@ -189,6 +189,7 @@ void CseEverySecond(void) } else { AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: Load overflow")); + cf_pulses_last_time = CSE_PULSES_NOT_INITIALIZED; } EnergyUpdateToday(); } diff --git a/sonoff/xsns_02_analog.ino b/sonoff/xsns_02_analog.ino index e93377165..85560e3d3 100644 --- a/sonoff/xsns_02_analog.ino +++ b/sonoff/xsns_02_analog.ino @@ -100,7 +100,7 @@ void AdcEverySecond(void) int adc = AdcRead(2); // Steinhart-Hart equation for thermistor as temperature sensor double Rt = (adc * ANALOG_NTC_BRIDGE_RESISTANCE) / (1024.0 * ANALOG_V33 - (double)adc); - double T = ANALOG_NTC_B_COEFFICIENT / (ANALOG_NTC_B_COEFFICIENT / ANALOG_T0 + log(Rt / ANALOG_NTC_RESISTANCE)); + double T = ANALOG_NTC_B_COEFFICIENT / (ANALOG_NTC_B_COEFFICIENT / ANALOG_T0 + TaylorLog(Rt / ANALOG_NTC_RESISTANCE)); adc_temp = ConvertTemp(TO_CELSIUS(T)); } } diff --git a/sonoff/xsns_17_senseair.ino b/sonoff/xsns_17_senseair.ino index 1cf69d23d..ebc440ec1 100644 --- a/sonoff/xsns_17_senseair.ino +++ b/sonoff/xsns_17_senseair.ino @@ -149,9 +149,9 @@ void SenseairShow(bool json) GetTextIndexed(senseair_types, sizeof(senseair_types), senseair_type -1, kSenseairTypes); if (json) { - ResponseAppend_P(PSTR("%s,\"%s\":{\"" D_JSON_CO2 "\":%d"), senseair_types, senseair_co2); + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_CO2 "\":%d"), senseair_types, senseair_co2); if (senseair_type != 2) { - ResponseAppend_P(PSTR("%s,\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s"), temperature, humidity); + ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s"), temperature, humidity); } ResponseJsonEnd(); #ifdef USE_DOMOTICZ diff --git a/sonoff/xsns_21_sgp30.ino b/sonoff/xsns_21_sgp30.ino index d8241f1d6..27d1a43f9 100644 --- a/sonoff/xsns_21_sgp30.ino +++ b/sonoff/xsns_21_sgp30.ino @@ -34,55 +34,122 @@ Adafruit_SGP30 sgp; uint8_t sgp30_type = 0; uint8_t sgp30_ready = 0; -uint8_t sgp30_counter = 0; +float sgp30_abshum; /********************************************************************************************/ +void sgp30_Init(void) { + if (sgp.begin()) { + sgp30_type = 1; +// snprintf_P(log_data, sizeof(log_data), PSTR("SGP: Serialnumber 0x%04X-0x%04X-0x%04X"), sgp.serialnumber[0], sgp.serialnumber[1], sgp.serialnumber[2]); +// AddLog(LOG_LEVEL_DEBUG); + snprintf_P(log_data, sizeof(log_data), S_LOG_I2C_FOUND_AT, "SGP30", 0x58); + AddLog(LOG_LEVEL_DEBUG); + } +} + +//#define POW_FUNC pow +#define POW_FUNC FastPrecisePow + +float sgp30_AbsoluteHumidity(float temperature, float humidity,char tempUnit) { + //taken from https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/ + //precision is about 0.1°C in range -30 to 35°C + //August-Roche-Magnus 6.1094 exp(17.625 x T)/(T + 243.04) + //Buck (1981) 6.1121 exp(17.502 x T)/(T + 240.97) + //reference https://www.eas.ualberta.ca/jdwilson/EAS372_13/Vomel_CIRES_satvpformulae.html + float temp = NAN; + const float mw = 18.01534; // molar mass of water g/mol + const float r = 8.31447215; // Universal gas constant J/mol/K + + if (isnan(temperature) || isnan(humidity) ) { + return NAN; + } + + if (tempUnit != 'C') { + temperature = (temperature - 32.0) * (5.0 / 9.0); /*conversion to [°C]*/ + } + + temp = POW_FUNC(2.718281828, (17.67 * temperature) / (temperature + 243.5)); + + + + //return (6.112 * temp * humidity * 2.1674) / (273.15 + temperature); //simplified version + return (6.112 * temp * humidity * mw) / ((273.15 + temperature) * r); //long version +} + +#define SAVE_PERIOD 30 + void Sgp30Update(void) // Perform every second to ensure proper operation of the baseline compensation algorithm { sgp30_ready = 0; - if (!sgp30_type) { - if (sgp.begin()) { - sgp30_type = 1; -// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SGP: Serialnumber 0x%04X-0x%04X-0x%04X"), sgp.serialnumber[0], sgp.serialnumber[1], sgp.serialnumber[2]); - AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, "SGP30", 0x58); + if (!sgp.IAQmeasure() || !sgp30_type) { + // retry to init every 100 seconds + if (21 == (uptime %100)) { + sgp30_Init(); } - } else { - if (!sgp.IAQmeasure()) return; // Measurement failed - sgp30_counter++; - if (30 == sgp30_counter) { - sgp30_counter = 0; + return; // Measurement failed + } + if (global_update) { + // abs hum in mg/m3 + sgp30_abshum=sgp30_AbsoluteHumidity(global_temperature,global_humidity,TempUnit()); + sgp.setHumidity(sgp30_abshum*1000); + } + sgp30_ready = 1; - uint16_t TVOC_base; - uint16_t eCO2_base; + // these should normally be stored permanently and used for fast restart + if (!(uptime%SAVE_PERIOD)) { + // store settings every N seconds + uint16_t TVOC_base; + uint16_t eCO2_base; + + if (!sgp.getIAQBaseline(&eCO2_base, &TVOC_base)) return; // Failed to get baseline readings +// snprintf_P(log_data, sizeof(log_data), PSTR("SGP: Baseline values eCO2 0x%04X, TVOC 0x%04X"), eCO2_base, TVOC_base); +// AddLog(LOG_LEVEL_DEBUG); - if (!sgp.getIAQBaseline(&eCO2_base, &TVOC_base)) return; // Failed to get baseline readings -// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SGP: Baseline values eCO2 0x%04X, TVOC 0x%04X"), eCO2_base, TVOC_base); - } - sgp30_ready = 1; } } + +#ifdef USE_WEBSERVER const char HTTP_SNS_SGP30[] PROGMEM = "{s}SGP30 " D_ECO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}" // {s} = , {m} = , {e} = "{s}SGP30 " D_TVOC "{m}%d " D_UNIT_PARTS_PER_BILLION "{e}"; +const char HTTP_SNS_AHUM[] PROGMEM = "{s}SGP30 " "Abs Humidity" "{m}%s g/m3{e}"; +#endif + +#define D_JSON_AHUM "aHumidity" void Sgp30Show(bool json) { if (sgp30_ready) { + char abs_hum[33]; + + if (global_update) { + // has humidity + temperature + dtostrfd(sgp30_abshum,4,abs_hum); + } + if (json) { - ResponseAppend_P(PSTR(",\"SGP30\":{\"" D_JSON_ECO2 "\":%d,\"" D_JSON_TVOC "\":%d}"), sgp.eCO2, sgp.TVOC); + ResponseAppend_P(PSTR(",\"SGP30\":{\"" D_JSON_ECO2 "\":%d,\"" D_JSON_TVOC "\":%d"), sgp.eCO2, sgp.TVOC); + if (global_update) { + ResponseAppend_P(PSTR(",\"" D_JSON_AHUM "\":%s"),abs_hum); + } + ResponseAppend_P(PSTR("}")); #ifdef USE_DOMOTICZ if (0 == tele_period) DomoticzSensor(DZ_AIRQUALITY, sgp.eCO2); #endif // USE_DOMOTICZ #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_SGP30, sgp.eCO2, sgp.TVOC); + if (global_update) { + WSContentSend_PD(HTTP_SNS_AHUM, abs_hum); + } #endif } } } + /*********************************************************************************************\ * Interface \*********************************************************************************************/ @@ -93,6 +160,9 @@ bool Xsns21(uint8_t function) if (i2c_flg) { switch (function) { + case FUNC_INIT: + sgp30_Init(); + break; case FUNC_EVERY_SECOND: Sgp30Update(); break; @@ -110,4 +180,4 @@ bool Xsns21(uint8_t function) } #endif // USE_SGP30 -#endif // USE_I2C \ No newline at end of file +#endif // USE_I2C diff --git a/sonoff/xsns_27_apds9960.ino b/sonoff/xsns_27_apds9960.ino index 7594a5322..909bb607e 100644 --- a/sonoff/xsns_27_apds9960.ino +++ b/sonoff/xsns_27_apds9960.ino @@ -374,21 +374,11 @@ void calculateColorTemperature(void) n = (xc - 0.3320F) / (0.1858F - yc); /* Calculate the final CCT */ - color_data.cct = (449.0F * powf(n, 3)) + (3525.0F * powf(n, 2)) + (6823.3F * n) + 5520.33F; + color_data.cct = (449.0F * FastPrecisePowf(n, 3)) + (3525.0F * FastPrecisePowf(n, 2)) + (6823.3F * n) + 5520.33F; return; } -/** -* Taken from the Adafruit-Library -* @brief Implements missing powf function -*/ - -float powf(const float x, const float y) -{ - return (float)(pow((double)x, (double)y)); -} - /******************************************************************************* * Getters and setters for register values ******************************************************************************/ diff --git a/sonoff/xsns_34_hx711.ino b/sonoff/xsns_34_hx711.ino index c1fd9c20e..4f1742943 100644 --- a/sonoff/xsns_34_hx711.ino +++ b/sonoff/xsns_34_hx711.ino @@ -62,6 +62,7 @@ enum HxCalibrationSteps { HX_CAL_END, HX_CAL_LIMBO, HX_CAL_FINISH, HX_CAL_FAIL, const char kHxCalibrationStates[] PROGMEM = D_HX_CAL_FAIL "|" D_HX_CAL_DONE "|" D_HX_CAL_REFERENCE "|" D_HX_CAL_REMOVE; long hx_weight = 0; +long hx_last_weight = 0; long hx_sum_weight = 0; long hx_offset = 0; long hx_scale = 1; @@ -116,11 +117,18 @@ long HxRead() /*********************************************************************************************/ -void HxReset(void) +void HxResetPart(void) { hx_tare_flg = true; hx_sum_weight = 0; hx_sample_count = 0; + hx_last_weight = 0; +} + +void HxReset(void) +{ + HxResetPart(); + Settings.energy_frequency_calibration = 0; } void HxCalibrationStateTextJson(uint8_t msg_id) @@ -147,6 +155,7 @@ void HxCalibrationStateTextJson(uint8_t msg_id) * Sensor34 5 - Set max weight * Sensor34 6 - Show item weigth in decigram * Sensor34 6 - Set item weight + * Sensor34 7 - Save current weight to be used as start weight on restart \*********************************************************************************************/ bool HxCommand(void) @@ -199,6 +208,10 @@ bool HxCommand(void) } show_parms = true; break; + case 7: // WeightSave + Settings.energy_frequency_calibration = hx_weight; + Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_34, D_JSON_DONE); + break; default: serviced = false; } @@ -238,8 +251,7 @@ void HxInit(void) if (!Settings.weight_reference) { Settings.weight_reference = HX_REFERENCE; } hx_scale = Settings.weight_calibration; HxRead(); - HxReset(); - + HxResetPart(); hx_type = 1; } } @@ -254,7 +266,16 @@ void HxEvery100mSecond(void) long average = hx_sum_weight / hx_sample_count; // grams long value = average - hx_offset; // grams hx_weight = value / hx_scale; // grams - if (hx_weight < 0) { hx_weight = 0; } + if (hx_weight < 0) { + if (Settings.energy_frequency_calibration) { + long difference = Settings.energy_frequency_calibration + hx_weight; + hx_last_weight = difference; + if (difference < 0) { HxReset(); } // Cancel last weight as there seems to be no more weight on the scale + } + hx_weight = 0; + } else { + hx_last_weight = Settings.energy_frequency_calibration; + } if (hx_tare_flg) { hx_tare_flg = false; @@ -313,6 +334,8 @@ void HxEvery100mSecond(void) if (!hx_calibrate_timer) { hx_calibrate_step = HX_CAL_END; // End of calibration } + } else { + hx_weight += hx_last_weight; // grams } hx_sum_weight = 0; @@ -320,6 +343,12 @@ void HxEvery100mSecond(void) } } +void HxSaveBeforeRestart() +{ + Settings.energy_frequency_calibration = hx_weight; + hx_sample_count = HX_SAMPLES +1; // Stop updating hx_weight +} + #ifdef USE_WEBSERVER const char HTTP_HX711_WEIGHT[] PROGMEM = "{s}HX711 " D_WEIGHT "{m}%s " D_UNIT_KILOGRAM "{e}"; // {s} = , {m} = , {e} = @@ -484,6 +513,9 @@ bool Xsns34(uint8_t function) case FUNC_JSON_APPEND: HxShow(1); break; + case FUNC_SAVE_BEFORE_RESTART: + HxSaveBeforeRestart(); + break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: HxShow(0); diff --git a/sonoff/xsns_44_sps30.ino b/sonoff/xsns_44_sps30.ino new file mode 100644 index 000000000..dfb96b912 --- /dev/null +++ b/sonoff/xsns_44_sps30.ino @@ -0,0 +1,299 @@ +/* + xsns_44_sps30.ino - Sensirion SPS30 + + Copyright (C) 2019 Gerhard Mutz and Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_I2C +#ifdef USE_SPS30 + +#define XSNS_44 44 + +#define SPS30_ADDR 0x69 + +#include +#include + +uint8_t sps30_ready = 0; +struct SPS30 { + float PM1_0; + float PM2_5; + float PM4_0; + float PM10; + float NCPM0_5; + float NCPM1_0; + float NCPM2_5; + float NCPM4_0; + float NCPM10; + float TYPSIZ; +} sps30_result; + +#define SPS_CMD_START_MEASUREMENT 0x0010 +#define SPS_CMD_START_MEASUREMENT_ARG 0x0300 +#define SPS_CMD_STOP_MEASUREMENT 0x0104 +#define SPS_CMD_READ_MEASUREMENT 0x0300 +#define SPS_CMD_GET_DATA_READY 0x0202 +#define SPS_CMD_AUTOCLEAN_INTERVAL 0x8004 +#define SPS_CMD_CLEAN 0x5607 +#define SPS_CMD_GET_ACODE 0xd025 +#define SPS_CMD_GET_SERIAL 0xd033 +#define SPS_CMD_RESET 0xd304 +#define SPS_WRITE_DELAY_US 20000 +#define SPS_MAX_SERIAL_LEN 32 + +uint8_t sps30_calc_CRC(uint8_t *data) { + uint8_t crc = 0xFF; + for (uint8_t i = 0; i < 2; i++) { + crc ^= data[i]; + for(uint8_t bit = 8; bit > 0; --bit) { + if(crc & 0x80) { + crc = (crc << 1) ^ 0x31u; + } else { + crc = (crc << 1); + } + } + } + return crc; +} + + +unsigned char twi_readFrom(unsigned char address, unsigned char* buf, unsigned int len, unsigned char sendStop); + +void sps30_get_data(uint16_t cmd, uint8_t *data, uint8_t dlen) { +unsigned char cmdb[2]; +uint8_t tmp[3]; +uint8_t index=0; +memset(data,0,dlen); +uint8_t twi_buff[64]; + + Wire.beginTransmission(SPS30_ADDR); + cmdb[0]=cmd>>8; + cmdb[1]=cmd; + Wire.write(cmdb,2); + Wire.endTransmission(); + + // need 60 bytes max + dlen/=2; + dlen*=3; + + twi_readFrom(SPS30_ADDR,twi_buff,dlen,1); + + uint8_t bind=0; + while (bind>8; + cmdb[1]=cmd; + + if (cmd==SPS_CMD_START_MEASUREMENT) { + cmdb[2]=SPS_CMD_START_MEASUREMENT_ARG>>8; + cmdb[3]=SPS_CMD_START_MEASUREMENT_ARG&0xff; + cmdb[4]=sps30_calc_CRC(&cmdb[2]); + Wire.write(cmdb,5); + } else { + Wire.write(cmdb,2); + } + Wire.endTransmission(); +} + +void SPS30_Detect() { + + if (!I2cDevice(SPS30_ADDR)) { + return; + } + uint8_t dcode[32]; + sps30_get_data(SPS_CMD_GET_SERIAL,dcode,sizeof(dcode)); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("sps30 found with serial: %s"),dcode); + sps30_cmd(SPS_CMD_START_MEASUREMENT); + sps30_ready = 1; +} + +#define D_UNIT_PM "ug/m3" +#define D_UNIT_NCPM "#/m3" + +#ifdef USE_WEBSERVER +const char HTTP_SNS_SPS30_a[] PROGMEM ="{s}SPS30 " "%s" "{m}%s " D_UNIT_PM "{e}"; +const char HTTP_SNS_SPS30_b[] PROGMEM ="{s}SPS30 " "%s" "{m}%s " D_UNIT_NCPM "{e}"; +const char HTTP_SNS_SPS30_c[] PROGMEM ="{s}SPS30 " "TYPSIZ" "{m}%s " "um" "{e}"; +#endif // USE_WEBSERVER + +#define PMDP 2 + +#define SPS30_HOURS Settings.sps30_inuse_hours +//#define SPS30_HOURS sps30_inuse_hours +//uint8_t sps30_inuse_hours; + +void SPS30_Every_Second() { + + if (!sps30_ready) return; + + if (uptime%10==0) { + uint8_t vars[sizeof(float)*10]; + sps30_get_data(SPS_CMD_READ_MEASUREMENT,vars,sizeof(vars)); + float *fp=&sps30_result.PM1_0; + + typedef union { + uint8_t array[4]; + float value; + } ByteToFloat; + + ByteToFloat conv; + + for (uint8_t count=0; count<10; count++) { + for (uint8_t i = 0; i < 4; i++){ + conv.array[3-i] = vars[count*sizeof(float)+i]; + } + *fp++=conv.value; + } + } + + if (uptime%3600==0 && uptime>60) { + // should auto clean once per week runtime + // so count hours, should be in Settings + SPS30_HOURS++; + if (SPS30_HOURS>(7*24)) { + sps30_cmd(SPS_CMD_CLEAN); + SPS30_HOURS=0; + } + } + +} + +void SPS30_Show(bool json) { + char str[64]; + if (!sps30_ready) { + return; + } + + if (json) { + dtostrfd(sps30_result.PM1_0,PMDP,str); + ResponseAppend_P(PSTR(",\"SPS30\":{\"" "PM1_0" "\":%s"), str); + dtostrfd(sps30_result.PM2_5,PMDP,str); + ResponseAppend_P(PSTR(",\"" "PM2_5" "\":%s"), str); + dtostrfd(sps30_result.PM4_0,PMDP,str); + ResponseAppend_P(PSTR(",\"" "PM4_0" "\":%s"), str); + dtostrfd(sps30_result.PM10,PMDP,str); + ResponseAppend_P(PSTR(",\"" "PM10" "\":%s"), str); + dtostrfd(sps30_result.NCPM0_5,PMDP,str); + ResponseAppend_P(PSTR(",\"" "NCPM0_5" "\":%s"), str); + dtostrfd(sps30_result.NCPM1_0,PMDP,str); + ResponseAppend_P(PSTR(",\"" "NCPM1_0" "\":%s"), str); + dtostrfd(sps30_result.NCPM2_5,PMDP,str); + ResponseAppend_P(PSTR(",\"" "NCPM2_5" "\":%s"), str); + dtostrfd(sps30_result.NCPM4_0,PMDP,str); + ResponseAppend_P(PSTR(",\"" "NCPM4_0" "\":%s"), str); + dtostrfd(sps30_result.NCPM10,PMDP,str); + ResponseAppend_P(PSTR(",\"" "NCPM10" "\":%s"), str); + dtostrfd(sps30_result.TYPSIZ,PMDP,str); + ResponseAppend_P(PSTR(",\"" "TYPSIZ" "\":%s}"), str); + +#ifdef USE_WEBSERVER + } else { + dtostrfd(sps30_result.PM1_0,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_a,"PM 1.0",str); + dtostrfd(sps30_result.PM2_5,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_a,"PM 2.5",str); + dtostrfd(sps30_result.PM4_0,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_a,"PM 4.0",str); + dtostrfd(sps30_result.PM10,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_a,"PM 10",str); + dtostrfd(sps30_result.NCPM0_5,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_b,"NCPM 0.5",str); + dtostrfd(sps30_result.NCPM1_0,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_b,"NCPM 1.0",str); + dtostrfd(sps30_result.NCPM2_5,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_b,"NCPM 2.5",str); + dtostrfd(sps30_result.NCPM4_0,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_b,"NCPM 4.0",str); + dtostrfd(sps30_result.NCPM10,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_b,"NCPM 10",str); + dtostrfd(sps30_result.TYPSIZ,PMDP,str); + WSContentSend_PD(HTTP_SNS_SPS30_c,str); +#endif + } + +} + + +bool XSNS_44_cmd(void) { + boolean serviced = true; + const char S_JSON_SPS30[] = "{\"" D_CMND_SENSOR "%d\":%s}"; + + if (XdrvMailbox.data_len > 0) { + char *cp=XdrvMailbox.data; + if (*cp=='c') { + // clean cmd + sps30_cmd(SPS_CMD_CLEAN); + cp++; + snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_SPS30, XSNS_44,"clean_fan"); + } else { + serviced=false; + } + } + return serviced; +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + + +bool Xsns44(byte function) +{ + bool result = false; + + if (i2c_flg) { + switch (function) { + case FUNC_INIT: + SPS30_Detect(); + break; + case FUNC_EVERY_SECOND: + SPS30_Every_Second(); + break; + case FUNC_JSON_APPEND: + SPS30_Show(1); + break; + case FUNC_COMMAND_SENSOR: + if (XSNS_44 == XdrvMailbox.index) { + result = XSNS_44_cmd(); + } + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + SPS30_Show(0); + break; +#endif // USE_WEBSERVER + } + } + return result; +} + +#endif // USE_SPS30 +#endif // USE_I2C diff --git a/tools/decode-config.py b/tools/decode-config.py index 6482edcbd..c50caa126 100755 --- a/tools/decode-config.py +++ b/tools/decode-config.py @@ -908,11 +908,9 @@ Setting_6_5_0_10['flag3'][0].update ({ 'use_underscore': ('