Skip to content

Commit

Permalink
PID Control (#4)
Browse files Browse the repository at this point in the history
* Release 6.2.0

Release 6.2.0

* Prep release

* Prep release

* Visual Studio Code adds

* Added Timeprop and PID

* Merge to 6.4.1 (#1) (#2)

* PS_16_DZ: GPIO13 => LED1

* PS_16_DZ: cleanup code

* Add Command CalcRes

* Update settings.ino

* Update decode-config.py

* Add SetOption59 info

Add SetOption59 info

* decode-config.py: add 6.3.0.11 to .14 settings

* Add dynamic buffer space

Make serial buffer space reservation dynamic

* Add dynamic buffer space

Add dynamic buffer space

* Fix dynamic buffer handling

Fix dynamic buffer handling

* Move static to dynamic buffers

 * Add support for SM Smart Wifi Dimmer PS-16-DZ (arendst#4465)
 * Move some static (serial) buffers to dynamic buffers

* Prep for Software SPI

Prep for Software SPI used by some displays

* PS_16_DZ: only update brightness if it actually changed

* PS_16_DZ: reduce buffer-size and actually check for overflow

* PS_16_DZ: optimize for size

* PS_16_DZ: further optimization

* PS_16_DZ: leave space for trailing 0-byte

* Hass: Restart if topic is changed

* PS_16_DZ: cleanup

* Fix possible overflow situation

Fix possible overflow situation

* Small improvements to Hass MQTT discovery

* Add support for Teckin US

Add support for Teckin US Wifi Smart Switch with Energy Monitoring (arendst#4481)

* Update display and epaper drivers

Update display and epaper drivers

* switch to using NewPing lib directly for sr04
add NewPing-1.9.1 lib

* Housekeeping

* Upd settings.ino - set sleep=0 when SetOption36>0

Update settings.ino - set sleep=0 when SetOption36>0

* Prevent sleep and SetOption36 being used concurrently

Prevent sleep and SetOption36 being used concurrently

* Fix sleep->SetOption36 transition

* Update fr-FR.h

Synced with changes since  v6.2.1.7

* Merge sleep command

* Bump version to 0x0603000F

Bump version to 0x0603000F to make sure new sleep command takes effect on 50 as default for upgrades where sleep is < 50.

* Update support_wifi.ino

* 6.3.0.15 Update dynamic sleep

6.3.0.15 20181201
 * Removed command SetOption36 (arendst#4497)
 * Add command SetOption60 0/1 to select dynamic sleep (0) or sleep (1) (arendst#4497)

* Update with dynamic sleep flag

* Added Support for ButtonN and SwitchN

* RULES: Added BREAK as an alternative ENDON

RULES: Added BREAK as an alternative ENDON that will stop the execution of the following rules.

If a rule that ends with BREAK, is triggered, then the following rules of that set will not be executed. This is useful for cases like: arendst#4477

* Update xsns_22_sr04.ino

* RULES: Added BREAK as an alternative ENDON

* Delete duplicated min and max functions

* Update README.md

* Update _changelog.ino

* Further improve Hass auto discovery

* Add dummy soft_spi_flg to satisfy compiler

@arendst Just adding this, for now, to satisfy the compiler until you get time to merge the rest of the Software SPI support.

* decode-config.py: add new settings

- add 6.3.0.15 setting change
- adapt 6.3.0.13 setting change

* Add GPIO2 for Sonoff Basic Template

Add GPIO2 for Sonoff Basic Template

* Add support for GPIO02 for newer Sonoff Basic

Add support for GPIO02 for newer Sonoff Basic

* Fix Compilation issue with STAGE Core

Now the Stage core has the the same STR macro defined in sonoff.h.

* Create RF-Bridge-EFM8BB1-20181127.hex

ignore RF pulses < 100µs, increased uart RX buffer
-> stopp red led flashing

* Announce switches

* Final fix compile error (arendst#4509)

Final fix soft_spi_flg compile error (arendst#4509)

* Fix logic for ON/OFF vs TOGGLE

* Initial translation to Swedish

* Added Key to select Swedish Translation

* Added Swedish to Precompiled bins

* Added missing "

* Matched the order of days with Tasmota Timers

* Update platformio.ini

Support for enable / disable exceptions code in firmware for esp8266 core 2.5.0 (stage) or up

* localization for Polish - KNX, timers, etc.

* Add Domoticz Selector for Fanspeed

Add support for iFan02 Fanspeed in Domoticz using a selector (arendst#4517)

* Update sonoff.ino

* 6.3.0.16 - Bump version

6.3.0.16 20181201
 * Add support for iFan02 Fanspeed in Domoticz using a selector (arendst#4517)
 * Add Announce Switches to MQTT Discovery (arendst#4531)

* Fix Domoticz Fanspeed

Fix Domoticz Fanspeed

* MCP230xx - Add Interrupt Retain

* MCP230xx driver - add interrupt retention

MCP230xx driver - add interrupt retention over teleperiod.

* Fix HASS discovery of switches

* Announce RGBW light, add 'White' command

* Enabled forced local operation when button- or switchtopic is set

* Update Hass discovery

* v2.1.0012
decode-config.py: add new settings

- add 6.3.0.16 setting change

* decode-config.py: add new settings

- add 6.3.0.16 setting change

* Fix reversed logic when announcing buttons / switches

* add DMP mode to MPU-6050

* fix compile issue on ESP8266

* Clean and shrink lights

Clean and shrink lights

* Add support for device registry

* Update my_user_config.h

* Fix Warning in Platformio if building MPU and KNX together

* USE_MPU6050_DMP moved to my_user_config.h

* USE_MPU6050_DMP moved to my_user_config.h

* Corrected Domoticz Temp published from MPU6050

* decode-config.py: add new settings

- add SetOption61 from PR arendst#4562

* Update my_user_config.h

* Fix language file

Fix language file

* 6.3.0.17 Add features

6.3.0.17 20181211
 * Add support for TheoV2 sensors as documented on https://sidweb.nl
 * Add support for SDM220 (arendst#3610)
 * Enhance support for MPU6050 using DMP (arendst#4581)

* add manzuko "power strip"

* Add more support

 * Add support for decoding Theo V2 sensors as documented on https://sidweb.nl using 434MHz RF sensor receiver
 * Add support for decoding Alecto V2 sensors like ACH2010, WS3000 and DKW2012 using 868MHz RF sensor receiver
 * Add support for Manzoku Power Strip (arendst#4590)

* Updated Spanish Translation

* Update de-DE.h

* More tweak of Hass switch/button

* Update fr-FR.h

Added SDM220  new strings

* Update fr-FR.h

SDM220 strings  reworked to fit in the space available

* initial work on range-support

* mqtt messages updated

* Publish state message when changing effect

* decode-config.py: Fix `@v` filename template (arendst#4609)

- fix restore filename auto extension

* add debug code to show final command sequence

* correcting memcpy calls

* Add TheoV2 sensor source

Add TheoV2 sensor source and information used with Rf Sensor

* Add LwIP 2 no features (Low Flash)

* Fix ArduinoOTA for Core 2.5.0 (fix is backwards compatible)

arendst#4619

* Fix ArduinoOTA for Core 2.5.0

Fix ArduinoOTA for Core 2.5.0

* Fix HomeAssistant Temp Sensor AutoDiscovery

arendst#4627

* New 2.5.0-2 toolchain does not like uncast bitwise

The new toolchain (2.5.0-2) used from core 2.5.0 does not want bitwise operations to be performed on non-integer variables so we cast the double used in send_2byte_float() to a (long) as it should be.

* Fix NewPing-1.9.1 library.properties

Fix NewPing-1.9.1 library.properties to avoid compiler warning about possibly not supporting ESP8266 whereas we know now that it does. This causes the compiler warning (which may alarm some users unnecessarily) that NewPing claims to run on avr and stm32.

* Updates for release 6.4.0

Updates for release 6.4.0

* Prep for release

Prep for release

* Updates for release 6.4.0

Updates for release 6.4.0

* Updates for release 6.4.0

Updates for release 6.4.0

* Updates for release 6.4.0

Updates for release 6.4.0

* Prep for release

Prep for release

* Updates for release 6.4.0

Updates for release 6.4.0

* Updates for release 6.4.0

Updates for release 6.4.0

* Prep for merge

* 6.4.0.1 New dev release

* Removing duplicate entrys

Options for core 2.5.0 where duplicated

* Update README.md

* Create boards.txt

* Create platform.txt

* No exceptions

* it-IT language updated

* option for firmware directory added

* Ease selecting core versions

Ease selecting core versions by grouping core parameters together. Select core version in group [core_active]

* webserver: swap index and name in module configuration

* Tune driver RF Sensor

- Free memory when driver RF Sensor is compiled but not used.
- Fix possible buffer overflow exceptions
- Add rule and hardware info to source

* introduce new api-call for dimmer-devices

* SerialLog: output CR + LF instead of just LF

* Update linker files

Update linker files to released linker files for coe 2.4.2 and up

* Add sk-SK language

* Update bg-BG.h

* Add support for AZ-Instrument 7798 CO2 meter/datalogger.

* Add support for AZ-Instrument 7798 CO2 meter/datalogger.

* Revert platformio.ini and my_user_config to original version,
adding line for new sensor but leaving it commented out.

* Include changes from original into sonoff/language/bg-BG.h

* Updated from originals: platformio.ini, sonoff/my_user_config.h.

* Revert to original RELEASENOTES.md.

* Add void to functions without parameters

* Fix temperature conversion

Fix temperature conversion to still use ConvertTemp() even if meter sends the value in F

* Add support for AZ-Instrument 7798 CO2

Add support for AZ-Instrument 7798 CO2 meter/datalogger

* pressure unit of measure reporting

Added a dedicated pressure sensor management to report back as pressure sensor and not fallback as a generic sensor in HA autodiscovery process

* add missing units of measurements for energy

* removed some errors from copy pasting..

* add empty unit of measurement for any sensors

* Added option for checking mqtt tls against root ca

Signed-off-by: Frank Meies <[email protected]>

* Change RAM usage BMP/BME

Change RAM usage BMP/BME I2C sensors

* Update Arduino IDE core 2.5.0 files

Update Arduino IDE core 2.5.0 files

* Fix wifi strongest signal detection

Fix wifi strongest signal detection (arendst#4704)

* 6.4.0.2 Fix possible dtostrf bos

6.4.0.2 20181221
 * Fix possible dtostrf buffer overflows by increasing buffers

* Still need patched PWM for core 2.5.0

Still need patched PWM for core 2.5.0

* Check for core version when enabling tls ca cert

Signed-off-by: Frank Meies <[email protected]>

* Check for core version when enabling tls ca cert, changed names of defines

Signed-off-by: Frank Meies <[email protected]>

* Remove SetOption55 (hass_short_discovery_msg)

* Rename HASS discovery string constants

* Remove Alexa message "this value is outside the range of the device"

* 6.4.0.3 Hass and Alexa fixes

6.4.0.3 20181222
 * Change Hass discovery to short MQTT messages as used by Hass 0.81 and up (arendst#4711)
 * Change FallbackTopic detection (arendst#4706)
 * Add define WIFI_SOFT_AP_CHANNEL in my_user_config.h to set Soft Access Point Channel number between 1 and 13 as used by Wifi Manager web GUI (arendst#4673)
 * Fix Alexa "this value is outside the range of the device". Needs power cycle and Alexa deletion/discovery cycle. (arendst#3159, arendst#4712)

* Add optional TLS_CA_CERT

Add define USE_MQTT_TLS_CA_CERT for checking MQTT TLS against root ca using Let's Encrypt cert from sonoff_letsencrypt.h - not supported with core 2.3.0 (arendst#4703)

* decode-config.py: adapt settings change

- remove SetOption55 (hass_short_discovery_msg)

* Change FallbackTopic

Change FallbackTopic from cmnd/<mqttclient>/ to cmnd/<mqttclient>_fb/ to discriminate from Topic (arendst#1528)

* PS_16_DZ: fix broken if-statement

* Change MQTT GUI password handling

Change MQTT GUI password handling (arendst#4723)

* Release 6.4.1

* 6.4.1.1 Fix most compiler warnings

6.4.1.1 20181224
 * Fix most compiler warnings

* Fix compiler warning

Fix compiler warning

* Change switch debounce

Change switch input detection by optimizing switch debounce (arendst#4724)

* Add INPUT_PULLUP option for single DS18B20

* Add variable %timestamp% to be used in rules

This PR adds a new variable %timestamp% to be used in a rule to allow the user to include the Time Stamp as Tasmota has for Status, Sensors, etc, for example:

Command:

publish stat/topic/sensor {"Time":"%timestamp%","mysensor":"%var1%"}

Output:

{"Time":"2018-12-27T12:52:57","mysensor":"1"}

(arendst#4734)

* 6.4.1.2 Rewrite Switch driver

6.4.1.2 20181228
 * Change switch driver making it modular and introduce input filter (arendst#4665, arendst#4724)
 * Add define DS18B20_INTERNAL_PULLUP to select internal input pullup when only one DS18B20 sensor is connected eliminating external resistor (arendst#4738)
 * Add variable %timestamp% to rules (arendst#4749)

* Update platformio.ini with stage

Update platformio.ini with latest stage option

* Prep for template tuning

* 6.4.1.3 Change sonoff_template.h

6.4.1.3 20181229
 * Change sonoff_template.h module lay-out by removing non-configurable GPIOs

* Fix Typo in Spanish Translation

* Fix MGC3130/AZ7798 compile error

* Add debugging options to Switch

Add more debugging options to Switch using SwitchDebounce

* Fix DS3231 driver

Fix DS3231 driver breaking function chain (arendst#4759)

* Disable unused feature

* Revert "Disable unused feature"

This reverts commit 8fe61f6.

* Add test for result usage

* Add tests for result
  • Loading branch information
wienerdogracing authored Dec 31, 2018
1 parent 23ba11e commit 8cbbaec
Show file tree
Hide file tree
Showing 7 changed files with 1,053 additions and 1 deletion.
192 changes: 192 additions & 0 deletions lib/ProcessControl/PID.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/**
* Copyright 2018 Colin Law
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* See Timeprop.h for Usage
*
**/


#include "PID.h"

PID::PID() {
m_initialised = 0;
m_last_sample_time = 0;
m_last_pv_update_time = 0;
m_last_power = 0.0;
}

void PID::initialise( double setpoint, double prop_band, double t_integral, double t_derivative,
double integral_default, int max_interval, double smooth_factor, unsigned char mode_auto, double manual_op ) {

m_setpoint = setpoint;
m_prop_band = prop_band;
m_t_integral = t_integral;
m_t_derivative = t_derivative;
m_integral_default = integral_default;
m_max_interval = max_interval;
m_smooth_factor= smooth_factor;
m_mode_auto= mode_auto;
m_manual_op = manual_op;

m_initialised = 1;

}


/* called regularly to calculate and return new power value */
double PID::tick( unsigned long nowSecs ) {
double power;
double factor;
if (m_initialised && m_last_pv_update_time) {
// we have been initialised and have been given a pv value
// check whether too long has elapsed since pv was last updated
if (m_max_interval > 0 && nowSecs - m_last_pv_update_time > m_max_interval) {
// yes, too long has elapsed since last PV update so go to fallback power
power = m_manual_op;
} else {
// is this the first time through here?
if (m_last_sample_time) {
// not first time
unsigned long delta_t = nowSecs - m_last_sample_time; // seconds
if (delta_t <= 0 || delta_t > m_max_interval) {
// too long since last sample so leave integral as is and set deriv to zero
m_derivative = 0;
} else {
if (m_smooth_factor > 0) {
// A derivative smoothing factor has been supplied
// smoothing time constant is td/factor but with a min of delta_t to stop overflows
int ts = m_t_derivative/m_smooth_factor > delta_t ? m_t_derivative/m_smooth_factor : delta_t;
factor = 1.0/(ts/delta_t);
} else {
// no integral smoothing so factor is 1, this makes smoothed_value the previous pv
factor = 1.0;
}
double delta_v = (m_pv - m_smoothed_value) * factor;
m_smoothed_value = m_smoothed_value + delta_v;
m_derivative = m_t_derivative * delta_v/delta_t;
// lock the integral if abs(previous integral + error) > prop_band/2
// as this means that P + I is outside the linear region so power will be 0 or full
// also lock if control is disabled
double error = m_pv - m_setpoint;
double pbo2 = m_prop_band/2.0;
double epi = error + m_integral;
if (epi < 0.0) epi = -epi; // abs value of error + m_integral
if (epi < pbo2 && m_mode_auto) {
if (m_t_integral <= 0) {
// t_integral is zero (or silly), set integral to one end or the other
// or half way if exactly on sp
if (error > 0.0) {
m_integral = pbo2;
} else if (error < 0) {
m_integral = -pbo2;
} else {
m_integral = 0.0;
}
} else {
m_integral = m_integral + error * delta_t/m_t_integral;
// clamp to +- 0.5 prop band widths so that it cannot push the zero power point outside the pb
if ( m_integral < -pbo2 ) {
m_integral = -pbo2;
} else if (m_integral > pbo2) {
m_integral = pbo2;
}
}
}
}

} else {
// first time through, initialise context data
m_smoothed_value = m_pv;
// setup the integral term so that the power out would be integral_default if pv=setpoint
m_integral = (0.5 - m_integral_default)*m_prop_band;
m_derivative = 0.0;
}

double proportional = m_pv - m_setpoint;
if (m_prop_band == 0) {
// prop band is zero so drop back to on/off control with zero hysteresis
if (proportional > 0.0) {
power = 0.0;
} else if (proportional < 0.0) {
power = 1.0;
} else {
// exactly on sp so leave power as it was last time round
power = m_last_power;
}
}
else {
power = -1.0/m_prop_band * (proportional + m_integral + m_derivative) + 0.5;
}
// set power to disabled value if the loop is not enabled
if (!m_mode_auto) {
power = m_manual_op;
}
m_last_sample_time = nowSecs;
}
} else {
// not yet initialised or no pv value yet so set power to disabled value
power = m_manual_op;
}
if (power < 0.0) {
power = 0.0;
} else if (power > 1.0) {
power = 1.0;
}
m_last_power = power;
return power;
}

// call to pass in new process value
void PID::setPv( double pv, unsigned long nowSecs ){
m_pv = pv;
m_last_pv_update_time = nowSecs;
}

// methods to modify configuration data
void PID::setSp( double setpoint ) {
m_setpoint = setpoint;
}

void PID::setPb( double prop_band ) {
m_prop_band = prop_band;
}

void PID::setTi( double t_integral ) {
m_t_integral = t_integral;
}

void PID::setTd( double t_derivative ) {
m_t_derivative = t_derivative;
}

void PID::setInitialInt( double integral_default ) {
m_integral_default = integral_default;
}

void PID::setDSmooth( double smooth_factor ) {
m_smooth_factor = smooth_factor;
}

void PID::setAuto( unsigned char mode_auto ) {
m_mode_auto = mode_auto;
}

void PID::setManualPower( double manual_op ) {
m_manual_op = manual_op;
}

void PID::setMaxInterval( int max_interval ) {
m_max_interval = max_interval;
}
90 changes: 90 additions & 0 deletions lib/ProcessControl/PID.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* Copyright 2018 Colin Law
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

/**
* A PID control class
*
* Github repository https://github.com/colinl/process-control.git
*
* Given ...
*
* Usage:
* First call initialise(), see below for parameters then
* ...
* The functions require a parameter nowSecs which is a representation of the
* current time in seconds. The absolute value of this is immaterial, it is
* used for relative timing only.
*
**/


#ifndef PID_h
#define PID_h

class PID {
public:

PID();

/*
Initialiser given
current time in seconds
*/
void initialise( double setpoint, double prop_band, double t_integral, double t_derivative,
double integral_default, int max_interval, double smooth_factor, unsigned char mode_auto, double manual_op );


/* called regularly to calculate and return new power value */
double tick(unsigned long nowSecs);

// call to pass in new process value
void setPv( double pv, unsigned long nowSecs );

// methods to modify configuration data
void setSp( double setpoint );
void setPb( double prop_band );
void setTi( double t_integral );
void setTd( double t_derivative );
void setInitialInt( double integral_default );
void setDSmooth( double smooth_factor );
void setAuto( unsigned char mode_auto );
void setManualPower( double manual_op );
void setMaxInterval( int max_interval );

private:
double m_pv;
double m_setpoint;
double m_prop_band;
double m_t_integral;
double m_t_derivative;
double m_integral_default;
double m_smooth_factor;
unsigned char m_mode_auto;
double m_manual_op;
int m_max_interval;
double m_last_power;


unsigned char m_initialised;
unsigned long m_last_pv_update_time; // the time of last pv update secs
unsigned long m_last_sample_time; // the time of the last tick() run
double m_smoothed_value;
double m_integral;
double m_derivative ;
};

#endif // Timeprop_h
94 changes: 94 additions & 0 deletions lib/ProcessControl/Timeprop.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* Copyright 2018 Colin Law
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* See Timeprop.h for Usage
*
**/


#include "Timeprop.h"

void Timeprop::initialise( int cycleTime, int deadTime, unsigned char invert, float fallbackPower, int maxUpdateInterval,
unsigned long nowSecs) {
m_cycleTime = cycleTime;
m_deadTime = deadTime;
m_invert = invert;
m_fallbackPower = fallbackPower;
m_maxUpdateInterval = maxUpdateInterval;

m_dtoc = (float)deadTime/cycleTime;
m_opState = 0;
setPower(m_fallbackPower, nowSecs);
}

/* set current power required 0:1, given power and current time in seconds */
void Timeprop::setPower( float power, unsigned long nowSecs ) {
if (power < 0.0) {
power = 0.0;
} else if (power >= 1.0) {
power = 1.0;
}
m_power = power;
m_lastPowerUpdateTime = nowSecs;
};

/* called regularly to provide new output value */
/* returns new o/p state 0, 1 */
int Timeprop::tick( unsigned long nowSecs) {
int newState;
float wave;
float direction;
float effectivePower;

// check whether too long has elapsed since power was last updated
if (m_maxUpdateInterval > 0 && nowSecs - m_lastPowerUpdateTime > m_maxUpdateInterval) {
// yes, go to fallback power
setPower(m_fallbackPower, nowSecs);
}

wave = (nowSecs % m_cycleTime)/(float)m_cycleTime;
// determine direction of travel and convert to triangular wave
if (wave < 0.5) {
direction = 1; // on the way up
wave = wave*2;
} else {
direction = -1; // on the way down
wave = (1 - wave)*2;
}
// if a dead_time has been supplied for this o/p then adjust power accordingly
if (m_deadTime > 0 && m_power > 0.0 && m_power < 1.0) {
effectivePower = (1.0-2.0*m_dtoc)*m_power + m_dtoc;
} else {
effectivePower = m_power;
}
// cope with end cases in case values outside 0..1
if (effectivePower <= 0.0) {
newState = 0; // no heat
} else if (effectivePower >= 1.0) {
newState = 1; // full heat
} else {
// only allow power to come on on the way down and off on the way up, to reduce short pulses
if (effectivePower >= wave && direction == -1) {
newState = 1;
} else if (effectivePower <= wave && direction == 1) {
newState = 0;
} else {
// otherwise leave it as it is
newState = m_opState;
}
}
m_opState = newState;
return m_invert ? (1-m_opState) : m_opState;
}
Loading

0 comments on commit 8cbbaec

Please sign in to comment.