From 66720754817652221d8753aff5434e585466a3e8 Mon Sep 17 00:00:00 2001 From: ascillato Date: Fri, 18 May 2018 15:00:37 -0300 Subject: [PATCH] Added Optional Files Added Optional Files --- lib/ESPAsyncUDP-master/.travis.yml | 37 ++ lib/ESPAsyncUDP-master/README.md | 10 + .../AsyncUDPClient/AsyncUDPClient.ino | 51 +++ .../AsyncUDPMulticastServer.ino | 52 +++ .../AsyncUDPServer/AsyncUDPServer.ino | 50 ++ lib/ESPAsyncUDP-master/keywords.txt | 33 ++ lib/ESPAsyncUDP-master/library.json | 17 + lib/ESPAsyncUDP-master/library.properties | 9 + lib/ESPAsyncUDP-master/src/AsyncUDP.cpp | 427 ++++++++++++++++++ lib/ESPAsyncUDP-master/src/ESPAsyncUDP.h | 130 ++++++ lib/ESPAsyncUDP-master/travis/common.sh | 23 + .../DPT.h | 2 +- .../LICENSE | 0 .../README.md | 0 .../esp-knx-ip-config.cpp | 0 .../esp-knx-ip-conversion.cpp | 0 .../esp-knx-ip-send.cpp | 18 +- .../esp-knx-ip-webserver.cpp | 0 .../esp-knx-ip.cpp | 28 +- .../esp-knx-ip.h | 38 +- .../environment-sensor/environment-sensor.ino | 0 .../examples/sonoff/sonoff.ino | 0 .../examples/static-config/static-config.ino | 0 .../keywords.txt | 0 .../library.properties | 2 +- lib/process-control-master/LICENSE | 201 +++++++++ lib/process-control-master/PID.cpp | 168 +++++++ lib/process-control-master/PID.h | 89 ++++ lib/process-control-master/README.md | 2 + lib/process-control-master/Timeprop.cpp | 94 ++++ lib/process-control-master/Timeprop.h | 85 ++++ sonoff/user_config.h | 167 ++++++- sonoff/xdrv_11_knx.ino | 13 +- sonoff/xdrv_91_timeprop.ino | 220 +++++++++ sonoff/xdrv_92_pid.ino | 374 +++++++++++++++ 35 files changed, 2323 insertions(+), 17 deletions(-) create mode 100644 lib/ESPAsyncUDP-master/.travis.yml create mode 100644 lib/ESPAsyncUDP-master/README.md create mode 100644 lib/ESPAsyncUDP-master/examples/AsyncUDPClient/AsyncUDPClient.ino create mode 100644 lib/ESPAsyncUDP-master/examples/AsyncUDPMulticastServer/AsyncUDPMulticastServer.ino create mode 100644 lib/ESPAsyncUDP-master/examples/AsyncUDPServer/AsyncUDPServer.ino create mode 100644 lib/ESPAsyncUDP-master/keywords.txt create mode 100644 lib/ESPAsyncUDP-master/library.json create mode 100644 lib/ESPAsyncUDP-master/library.properties create mode 100644 lib/ESPAsyncUDP-master/src/AsyncUDP.cpp create mode 100644 lib/ESPAsyncUDP-master/src/ESPAsyncUDP.h create mode 100644 lib/ESPAsyncUDP-master/travis/common.sh rename lib/{esp-knx-ip-0.5.0 => esp-knx-ip-0.5.1}/DPT.h (99%) rename lib/{esp-knx-ip-0.5.0 => esp-knx-ip-0.5.1}/LICENSE (100%) rename lib/{esp-knx-ip-0.5.0 => esp-knx-ip-0.5.1}/README.md (100%) rename lib/{esp-knx-ip-0.5.0 => esp-knx-ip-0.5.1}/esp-knx-ip-config.cpp (100%) rename lib/{esp-knx-ip-0.5.0 => esp-knx-ip-0.5.1}/esp-knx-ip-conversion.cpp (100%) rename lib/{esp-knx-ip-0.5.0 => esp-knx-ip-0.5.1}/esp-knx-ip-send.cpp (88%) rename lib/{esp-knx-ip-0.5.0 => esp-knx-ip-0.5.1}/esp-knx-ip-webserver.cpp (100%) rename lib/{esp-knx-ip-0.5.0 => esp-knx-ip-0.5.1}/esp-knx-ip.cpp (97%) rename lib/{esp-knx-ip-0.5.0 => esp-knx-ip-0.5.1}/esp-knx-ip.h (93%) rename lib/{esp-knx-ip-0.5.0 => esp-knx-ip-0.5.1}/examples/environment-sensor/environment-sensor.ino (100%) rename lib/{esp-knx-ip-0.5.0 => esp-knx-ip-0.5.1}/examples/sonoff/sonoff.ino (100%) rename lib/{esp-knx-ip-0.5.0 => esp-knx-ip-0.5.1}/examples/static-config/static-config.ino (100%) rename lib/{esp-knx-ip-0.5.0 => esp-knx-ip-0.5.1}/keywords.txt (100%) rename lib/{esp-knx-ip-0.5.0 => esp-knx-ip-0.5.1}/library.properties (95%) create mode 100644 lib/process-control-master/LICENSE create mode 100644 lib/process-control-master/PID.cpp create mode 100644 lib/process-control-master/PID.h create mode 100644 lib/process-control-master/README.md create mode 100644 lib/process-control-master/Timeprop.cpp create mode 100644 lib/process-control-master/Timeprop.h create mode 100644 sonoff/xdrv_91_timeprop.ino create mode 100644 sonoff/xdrv_92_pid.ino diff --git a/lib/ESPAsyncUDP-master/.travis.yml b/lib/ESPAsyncUDP-master/.travis.yml new file mode 100644 index 000000000..c1ef4ec8f --- /dev/null +++ b/lib/ESPAsyncUDP-master/.travis.yml @@ -0,0 +1,37 @@ +sudo: false +language: bash +os: + - linux + +script: + - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_1.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :1 -ac -screen 0 1280x1024x16 + - sleep 3 + - export DISPLAY=:1.0 + - wget http://downloads.arduino.cc/arduino-1.6.5-linux64.tar.xz + - tar xf arduino-1.6.5-linux64.tar.xz + - mv arduino-1.6.5 $HOME/arduino_ide + - export PATH="$HOME/arduino_ide:$PATH" + - which arduino + - mkdir -p $HOME/Arduino/libraries + - cp -r $TRAVIS_BUILD_DIR $HOME/Arduino/libraries/ESPAsyncUDP + - cd $HOME/arduino_ide/hardware + - mkdir esp8266com + - cd esp8266com + - git clone https://github.com/esp8266/Arduino.git esp8266 + - cd esp8266/tools + - python get.py + - source $TRAVIS_BUILD_DIR/travis/common.sh + - arduino --board esp8266com:esp8266:generic --save-prefs + - arduino --get-pref sketchbook.path + - build_sketches arduino $HOME/Arduino/libraries/ESPAsyncUDP esp8266 + +notifications: + email: + on_success: change + on_failure: change + webhooks: + urls: + - https://webhooks.gitter.im/e/60e65d0c78ea0a920347 + on_success: change # options: [always|never|change] default: always + on_failure: always # options: [always|never|change] default: always + on_start: false # default: false diff --git a/lib/ESPAsyncUDP-master/README.md b/lib/ESPAsyncUDP-master/README.md new file mode 100644 index 000000000..997a7cc26 --- /dev/null +++ b/lib/ESPAsyncUDP-master/README.md @@ -0,0 +1,10 @@ +# ESPAsyncUDP +Async UDP Library for ESP8266 Arduino [![Build Status](https://travis-ci.org/me-no-dev/ESPAsyncUDP.svg?branch=master)](https://travis-ci.org/me-no-dev/ESPAsyncUDP) + +[![Join the chat at https://gitter.im/me-no-dev/ESPAsyncWebServer](https://badges.gitter.im/me-no-dev/ESPAsyncWebServer.svg)](https://gitter.im/me-no-dev/ESPAsyncWebServer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +This is a fully asynchronous UDP library, aimed at enabling trouble-free, multi-connection network environment for Espressif's ESP8266 MCUs. + +The library is easy to use and includes support for Unicast, Broadcast and Multicast environments + +Latest GIT version of ESP8266 Arduino might be required for this library to work diff --git a/lib/ESPAsyncUDP-master/examples/AsyncUDPClient/AsyncUDPClient.ino b/lib/ESPAsyncUDP-master/examples/AsyncUDPClient/AsyncUDPClient.ino new file mode 100644 index 000000000..cf528fe12 --- /dev/null +++ b/lib/ESPAsyncUDP-master/examples/AsyncUDPClient/AsyncUDPClient.ino @@ -0,0 +1,51 @@ +#include +#include "ESPAsyncUDP.h" + +const char * ssid = "***********"; +const char * password = "***********"; + +AsyncUDP udp; + +void setup() +{ + Serial.begin(115200); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.println("WiFi Failed"); + while(1) { + delay(1000); + } + } + if(udp.connect(IPAddress(192,168,1,100), 1234)) { + Serial.println("UDP connected"); + udp.onPacket([](AsyncUDPPacket packet) { + Serial.print("UDP Packet Type: "); + Serial.print(packet.isBroadcast()?"Broadcast":packet.isMulticast()?"Multicast":"Unicast"); + Serial.print(", From: "); + Serial.print(packet.remoteIP()); + Serial.print(":"); + Serial.print(packet.remotePort()); + Serial.print(", To: "); + Serial.print(packet.localIP()); + Serial.print(":"); + Serial.print(packet.localPort()); + Serial.print(", Length: "); + Serial.print(packet.length()); + Serial.print(", Data: "); + Serial.write(packet.data(), packet.length()); + Serial.println(); + //reply to the client + packet.printf("Got %u bytes of data", packet.length()); + }); + //Send unicast + udp.print("Hello Server!"); + } +} + +void loop() +{ + delay(1000); + //Send broadcast on port 1234 + udp.broadcastTo("Anyone here?", 1234); +} diff --git a/lib/ESPAsyncUDP-master/examples/AsyncUDPMulticastServer/AsyncUDPMulticastServer.ino b/lib/ESPAsyncUDP-master/examples/AsyncUDPMulticastServer/AsyncUDPMulticastServer.ino new file mode 100644 index 000000000..bb3e69c9b --- /dev/null +++ b/lib/ESPAsyncUDP-master/examples/AsyncUDPMulticastServer/AsyncUDPMulticastServer.ino @@ -0,0 +1,52 @@ +#include +#include "ESPAsyncUDP.h" + +const char * ssid = "***********"; +const char * password = "***********"; + +AsyncUDP udp; + +void setup() +{ + Serial.begin(115200); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.println("WiFi Failed"); + while(1) { + delay(1000); + } + } + if(udp.listenMulticast(IPAddress(239,1,2,3), 1234)) { + Serial.print("UDP Listening on IP: "); + Serial.println(WiFi.localIP()); + udp.onPacket([](AsyncUDPPacket packet) { + Serial.print("UDP Packet Type: "); + Serial.print(packet.isBroadcast()?"Broadcast":packet.isMulticast()?"Multicast":"Unicast"); + Serial.print(", From: "); + Serial.print(packet.remoteIP()); + Serial.print(":"); + Serial.print(packet.remotePort()); + Serial.print(", To: "); + Serial.print(packet.localIP()); + Serial.print(":"); + Serial.print(packet.localPort()); + Serial.print(", Length: "); + Serial.print(packet.length()); + Serial.print(", Data: "); + Serial.write(packet.data(), packet.length()); + Serial.println(); + //reply to the client + packet.printf("Got %u bytes of data", packet.length()); + }); + //Send multicast + udp.print("Hello!"); + } +} + +void loop() +{ + delay(1000); + //Send multicast + udp.print("Anyone here?"); +} diff --git a/lib/ESPAsyncUDP-master/examples/AsyncUDPServer/AsyncUDPServer.ino b/lib/ESPAsyncUDP-master/examples/AsyncUDPServer/AsyncUDPServer.ino new file mode 100644 index 000000000..fc12a7fc3 --- /dev/null +++ b/lib/ESPAsyncUDP-master/examples/AsyncUDPServer/AsyncUDPServer.ino @@ -0,0 +1,50 @@ +#include +#include "ESPAsyncUDP.h" + +const char * ssid = "***********"; +const char * password = "***********"; + +AsyncUDP udp; + +void setup() +{ + Serial.begin(115200); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.println("WiFi Failed"); + while(1) { + delay(1000); + } + } + if(udp.listen(1234)) { + Serial.print("UDP Listening on IP: "); + Serial.println(WiFi.localIP()); + udp.onPacket([](AsyncUDPPacket packet) { + Serial.print("UDP Packet Type: "); + Serial.print(packet.isBroadcast()?"Broadcast":packet.isMulticast()?"Multicast":"Unicast"); + Serial.print(", From: "); + Serial.print(packet.remoteIP()); + Serial.print(":"); + Serial.print(packet.remotePort()); + Serial.print(", To: "); + Serial.print(packet.localIP()); + Serial.print(":"); + Serial.print(packet.localPort()); + Serial.print(", Length: "); + Serial.print(packet.length()); + Serial.print(", Data: "); + Serial.write(packet.data(), packet.length()); + Serial.println(); + //reply to the client + packet.printf("Got %u bytes of data", packet.length()); + }); + } +} + +void loop() +{ + delay(1000); + //Send broadcast + udp.broadcast("Anyone here?"); +} diff --git a/lib/ESPAsyncUDP-master/keywords.txt b/lib/ESPAsyncUDP-master/keywords.txt new file mode 100644 index 000000000..67c0b97a7 --- /dev/null +++ b/lib/ESPAsyncUDP-master/keywords.txt @@ -0,0 +1,33 @@ +####################################### +# Syntax Coloring Map For Ultrasound +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +AsyncUDP KEYWORD1 +AsyncUDPPacket KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +connect KEYWORD2 +connected KEYWORD2 +listen KEYWORD2 +listenMulticast KEYWORD2 +close KEYWORD2 +write KEYWORD2 +broadcast KEYWORD2 +onPacket KEYWORD2 +data KEYWORD2 +length KEYWORD2 +localIP KEYWORD2 +localPort KEYWORD2 +remoteIP KEYWORD2 +remotePort KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/lib/ESPAsyncUDP-master/library.json b/lib/ESPAsyncUDP-master/library.json new file mode 100644 index 000000000..fe300b6e6 --- /dev/null +++ b/lib/ESPAsyncUDP-master/library.json @@ -0,0 +1,17 @@ +{ + "name":"ESPAsyncUDP", + "description":"Asynchronous UDP Library for ESP8266", + "keywords":"async,udp,server,client,multicast,broadcast", + "authors": + { + "name": "Hristo Gochkov", + "maintainer": true + }, + "repository": + { + "type": "git", + "url": "https://github.com/me-no-dev/ESPAsyncUDP.git" + }, + "frameworks": "arduino", + "platforms":"espressif" +} diff --git a/lib/ESPAsyncUDP-master/library.properties b/lib/ESPAsyncUDP-master/library.properties new file mode 100644 index 000000000..1aa6ef1ad --- /dev/null +++ b/lib/ESPAsyncUDP-master/library.properties @@ -0,0 +1,9 @@ +name=ESP Async UDP +version=1.0.0 +author=Me-No-Dev +maintainer=Me-No-Dev +sentence=Async UDP Library for ESP8266 +paragraph=Async UDP Library for ESP8266 +category=Other +url=https://github.com/me-no-dev/ESPAsyncUDP +architectures=* diff --git a/lib/ESPAsyncUDP-master/src/AsyncUDP.cpp b/lib/ESPAsyncUDP-master/src/AsyncUDP.cpp new file mode 100644 index 000000000..2a538c4c6 --- /dev/null +++ b/lib/ESPAsyncUDP-master/src/AsyncUDP.cpp @@ -0,0 +1,427 @@ +#include "Arduino.h" +#include "ESPAsyncUDP.h" + +extern "C" { +#include "user_interface.h" +#include "lwip/opt.h" +#include "lwip/inet.h" +#include "lwip/udp.h" +#include "lwip/igmp.h" +} + +AsyncUDPMessage::AsyncUDPMessage(size_t size) +{ + _index = 0; + if(size > 1460) { + size = 1460; + } + _size = size; + _buffer = (uint8_t *)malloc(size); +} + +AsyncUDPMessage::~AsyncUDPMessage() +{ + if(_buffer) { + free(_buffer); + } +} + +size_t AsyncUDPMessage::write(const uint8_t *data, size_t len) +{ + if(_buffer == NULL) { + return 0; + } + size_t s = space(); + if(len > s) { + len = s; + } + memcpy(_buffer + _index, data, len); + _index += len; + return len; +} + +size_t AsyncUDPMessage::write(uint8_t data) +{ + return write(&data, 1); +} + +size_t AsyncUDPMessage::space() +{ + if(_buffer == NULL) { + return 0; + } + return _size - _index; +} + +uint8_t * AsyncUDPMessage::data() +{ + return _buffer; +} + +size_t AsyncUDPMessage::length() +{ + return _index; +} + +void AsyncUDPMessage::flush() +{ + _index = 0; +} + + +AsyncUDPPacket::AsyncUDPPacket(AsyncUDP *udp, ip_addr_t *localIp, uint16_t localPort, ip_addr_t *remoteIp, uint16_t remotePort, uint8_t *data, size_t len) +{ + _udp = udp; + _localIp = localIp; + _localPort = localPort; + _remoteIp = remoteIp; + _remotePort = remotePort; + _data = data; + _len = len; +} + +AsyncUDPPacket::~AsyncUDPPacket() +{ + +} + +uint8_t * AsyncUDPPacket::data() +{ + return _data; +} + +size_t AsyncUDPPacket::length() +{ + return _len; +} + +IPAddress AsyncUDPPacket::localIP() +{ + return IPAddress(_localIp->addr); +} + +uint16_t AsyncUDPPacket::localPort() +{ + return _localPort; +} + +IPAddress AsyncUDPPacket::remoteIP() +{ + return IPAddress(_remoteIp->addr); +} + +uint16_t AsyncUDPPacket::remotePort() +{ + return _remotePort; +} + +bool AsyncUDPPacket::isBroadcast() +{ + return _localIp->addr == 0xFFFFFFFF || _localIp->addr == (uint32_t)(0); +} + +bool AsyncUDPPacket::isMulticast() +{ + return ip_addr_ismulticast(_localIp); +} + +size_t AsyncUDPPacket::write(const uint8_t *data, size_t len) +{ + return _udp->writeTo(data, len, _remoteIp, _remotePort); +} + +size_t AsyncUDPPacket::write(uint8_t data) +{ + return write(&data, 1); +} + +size_t AsyncUDPPacket::send(AsyncUDPMessage &message) +{ + return write(message.data(), message.length()); +} + + + +AsyncUDP::AsyncUDP() +{ + _pcb = NULL; + _connected = false; + _handler = NULL; +} + +AsyncUDP::~AsyncUDP() +{ + close(); +} + +AsyncUDP::operator bool() +{ + return _connected; +} + +bool AsyncUDP::connected() +{ + return _connected; +} + +void AsyncUDP::onPacket(AuPacketHandlerFunctionWithArg cb, void * arg) +{ + onPacket(std::bind(cb, arg, std::placeholders::_1)); +} + +void AsyncUDP::onPacket(AuPacketHandlerFunction cb) +{ + _handler = cb; +} + +void AsyncUDP::_recv(udp_pcb *upcb, pbuf *pb, ip_addr_t *addr, uint16_t port) +{ + (void)upcb; // its unused, avoid warning + while(pb != NULL) { + if(_handler) { + uint8_t * data = (uint8_t*)((pb)->payload); + size_t len = pb->len; + + ip_hdr* iphdr = reinterpret_cast(data - UDP_HLEN - IP_HLEN); + ip_addr_t daddr; + daddr.addr = iphdr->dest.addr; + + udp_hdr* udphdr = reinterpret_cast(((uint8_t*)((pb)->payload)) - UDP_HLEN); + uint16_t dport = ntohs(udphdr->dest); + + AsyncUDPPacket packet(this, &daddr, dport, addr, port, data, len); + _handler(packet); + } + + pbuf * this_pb = pb; + pb = pb->next; + this_pb->next = NULL; + pbuf_free(this_pb); + } +} + +#if LWIP_VERSION_MAJOR == 1 +void AsyncUDP::_s_recv(void *arg, udp_pcb *upcb, pbuf *p, ip_addr_t *addr, uint16_t port) +#else +void AsyncUDP::_s_recv(void *arg, udp_pcb *upcb, pbuf *p, const ip_addr_t *addr, uint16_t port) +#endif +{ + reinterpret_cast(arg)->_recv(upcb, p, (ip_addr_t *)addr, port); +} + +bool AsyncUDP::listen(ip_addr_t *addr, uint16_t port) +{ + close(); + _pcb = udp_new(); + if(_pcb == NULL) { + return false; + } + err_t err = udp_bind(_pcb, addr, port); + if(err != ERR_OK) { + close(); + return false; + } + udp_recv(_pcb, &_s_recv, (void *) this); + _connected = true; + return true; +} + +bool AsyncUDP::listenMulticast(ip_addr_t *addr, uint16_t port, uint8_t ttl) +{ + close(); + if(!ip_addr_ismulticast(addr)) { + return false; + } + ip_addr_t multicast_if_addr; + struct ip_info ifIpInfo; + int mode = wifi_get_opmode(); + if(mode & STATION_MODE) { + wifi_get_ip_info(STATION_IF, &ifIpInfo); + multicast_if_addr.addr = ifIpInfo.ip.addr; + } else if (mode & SOFTAP_MODE) { + wifi_get_ip_info(SOFTAP_IF, &ifIpInfo); + multicast_if_addr.addr = ifIpInfo.ip.addr; + } else { + return false; + } + if (igmp_joingroup(&multicast_if_addr, addr)!= ERR_OK) { + return false; + } + if(!listen(IPADDR_ANY, port)) { + return false; + } +#if LWIP_VERSION_MAJOR == 1 + udp_set_multicast_netif_addr(_pcb, multicast_if_addr); +#else + udp_set_multicast_netif_addr(_pcb, &multicast_if_addr); +#endif + udp_set_multicast_ttl(_pcb, ttl); + ip_addr_copy(_pcb->remote_ip, *addr); + _pcb->remote_port = port; + return true; +} + +bool AsyncUDP::connect(ip_addr_t *addr, uint16_t port) +{ + close(); + _pcb = udp_new(); + if(_pcb == NULL) { + return false; + } + err_t err = udp_connect(_pcb, addr, port); + if(err != ERR_OK) { + close(); + return false; + } + udp_recv(_pcb, &_s_recv, (void *) this); + _connected = true; + return true; +} + +void AsyncUDP::close() +{ + if(_pcb != NULL) { + if(_connected) { + udp_disconnect(_pcb); + } + udp_remove(_pcb); + _connected = false; + _pcb = NULL; + } +} + +size_t AsyncUDP::writeTo(const uint8_t *data, size_t len, ip_addr_t *addr, uint16_t port) +{ + if(!_pcb && !connect(addr, port)) { + return 0; + } + if(len > 1460) { + len = 1460; + } + pbuf* pbt = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM); + if(pbt != NULL) { + uint8_t* dst = reinterpret_cast(pbt->payload); + memcpy(dst, data, len); + err_t err = udp_sendto(_pcb, pbt, addr, port); + pbuf_free(pbt); + if(err < ERR_OK) { + return 0; + } + return len; + } + return 0; +} + +bool AsyncUDP::listen(const IPAddress addr, uint16_t port) +{ + ip_addr_t laddr; + laddr.addr = addr; + return listen(&laddr, port); +} + +bool AsyncUDP::listen(uint16_t port) +{ + return listen(IPAddress((uint32_t)INADDR_ANY), port); +} + +bool AsyncUDP::listenMulticast(const IPAddress addr, uint16_t port, uint8_t ttl) +{ + ip_addr_t laddr; + laddr.addr = addr; + return listenMulticast(&laddr, port, ttl); +} + +bool AsyncUDP::connect(const IPAddress addr, uint16_t port) +{ + ip_addr_t daddr; + daddr.addr = addr; + return connect(&daddr, port); +} + +size_t AsyncUDP::writeTo(const uint8_t *data, size_t len, const IPAddress addr, uint16_t port) +{ + ip_addr_t daddr; + daddr.addr = addr; + return writeTo(data, len, &daddr, port); +} + +size_t AsyncUDP::write(const uint8_t *data, size_t len) +{ + //return writeTo(data, len, &(_pcb->remote_ip), _pcb->remote_port); + if(_pcb){ // Patch applied (https://github.com/me-no-dev/ESPAsyncUDP/pull/21) + return writeTo(data, len, &(_pcb->remote_ip), _pcb->remote_port); + } + return 0; +} + +size_t AsyncUDP::write(uint8_t data) +{ + return write(&data, 1); +} + +size_t AsyncUDP::broadcastTo(uint8_t *data, size_t len, uint16_t port) +{ + ip_addr_t daddr; + daddr.addr = 0xFFFFFFFF; + return writeTo(data, len, &daddr, port); +} + +size_t AsyncUDP::broadcastTo(const char * data, uint16_t port) +{ + return broadcastTo((uint8_t *)data, strlen(data), port); +} + +size_t AsyncUDP::broadcast(uint8_t *data, size_t len) +{ + if(_pcb->local_port != 0) { + return broadcastTo(data, len, _pcb->local_port); + } + return 0; +} + +size_t AsyncUDP::broadcast(const char * data) +{ + return broadcast((uint8_t *)data, strlen(data)); +} + + +size_t AsyncUDP::sendTo(AsyncUDPMessage &message, ip_addr_t *addr, uint16_t port) +{ + if(!message) { + return 0; + } + return writeTo(message.data(), message.length(), addr, port); +} + +size_t AsyncUDP::sendTo(AsyncUDPMessage &message, const IPAddress addr, uint16_t port) +{ + //if(!message) { + if((!message) || (!_pcb)) { // Patch applied (https://github.com/me-no-dev/ESPAsyncUDP/pull/21) + return 0; + } + return writeTo(message.data(), message.length(), addr, port); +} + +size_t AsyncUDP::send(AsyncUDPMessage &message) +{ + if(!message) { + return 0; + } + return writeTo(message.data(), message.length(), &(_pcb->remote_ip), _pcb->remote_port); +} + +size_t AsyncUDP::broadcastTo(AsyncUDPMessage &message, uint16_t port) +{ + if(!message) { + return 0; + } + return broadcastTo(message.data(), message.length(), port); +} + +size_t AsyncUDP::broadcast(AsyncUDPMessage &message) +{ + if(!message) { + return 0; + } + return broadcast(message.data(), message.length()); +} diff --git a/lib/ESPAsyncUDP-master/src/ESPAsyncUDP.h b/lib/ESPAsyncUDP-master/src/ESPAsyncUDP.h new file mode 100644 index 000000000..8e5a70b21 --- /dev/null +++ b/lib/ESPAsyncUDP-master/src/ESPAsyncUDP.h @@ -0,0 +1,130 @@ +#ifndef ESPASYNCUDP_H +#define ESPASYNCUDP_H + +#include "IPAddress.h" +#include "Print.h" +#include +#include "lwip/init.h" + +class AsyncUDP; +class AsyncUDPPacket; +class AsyncUDPMessage; +struct udp_pcb; +struct pbuf; +#if LWIP_VERSION_MAJOR == 1 +struct ip_addr; +typedef struct ip_addr ip_addr_t; +#else +struct ip4_addr; +typedef struct ip4_addr ip_addr_t; +#endif + +class AsyncUDPMessage : public Print +{ +protected: + uint8_t *_buffer; + size_t _index; + size_t _size; +public: + AsyncUDPMessage(size_t size=1460); + virtual ~AsyncUDPMessage(); + size_t write(const uint8_t *data, size_t len); + size_t write(uint8_t data); + size_t space(); + uint8_t * data(); + size_t length(); + void flush(); + operator bool() + { + return _buffer != NULL; + } +}; + +class AsyncUDPPacket : public Print +{ +protected: + AsyncUDP *_udp; + ip_addr_t *_localIp; + uint16_t _localPort; + ip_addr_t *_remoteIp; + uint16_t _remotePort; + uint8_t *_data; + size_t _len; +public: + AsyncUDPPacket(AsyncUDP *udp, ip_addr_t *localIp, uint16_t localPort, ip_addr_t *remoteIp, uint16_t remotePort, uint8_t *data, size_t len); + virtual ~AsyncUDPPacket(); + + uint8_t * data(); + size_t length(); + bool isBroadcast(); + bool isMulticast(); + + IPAddress localIP(); + uint16_t localPort(); + IPAddress remoteIP(); + uint16_t remotePort(); + + size_t send(AsyncUDPMessage &message); + + size_t write(const uint8_t *data, size_t len); + size_t write(uint8_t data); +}; + +typedef std::function AuPacketHandlerFunction; +typedef std::function AuPacketHandlerFunctionWithArg; + +class AsyncUDP : public Print +{ +protected: + udp_pcb *_pcb; + bool _connected; + AuPacketHandlerFunction _handler; + + void _recv(udp_pcb *upcb, pbuf *pb, ip_addr_t *addr, uint16_t port); +#if LWIP_VERSION_MAJOR == 1 + static void _s_recv(void *arg, udp_pcb *upcb, pbuf *p, ip_addr_t *addr, uint16_t port); +#else + static void _s_recv(void *arg, udp_pcb *upcb, pbuf *p, const ip_addr_t *addr, uint16_t port); +#endif + +public: + AsyncUDP(); + virtual ~AsyncUDP(); + + void onPacket(AuPacketHandlerFunctionWithArg cb, void * arg=NULL); + void onPacket(AuPacketHandlerFunction cb); + + bool listen(ip_addr_t *addr, uint16_t port); + bool listen(const IPAddress addr, uint16_t port); + bool listen(uint16_t port); + + bool listenMulticast(ip_addr_t *addr, uint16_t port, uint8_t ttl=1); + bool listenMulticast(const IPAddress addr, uint16_t port, uint8_t ttl=1); + + bool connect(ip_addr_t *addr, uint16_t port); + bool connect(const IPAddress addr, uint16_t port); + + void close(); + + size_t writeTo(const uint8_t *data, size_t len, ip_addr_t *addr, uint16_t port); + size_t writeTo(const uint8_t *data, size_t len, const IPAddress addr, uint16_t port); + size_t write(const uint8_t *data, size_t len); + size_t write(uint8_t data); + + size_t broadcastTo(uint8_t *data, size_t len, uint16_t port); + size_t broadcastTo(const char * data, uint16_t port); + size_t broadcast(uint8_t *data, size_t len); + size_t broadcast(const char * data); + + size_t sendTo(AsyncUDPMessage &message, ip_addr_t *addr, uint16_t port); + size_t sendTo(AsyncUDPMessage &message, const IPAddress addr, uint16_t port); + size_t send(AsyncUDPMessage &message); + + size_t broadcastTo(AsyncUDPMessage &message, uint16_t port); + size_t broadcast(AsyncUDPMessage &message); + + bool connected(); + operator bool(); +}; + +#endif diff --git a/lib/ESPAsyncUDP-master/travis/common.sh b/lib/ESPAsyncUDP-master/travis/common.sh new file mode 100644 index 000000000..57bede343 --- /dev/null +++ b/lib/ESPAsyncUDP-master/travis/common.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +function build_sketches() +{ + local arduino=$1 + local srcpath=$2 + local platform=$3 + local sketches=$(find $srcpath -name *.ino) + for sketch in $sketches; do + local sketchdir=$(dirname $sketch) + if [[ -f "$sketchdir/.$platform.skip" ]]; then + echo -e "\n\n ------------ Skipping $sketch ------------ \n\n"; + continue + fi + echo -e "\n\n ------------ Building $sketch ------------ \n\n"; + $arduino --verify $sketch; + local result=$? + if [ $result -ne 0 ]; then + echo "Build failed ($1)" + return $result + fi + done +} diff --git a/lib/esp-knx-ip-0.5.0/DPT.h b/lib/esp-knx-ip-0.5.1/DPT.h similarity index 99% rename from lib/esp-knx-ip-0.5.0/DPT.h rename to lib/esp-knx-ip-0.5.1/DPT.h index 72e0c7f54..71045b103 100644 --- a/lib/esp-knx-ip-0.5.0/DPT.h +++ b/lib/esp-knx-ip-0.5.1/DPT.h @@ -69,4 +69,4 @@ typedef struct __color uint8_t red; uint8_t green; uint8_t blue; -} color_t; \ No newline at end of file +} color_t; diff --git a/lib/esp-knx-ip-0.5.0/LICENSE b/lib/esp-knx-ip-0.5.1/LICENSE similarity index 100% rename from lib/esp-knx-ip-0.5.0/LICENSE rename to lib/esp-knx-ip-0.5.1/LICENSE diff --git a/lib/esp-knx-ip-0.5.0/README.md b/lib/esp-knx-ip-0.5.1/README.md similarity index 100% rename from lib/esp-knx-ip-0.5.0/README.md rename to lib/esp-knx-ip-0.5.1/README.md diff --git a/lib/esp-knx-ip-0.5.0/esp-knx-ip-config.cpp b/lib/esp-knx-ip-0.5.1/esp-knx-ip-config.cpp similarity index 100% rename from lib/esp-knx-ip-0.5.0/esp-knx-ip-config.cpp rename to lib/esp-knx-ip-0.5.1/esp-knx-ip-config.cpp diff --git a/lib/esp-knx-ip-0.5.0/esp-knx-ip-conversion.cpp b/lib/esp-knx-ip-0.5.1/esp-knx-ip-conversion.cpp similarity index 100% rename from lib/esp-knx-ip-0.5.0/esp-knx-ip-conversion.cpp rename to lib/esp-knx-ip-0.5.1/esp-knx-ip-conversion.cpp diff --git a/lib/esp-knx-ip-0.5.0/esp-knx-ip-send.cpp b/lib/esp-knx-ip-0.5.1/esp-knx-ip-send.cpp similarity index 88% rename from lib/esp-knx-ip-0.5.0/esp-knx-ip-send.cpp rename to lib/esp-knx-ip-0.5.1/esp-knx-ip-send.cpp index 72459f922..624e08d18 100644 --- a/lib/esp-knx-ip-0.5.0/esp-knx-ip-send.cpp +++ b/lib/esp-knx-ip-0.5.1/esp-knx-ip-send.cpp @@ -33,10 +33,11 @@ void ESPKNXIP::send(address_t const &receiver, knx_command_type_t ct, uint8_t da cemi_msg->additional_info_len = 0; cemi_service_t *cemi_data = &cemi_msg->data.service_information; cemi_data->control_1.bits.confirm = 0; - cemi_data->control_1.bits.ack = 0; +//cemi_data->control_1.bits.ack = 1; + cemi_data->control_1.bits.ack = 0; // ask for ACK? 0-no 1-yes cemi_data->control_1.bits.priority = B11; cemi_data->control_1.bits.system_broadcast = 0x01; - cemi_data->control_1.bits.repeat = 0x01; + cemi_data->control_1.bits.repeat = 0x01; // 0 = repeated telegram, 1 = not repeated telegram cemi_data->control_1.bits.reserved = 0; cemi_data->control_1.bits.frame_type = 0x01; cemi_data->control_2.bits.extended_frame_format = 0x00; @@ -47,10 +48,13 @@ void ESPKNXIP::send(address_t const &receiver, knx_command_type_t ct, uint8_t da //cemi_data->destination.bytes.high = (area << 3) | line; //cemi_data->destination.bytes.low = member; cemi_data->data_len = data_len; - cemi_data->pci.apci = (ct & 0x0C) >> 2; - cemi_data->pci.tpci_seq_number = 0x00; // ??? - cemi_data->pci.tpci_comm_type = KNX_COT_UDP; // ??? + cemi_data->pci.apci = (ct & 0x0C) >> 2; +//cemi_data->pci.apci = KNX_COT_NCD_ACK; + cemi_data->pci.tpci_seq_number = 0x00; + cemi_data->pci.tpci_comm_type = KNX_COT_UDP; // Type of communication: DATA PACKAGE or CONTROL DATA +//cemi_data->pci.tpci_comm_type = KNX_COT_NCD; // Type of communication: DATA PACKAGE or CONTROL DATA memcpy(cemi_data->data, data, data_len); +//cemi_data->data[0] = (cemi_data->data[0] & 0x3F) | ((KNX_COT_NCD_ACK & 0x03) << 6); cemi_data->data[0] = (cemi_data->data[0] & 0x3F) | ((ct & 0x03) << 6); #if SEND_CHECKSUM @@ -73,9 +77,13 @@ void ESPKNXIP::send(address_t const &receiver, knx_command_type_t ct, uint8_t da DEBUG_PRINTLN(F("")); #endif +#ifdef USE_ASYNC_UDP + udp.writeTo(buf, len, MULTICAST_IP, MULTICAST_PORT); +#else udp.beginPacketMulticast(MULTICAST_IP, MULTICAST_PORT, WiFi.localIP()); udp.write(buf, len); udp.endPacket(); +#endif } void ESPKNXIP::send_1bit(address_t const &receiver, knx_command_type_t ct, uint8_t bit) diff --git a/lib/esp-knx-ip-0.5.0/esp-knx-ip-webserver.cpp b/lib/esp-knx-ip-0.5.1/esp-knx-ip-webserver.cpp similarity index 100% rename from lib/esp-knx-ip-0.5.0/esp-knx-ip-webserver.cpp rename to lib/esp-knx-ip-0.5.1/esp-knx-ip-webserver.cpp diff --git a/lib/esp-knx-ip-0.5.0/esp-knx-ip.cpp b/lib/esp-knx-ip-0.5.1/esp-knx-ip.cpp similarity index 97% rename from lib/esp-knx-ip-0.5.0/esp-knx-ip.cpp rename to lib/esp-knx-ip-0.5.1/esp-knx-ip.cpp index b572333b9..767fa9752 100644 --- a/lib/esp-knx-ip-0.5.0/esp-knx-ip.cpp +++ b/lib/esp-knx-ip-0.5.1/esp-knx-ip.cpp @@ -96,7 +96,12 @@ void ESPKNXIP::__start() server->begin(); } +#ifdef USE_ASYNC_UDP + udp.listenMulticast(MULTICAST_IP, MULTICAST_PORT); + udp.onPacket([this](AsyncUDPPacket &packet) { __loop_knx(packet); }); +#else udp.beginMulticast(WiFi.localIP(), MULTICAST_IP, MULTICAST_PORT); +#endif } void ESPKNXIP::save_to_eeprom() @@ -511,7 +516,9 @@ feedback_id_t ESPKNXIP::feedback_register_action(String name, feedback_action_fp void ESPKNXIP::loop() { + #ifndef USE_ASYNC_UDP __loop_knx(); + #endif if (server != nullptr) { __loop_webserver(); @@ -523,9 +530,16 @@ void ESPKNXIP::__loop_webserver() server->handleClient(); } +#ifdef USE_ASYNC_UDP +void ESPKNXIP::__loop_knx(AsyncUDPPacket &packet) +{ + size_t read = packet.length(); +#else void ESPKNXIP::__loop_knx() { int read = udp.parsePacket(); +#endif + if (!read) { return; @@ -534,19 +548,31 @@ void ESPKNXIP::__loop_knx() DEBUG_PRINT(F("LEN: ")); DEBUG_PRINTLN(read); +#ifdef USE_ASYNC_UDP + uint8_t *buf = packet.data(); +#else uint8_t buf[read]; - udp.read(buf, read); udp.flush(); +#endif DEBUG_PRINT(F("Got packet:")); + #ifdef ESP_KNX_DEBUG + +#ifdef USE_ASYNC_UDP + for (size_t i = 0; i < read; ++i) +#else for (int i = 0; i < read; ++i) +#endif + { DEBUG_PRINT(F(" 0x")); DEBUG_PRINT(buf[i], 16); } + #endif + DEBUG_PRINTLN(F("")); knx_ip_pkt_t *knx_pkt = (knx_ip_pkt_t *)buf; diff --git a/lib/esp-knx-ip-0.5.0/esp-knx-ip.h b/lib/esp-knx-ip-0.5.1/esp-knx-ip.h similarity index 93% rename from lib/esp-knx-ip-0.5.0/esp-knx-ip.h rename to lib/esp-knx-ip-0.5.1/esp-knx-ip.h index 538264b3e..d8fde1dc9 100644 --- a/lib/esp-knx-ip-0.5.0/esp-knx-ip.h +++ b/lib/esp-knx-ip-0.5.1/esp-knx-ip.h @@ -7,6 +7,14 @@ #ifndef ESP_KNX_IP_H #define ESP_KNX_IP_H +//#define USE_ASYNC_UDP // UDP WIFI Library Selection for Multicast + // If commented out, the esp-knx-ip library will use WIFI_UDP Library that is compatible with ESP8266 Library Version 2.3.0 and up + // If not commented out, the esp-knx-ip library will use ESPAsyncUDP Library that is compatible with ESP8266 Library Version 2.4.0 and up + // The ESPAsyncUDP Library have a more reliable multicast communication + // Please Use it with Patch (https://github.com/me-no-dev/ESPAsyncUDP/pull/21) ) + // check line 57 on esp-knx-ip.h file is uncommented: #include + // Comment out that line when using UDP WIFI to avoid compiling issues on PlatformIO with ESP8266 Library Version 2.3.0 + /** * CONFIG * All MAX_ values must not exceed 255 (1 byte, except MAC_CONFIG_SPACE which can go up to 2 bytes, so 0xffff in theory) and must not be negative! @@ -25,8 +33,8 @@ #define ALLOW_MULTIPLE_CALLBACKS_PER_ADDRESS 1 // [Default 0] Set to 1 to always test all assigned callbacks. This allows for multiple callbacks being assigned to the same address. If disabled, only the first assigned will be called. // Webserver related -#define USE_BOOTSTRAP 1 // [Default 1] Set to 1 to enable use of bootstrap CSS for nicer webconfig. CSS is loaded from bootstrapcdn.com. Set to 0 to disable -#define ROOT_PREFIX "" // [Default ""] This gets prepended to all webserver paths, default is empty string "". Set this to "/knx" if you want the config to be available on http:///knx +#define USE_BOOTSTRAP 0 // [Default 1] Set to 1 to enable use of bootstrap CSS for nicer webconfig. CSS is loaded from bootstrapcdn.com. Set to 0 to disable +#define ROOT_PREFIX "/knx" // [Default ""] This gets prepended to all webserver paths, default is empty string "". Set this to "/knx" if you want the config to be available on http:///knx #define DISABLE_EEPROM_BUTTONS 1 // [Default 0] Set to 1 to disable the EEPROM buttons in the web ui. #define DISABLE_REBOOT_BUTTON 1 // [Default 0] Set to 1 to disable the reboot button in the web ui. #define DISABLE_RESTORE_BUTTON 1 // [Default 0] Set to 1 to disable the "restore defaults" button in the web ui. @@ -45,7 +53,13 @@ #include "Arduino.h" #include #include + +#ifdef USE_ASYNC_UDP +//#include +#else #include +#endif + #include #include "DPT.h" @@ -157,6 +171,14 @@ typedef enum __knx_communication_type { KNX_COT_NCD = 0x03, // Numbered Control Data } knx_communication_type_t; +/** + * acpi for KNX_COT_NCD + */ +typedef enum __knx_cot_ncd_ack_type { + KNX_COT_NCD_ACK = 0x10, // Inform positively reception of the Previouly received telegram + KNX_COT_NCD_NACK = 0x11, // Inform negatively reception of the Previouly received telegram +} knx_cot_ncd_ack_type_t; + /** * KNX/IP header */ @@ -217,7 +239,7 @@ typedef struct __cemi_service uint8_t ack:1; // 0 = no ack, 1 = ack uint8_t priority:2; // 0 = system, 1 = high, 2 = urgent/alarm, 3 = normal uint8_t system_broadcast:1; // 0 = system broadcast, 1 = broadcast - uint8_t repeat:1; // 0 = repeat on error, 1 = do not repeat + uint8_t repeat:1; // 0 = repeated telegram, 1 = not repeated telegram uint8_t reserved:1; // always zero uint8_t frame_type:1; // 0 = extended, 1 = standard } bits; @@ -509,7 +531,12 @@ class ESPKNXIP { private: void __start(); + +#ifdef USE_ASYNC_UDP + void __loop_knx(AsyncUDPPacket &packet); +#else void __loop_knx(); +#endif // Webserver functions void __loop_webserver(); @@ -544,7 +571,12 @@ class ESPKNXIP { ESP8266WebServer *server; address_t physaddr; + +#ifdef USE_ASYNC_UDP + AsyncUDP udp; +#else WiFiUDP udp; +#endif callback_assignment_id_t registered_callback_assignments; callback_assignment_id_t free_callback_assignment_slots; diff --git a/lib/esp-knx-ip-0.5.0/examples/environment-sensor/environment-sensor.ino b/lib/esp-knx-ip-0.5.1/examples/environment-sensor/environment-sensor.ino similarity index 100% rename from lib/esp-knx-ip-0.5.0/examples/environment-sensor/environment-sensor.ino rename to lib/esp-knx-ip-0.5.1/examples/environment-sensor/environment-sensor.ino diff --git a/lib/esp-knx-ip-0.5.0/examples/sonoff/sonoff.ino b/lib/esp-knx-ip-0.5.1/examples/sonoff/sonoff.ino similarity index 100% rename from lib/esp-knx-ip-0.5.0/examples/sonoff/sonoff.ino rename to lib/esp-knx-ip-0.5.1/examples/sonoff/sonoff.ino diff --git a/lib/esp-knx-ip-0.5.0/examples/static-config/static-config.ino b/lib/esp-knx-ip-0.5.1/examples/static-config/static-config.ino similarity index 100% rename from lib/esp-knx-ip-0.5.0/examples/static-config/static-config.ino rename to lib/esp-knx-ip-0.5.1/examples/static-config/static-config.ino diff --git a/lib/esp-knx-ip-0.5.0/keywords.txt b/lib/esp-knx-ip-0.5.1/keywords.txt similarity index 100% rename from lib/esp-knx-ip-0.5.0/keywords.txt rename to lib/esp-knx-ip-0.5.1/keywords.txt diff --git a/lib/esp-knx-ip-0.5.0/library.properties b/lib/esp-knx-ip-0.5.1/library.properties similarity index 95% rename from lib/esp-knx-ip-0.5.0/library.properties rename to lib/esp-knx-ip-0.5.1/library.properties index 57cad993c..1adbc402a 100644 --- a/lib/esp-knx-ip-0.5.0/library.properties +++ b/lib/esp-knx-ip-0.5.1/library.properties @@ -1,5 +1,5 @@ name=ESP KNX IP Library -version=0.5 +version=0.5.1 author=Nico Weichbrodt maintainer=Nico Weichbrodt sentence=ESP8266 library for KNX/IP communication. diff --git a/lib/process-control-master/LICENSE b/lib/process-control-master/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/lib/process-control-master/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/lib/process-control-master/PID.cpp b/lib/process-control-master/PID.cpp new file mode 100644 index 000000000..b19cfa64c --- /dev/null +++ b/lib/process-control-master/PID.cpp @@ -0,0 +1,168 @@ +/** + * 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; +} + +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; + unsigned char integral_locked = 0; + 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) { + integral_locked = 0; + 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; + } + integral_locked = 1; + } + } + + } 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; + power = -1.0/m_prop_band * (proportional + m_integral + m_derivative) + 0.5; + if (power < 0.0) { + power = 0.0; + } else if (power > 1.0) { + power = 1.0; + } + // 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; + } + 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; +} diff --git a/lib/process-control-master/PID.h b/lib/process-control-master/PID.h new file mode 100644 index 000000000..782baa8ef --- /dev/null +++ b/lib/process-control-master/PID.h @@ -0,0 +1,89 @@ +/** + * 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; + + + 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 diff --git a/lib/process-control-master/README.md b/lib/process-control-master/README.md new file mode 100644 index 000000000..4682d2f3f --- /dev/null +++ b/lib/process-control-master/README.md @@ -0,0 +1,2 @@ +# process-control +A C++ library of process control algorithms diff --git a/lib/process-control-master/Timeprop.cpp b/lib/process-control-master/Timeprop.cpp new file mode 100644 index 000000000..c4d5e9eb8 --- /dev/null +++ b/lib/process-control-master/Timeprop.cpp @@ -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; +} diff --git a/lib/process-control-master/Timeprop.h b/lib/process-control-master/Timeprop.h new file mode 100644 index 000000000..c6df45be0 --- /dev/null +++ b/lib/process-control-master/Timeprop.h @@ -0,0 +1,85 @@ +/** + * 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 class to generate a time proportioned digital output from a linear input + * + * Github repository https://github.com/colinl/process-control.git + * + * Given a required power value in the range 0.0 to 1.0 this class generates + * a time proportioned 0/1 output (representing OFF/ON) which averages to the + * required power value. The cycle time is configurable. If, for example, this + * is set to 10 minutes and the power input is 0.2 then the output will be on + * for two minutes in every ten minutes. + * + * A value for actuator dead time may be provided. If you have a device that + * takes a significant time to open/close then set this to the average of the + * open and close times. The algorithim will then adjust the output timing + * accordingly to ensure that the output is not switched more rapidly than + * the actuator can cope with. + * + * A facility to invert the output is provided which can be useful when used in + * refrigeration processes and similar. + * + * Usage: + * First call initialise(), see below for parameters then call setPower() to + * specify the current power required. + * Then regularly call tick() to determine the output state required. + * setPower may be called as often as required to change the power required. + * 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 Timeprop_h +#define Timeprop_h + +class Timeprop { +public: + /* + Initialiser given + cycleTime seconds + actuator deadTime seconds + whether to invert the output + fallback power value if updates are not received within time below + max number of seconds to allow between power updates before falling back to default power (0 to disable) + current time in seconds + */ + void initialise( int cycleTime, int deadTime, unsigned char invert, float fallbackPower, int maxUpdateInterval, + unsigned long nowSecs); + + /* set current power required 0:1, given power and current time in seconds */ + void setPower( float power, unsigned long nowSecs ); + + /* called regularly to provide new output value */ + /* returns new o/p state 0, 1 */ + int tick(unsigned long nowSecs); + +private: + int m_cycleTime; // cycle time seconds, float to force float calcs + int m_deadTime; // actuator action time seconds + unsigned char m_invert; // whether to invert the output + float m_dtoc; // deadTime/m_cycleTime + int m_opState; // current output state (before invert) + float m_power; // required power 0:1 + float m_fallbackPower; // falls back to this if updates not received with max allowed timezone + int m_maxUpdateInterval; // max time between updates + unsigned long m_lastPowerUpdateTime; // the time of last power update secs +}; + +#endif // Timeprop_h diff --git a/sonoff/user_config.h b/sonoff/user_config.h index 5a4f077e2..2ccbc00f5 100644 --- a/sonoff/user_config.h +++ b/sonoff/user_config.h @@ -309,6 +309,10 @@ //#define USE_SR04 // Add support for HC-SR04 ultrasonic devices (+1k code) +/*********************************************************************************************\ + * DISPLAY +\*********************************************************************************************/ + //#define USE_DISPLAY // Add I2C Display Support for LCD, Oled and up to eigth Matrices (+19k code) //#define DISPLAY_CONFIG // Display Support for LCD, Oled @@ -334,6 +338,167 @@ #endif // DISPLAY_CONFIG +/*********************************************************************************************\ + * TIME PROPORTIONAL CONTROLLER +\*********************************************************************************************/ + +/** + * Code to drive one or more relays in a time proportioned manner give a + * required power value. + * + * Given required power values in the range 0.0 to 1.0 the relays will be + * driven on/off in such that the average power suppled will represent + * the required power. + * The cycle time is configurable. If, for example, the + * period is set to 10 minutes and the power input is 0.2 then the output will + * be on for two minutes in every ten minutes. + * + * A value for actuator dead time may be provided. If you have a device that + * takes a significant time to open/close then set this to the average of the + * open and close times. The algorithim will then adjust the output timing + * accordingly to ensure that the output is not switched more rapidly than + * the actuator can cope with. + * + * A facility to invert the output is provided which can be useful when used in + * refrigeration processes and similar. + * + * In the case where only one relay is being driven the power value is set by + * writing the value to the mqtt topic cmnd/timeprop_setpower_0. If more than + * one relay is being driven (as might be the case for a heat/cool application + * where one relay drives the heater and the other the cooler) then the power + * for the second relay is written to topic cmnd/timeprop_setpower_1 and so on. + * + * To cope with the problem of temporary wifi failure etc a + * TIMEPROP_MAX_UPDATE_INTERVALS value is available. This can be set to the max + * expected time between power updates and if this time is exceeded then the + * power will fallback to a given safe value until a new value is provided. Set + * the interval to 0 to disable this feature. + * + **/ + +//#define USE_TIMEPROP // include the timeprop feature (+1.2k) + +// Configuration for single output +/* + #define TIMEPROP_NUM_OUTPUTS 1 // how many outputs to control (with separate alogorithm for each) + #define TIMEPROP_CYCLETIMES 60 // cycle time seconds + #define TIMEPROP_DEADTIMES 0 // actuator action time seconds + #define TIMEPROP_OPINVERTS false // whether to invert the output + #define TIMEPROP_FALLBACK_POWERS 0 // falls back to this if too long betwen power updates + #define TIMEPROP_MAX_UPDATE_INTERVALS 120 // max no secs that are allowed between power updates (0 to disable) + #define TIMEPROP_RELAYS 1 // which relay to control 1:8 +/* + +// Configuration for two outputs: +/* + #define TIMEPROP_NUM_OUTPUTS 2 // how many outputs to control (with separate alogorithm for each) + #define TIMEPROP_CYCLETIMES 60, 10 // cycle time seconds + #define TIMEPROP_DEADTIMES 0, 0 // actuator action time seconds + #define TIMEPROP_OPINVERTS false, false // whether to invert the output + #define TIMEPROP_FALLBACK_POWERS 0, 0 // falls back to this if too long betwen power updates + #define TIMEPROP_MAX_UPDATE_INTERVALS 120, 120 // max no secs that are allowed between power updates (0 to disable) + #define TIMEPROP_RELAYS 1, 2 // which relay to control 1:8 +*/ + +/*********************************************************************************************\ + * PID CONTROLLER +\*********************************************************************************************/ + +// Help with using the PID algorithm and with loop tuning can be found at +// http://blog.clanlaw.org.uk/2018/01/09/PID-tuning-with-node-red-contrib-pid.html +// This is directed towards using the algorithm in the node-red node node-red-contrib-pid but the algorithm here is based on +// the code there and the tuning techique described there should work just the same. + +//#define USE_PID // include the pid feature (+4.3k) + #define PID_SETPOINT 19.5 // Setpoint value. This is the process value that the process is + // aiming for. + // May be adjusted via MQTT using cmnd pid_sp + + #define PID_PROPBAND 5 // Proportional band in process units (eg degrees). This controls + // the gain of the loop and is the range of process value over which + // the power output will go from 0 to full power. The units are that + // of the process and setpoint, so for example in a heating + // application it might be set to 1.5 degrees. + // May be adjusted via MQTT using cmnd pid_pb + + #define PID_INTEGRAL_TIME 1800 // Integral time seconds. This is a setting for the integral time, + // in seconds. It represents the time constant of the integration + // effect. The larger the value the slower the integral effect will be. + // Obviously the slower the process is the larger this should be. For + // example for a domestic room heated by convection radiators a setting + // of one hour might be appropriate (in seconds). To disable the + // integral effect set this to a large number. + // May be adjusted via MQTT using cmnd pid_ti + + #define PID_DERIVATIVE_TIME 15 // Derivative time seconds. This is a setting for the derivative time, + // in seconds. It represents the time constant of the derivative effect. + // The larger the value the greater will be the derivative effect. + // Typically this will be set to somewhat less than 25% of the integral + // setting, once the integral has been adjusted to the optimum value. To + // disable the derivative effect set this to 0. When initially tuning a + // loop it is often sensible to start with derivative zero and wind it in + // once other parameters have been setup. + // May be adjusted via MQTT using cmnd pid_td + + #define PID_INITIAL_INT 0.5 // Initial integral value (0:1). This is an initial value which is used + // to preset the integrated error value when the flow is deployed in + // order to assist in homing in on the setpoint the first time. It should + // be set to an estimate of what the power requirement might be in order + // to maintain the process at the setpoint. For example for a domestic + // room heating application it might be set to 0.2 indicating that 20% of + // the available power might be required to maintain the setpoint. The + // value is of no consequence apart from device restart. + + #define PID_MAX_INTERVAL 300 // This is the maximum time in seconds that is expected between samples. + // It is provided to cope with unusual situations such as a faulty sensor + // that might prevent the node from being supplied with a process value. + // If no new process value is received for this time then the power is set + // to the value defined for PID_MANUAL_POWER. + // May be adjusted via MQTT using cmnd pid_max_interval + + #define PID_DERIV_SMOOTH_FACTOR 3 // In situations where the process sensor has limited resolution (such as + // the DS18B20), the use of deriviative can be problematic as when the + // process is changing only slowly the steps in the value cause spikes in + // the derivative. To reduce the effect of these this parameter can be + // set to apply a filter to the derivative term. I have found that with + // the DS18B20 that a value of 3 here can be beneficial, providing + // effectively a low pass filter on the derivative at 1/3 of the derivative + // time. This feature may also be useful if the process value is particularly + // noisy. The smaller the value the greater the filtering effect but the + // more it will reduce the effectiveness of the derivative. A value of zero + // disables this feature. + // May be adjusted via MQTT using cmnd pid_d_smooth + + #define PID_AUTO 1 // Auto mode 1 or 0 (for manual). This can be used to enable or disable + // the control (1=enable, auto mode, 0=disabled, manual mode). When in + // manual mode the output is set the value definded for PID_MANUAL_POWER + // May be adjusted via MQTT using cmnd pid_auto + + #define PID_MANUAL_POWER 0 // Power output when in manual mode or fallback mode if too long elapses + // between process values + // May be adjusted via MQTT using cmnd pid_manual_power + + #define PID_UPDATE_SECS 0 // How often to run the pid algorithm (integer secs) or 0 to run the algorithm + // each time a new pv value is received, for most applictions specify 0. + // Otherwise set this to a time + // that is short compared to the response of the process. For example, + // something like 15 seconds may well be appropriate for a domestic room + // heating application. + // May be adjusted via MQTT using cmnd pid_update_secs + + #define PID_USE_TIMPROP 1 // To use an internal relay for a time proportioned output to drive the + // process, set this to indicate which timeprop output to use. For a device + // with just one relay then this will be 1. + // It is then also necessary to define USE_TIMEPROP and set the output up as + // explained in xdrv_91_timeprop.ino + // To disable this feature leave this undefined (undefined, not defined to nothing). + + #define PID_USE_LOCAL_SENSOR // if defined then the local sensor will be used for pv. Leave undefined if + // this is not required. The rate that the sensor is read is defined by TELE_PERIOD + // If not using the sensor then you can supply process values via MQTT using + // cmnd pid_pv + + /*********************************************************************************************\ * Select features and sensors enabled in previous version saving space \*********************************************************************************************/ @@ -350,7 +515,7 @@ * Select KNX without Emulation to save space \*********************************************************************************************/ -#define USE_KNX_NO_EMULATION // Create sonoff-knx with KNX but without Emulation (See sonoff_post.h) +//#define USE_KNX_NO_EMULATION // Create sonoff-knx with KNX but without Emulation (See sonoff_post.h) /*********************************************************************************************\ * Compile a minimal version if upgrade memory gets tight ONLY TO BE USED FOR UPGRADE STEP 1! diff --git a/sonoff/xdrv_11_knx.ino b/sonoff/xdrv_11_knx.ino index 84bd9de23..038c224d9 100644 --- a/sonoff/xdrv_11_knx.ino +++ b/sonoff/xdrv_11_knx.ino @@ -48,11 +48,14 @@ byte Settings.knx_CB_param[MAX_KNX_CB] Type of Output (set relay, t \*********************************************************************************************/ -#include +#include // KNX Library + // Note: Inside the file there is a //#define USE_ASYNC_UDP // UDP WIFI Library Selection for Multicast + // If commented out, the esp-knx-ip library will use WIFI_UDP Library that is compatible with ESP8266 Library Version 2.3.0 and up + // If not commented out, the esp-knx-ip library will use ESPAsyncUDP Library that is compatible with ESP8266 Library Version 2.4.0 and up + // The ESPAsyncUDP Library have a more reliable multicast communication + // Please Use it with Patch (https://github.com/me-no-dev/ESPAsyncUDP/pull/21) ) -//#include - -//void KNX_CB_Action(message_t const &msg, void *arg); // Define function (action callback) to be called by the KNX_IP Library +//void KNX_CB_Action(message_t const &msg, void *arg); // Define function (action callback) to be called by the Esp-KNX-IP Library // when an action is requested by another KNX Device address_t KNX_physs_addr; // Physical KNX address of this device @@ -970,4 +973,4 @@ boolean Xdrv11(byte function) return result; } -#endif // USE_KNX \ No newline at end of file +#endif // USE_KNX diff --git a/sonoff/xdrv_91_timeprop.ino b/sonoff/xdrv_91_timeprop.ino new file mode 100644 index 000000000..c14aa0b23 --- /dev/null +++ b/sonoff/xdrv_91_timeprop.ino @@ -0,0 +1,220 @@ +/* + xdrv_91_timeprop.ino - Timeprop support for Sonoff-Tasmota + Copyright (C) 2018 Colin Law and Thomas Herrmann + 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 . +*/ + +/** + * Code to drive one or more relays in a time proportioned manner give a + * required power value. + * + * Given required power values in the range 0.0 to 1.0 the relays will be + * driven on/off in such that the average power suppled will represent + * the required power. + * The cycle time is configurable. If, for example, the + * period is set to 10 minutes and the power input is 0.2 then the output will + * be on for two minutes in every ten minutes. + * + * A value for actuator dead time may be provided. If you have a device that + * takes a significant time to open/close then set this to the average of the + * open and close times. The algorithim will then adjust the output timing + * accordingly to ensure that the output is not switched more rapidly than + * the actuator can cope with. + * + * A facility to invert the output is provided which can be useful when used in + * refrigeration processes and similar. + * + * In the case where only one relay is being driven the power value is set by + * writing the value to the mqtt topic cmnd/timeprop_setpower_0. If more than + * one relay is being driven (as might be the case for a heat/cool application + * where one relay drives the heater and the other the cooler) then the power + * for the second relay is written to topic cmnd/timeprop_setpower_1 and so on. + * + * To cope with the problem of temporary wifi failure etc a + * TIMEPROP_MAX_UPDATE_INTERVALS value is available. This can be set to the max + * expected time between power updates and if this time is exceeded then the + * power will fallback to a given safe value until a new value is provided. Set + * the interval to 0 to disable this feature. + * + * Usage: + * Place this file in the sonoff folder. + * Clone the library https://github.com/colinl/process-control.git from Github + * into a subfolder of lib. + * In user_config.h or user_config_override.h for a single relay, include + * code as follows: + + #define USE_TIMEPROP // include the timeprop feature (+1.2k) + // for single output + #define TIMEPROP_NUM_OUTPUTS 1 // how many outputs to control (with separate alogorithm for each) + #define TIMEPROP_CYCLETIMES 60 // cycle time seconds + #define TIMEPROP_DEADTIMES 0 // actuator action time seconds + #define TIMEPROP_OPINVERTS false // whether to invert the output + #define TIMEPROP_FALLBACK_POWERS 0 // falls back to this if too long betwen power updates + #define TIMEPROP_MAX_UPDATE_INTERVALS 120 // max no secs that are allowed between power updates (0 to disable) + #define TIMEPROP_RELAYS 1 // which relay to control 1:8 + + * or for two relays: + #define USE_TIMEPROP // include the timeprop feature (+1.2k) + // for single output + #define TIMEPROP_NUM_OUTPUTS 2 // how many outputs to control (with separate alogorithm for each) + #define TIMEPROP_CYCLETIMES 60, 10 // cycle time seconds + #define TIMEPROP_DEADTIMES 0, 0 // actuator action time seconds + #define TIMEPROP_OPINVERTS false, false // whether to invert the output + #define TIMEPROP_FALLBACK_POWERS 0, 0 // falls back to this if too long betwen power updates + #define TIMEPROP_MAX_UPDATE_INTERVALS 120, 120 // max no secs that are allowed between power updates (0 to disable) + #define TIMEPROP_RELAYS 1, 2 // which relay to control 1:8 + + * Publish values between 0 and 1 to the topic(s) described above + * +**/ + + +#ifdef USE_TIMEPROP + +# include "Timeprop.h" + +#define D_CMND_TIMEPROP "timeprop_" +#define D_CMND_TIMEPROP_SETPOWER "setpower_" // add index no on end (0:8) and data is power 0:1 + +enum TimepropCommands { CMND_TIMEPROP_SETPOWER }; +const char kTimepropCommands[] PROGMEM = D_CMND_TIMEPROP_SETPOWER; + +static Timeprop timeprops[TIMEPROP_NUM_OUTPUTS]; +static int relayNos[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_RELAYS}; +static long currentRelayStates = 0; // current actual relay states. Bit 0 first relay + +/* call this from elsewhere if required to set the power value for one of the timeprop instances */ +/* index specifies which one, 0 up */ +void Timeprop_Set_Power( int index, float power ) +{ + if (index >= 0 && index < TIMEPROP_NUM_OUTPUTS) + { + timeprops[index].setPower( power, utc_time); + } +} + +void Timeprop_Init() +{ + snprintf_P(log_data, sizeof(log_data), "Timeprop Init"); + AddLog(LOG_LEVEL_INFO); + int cycleTimes[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_CYCLETIMES}; + int deadTimes[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_DEADTIMES}; + int opInverts[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_OPINVERTS}; + int fallbacks[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_FALLBACK_POWERS}; + int maxIntervals[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_MAX_UPDATE_INTERVALS}; + + for (int i=0; i= 0 ? XdrvMailbox.topic : ""), + (XdrvMailbox.data_len >= 0 ? XdrvMailbox.data : "")); + + AddLog(LOG_LEVEL_INFO); + */ + if (0 == strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_TIMEPROP), ua_prefix_len)) { + // command starts with timeprop_ + int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + ua_prefix_len, kTimepropCommands); + if (CMND_TIMEPROP_SETPOWER == command_code) { + /* + snprintf_P(log_data, sizeof(log_data), "Timeprop command timeprop_setpower: " + "index: %d data_len: %d payload: %d topic: %s data: %s", + XdrvMailbox.index, + XdrvMailbox.data_len, + XdrvMailbox.payload, + (XdrvMailbox.payload >= 0 ? XdrvMailbox.topic : ""), + (XdrvMailbox.data_len >= 0 ? XdrvMailbox.data : "")); + AddLog(LOG_LEVEL_INFO); + */ + if (XdrvMailbox.index >=0 && XdrvMailbox.index < TIMEPROP_NUM_OUTPUTS) { + timeprops[XdrvMailbox.index].setPower( atof(XdrvMailbox.data), utc_time ); + } + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_TIMEPROP D_CMND_TIMEPROP_SETPOWER "%d\":\"%s\"}"), + XdrvMailbox.index, XdrvMailbox.data); + } + else { + serviced = false; + } + } else { + serviced = false; + } + return serviced; +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +#define XDRV_91 + +boolean Xdrv91(byte function) +{ + boolean result = false; + + switch (function) { + case FUNC_INIT: + Timeprop_Init(); + break; + case FUNC_EVERY_SECOND: + Timeprop_Every_Second(); + break; + case FUNC_COMMAND: + result = Timeprop_Command(); + break; + case FUNC_SET_POWER: + Timeprop_Xdrv_Power(); + break; + } + return result; +} + +#endif // USE_TIMEPROP diff --git a/sonoff/xdrv_92_pid.ino b/sonoff/xdrv_92_pid.ino new file mode 100644 index 000000000..163944e40 --- /dev/null +++ b/sonoff/xdrv_92_pid.ino @@ -0,0 +1,374 @@ +/* + xdrv_92_pid.ino - PID algorithm plugin for Sonoff-Tasmota + Copyright (C) 2018 Colin Law and Thomas Herrmann + 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 . +*/ + +/** + * Code to + * + * Usage: + * Place this file in the sonoff folder. + * Clone the library https://github.com/colinl/process-control.git from Github + * into a subfolder of lib. + * If you want to use a time proportioned relay output with this then also get + * xdrv_91_timeprop.ino + * In user_config.h or user_config_override.h include code as follows: + + #define USE_PID // include the pid feature (+4.3k) + #define PID_SETPOINT 19.5 // Setpoint value. This is the process value that the process is + // aiming for. + // May be adjusted via MQTT using cmnd pid_sp + + #define PID_PROPBAND 5 // Proportional band in process units (eg degrees). This controls + // the gain of the loop and is the range of process value over which + // the power output will go from 0 to full power. The units are that + // of the process and setpoint, so for example in a heating + // application it might be set to 1.5 degrees. + // May be adjusted via MQTT using cmnd pid_pb + + #define PID_INTEGRAL_TIME 1800 // Integral time seconds. This is a setting for the integral time, + // in seconds. It represents the time constant of the integration + // effect. The larger the value the slower the integral effect will be. + // Obviously the slower the process is the larger this should be. For + // example for a domestic room heated by convection radiators a setting + // of one hour might be appropriate (in seconds). To disable the + // integral effect set this to a large number. + // May be adjusted via MQTT using cmnd pid_ti + + #define PID_DERIVATIVE_TIME 15 // Derivative time seconds. This is a setting for the derivative time, + // in seconds. It represents the time constant of the derivative effect. + // The larger the value the greater will be the derivative effect. + // Typically this will be set to somewhat less than 25% of the integral + // setting, once the integral has been adjusted to the optimum value. To + // disable the derivative effect set this to 0. When initially tuning a + // loop it is often sensible to start with derivative zero and wind it in + // once other parameters have been setup. + // May be adjusted via MQTT using cmnd pid_td + + #define PID_INITIAL_INT 0.5 // Initial integral value (0:1). This is an initial value which is used + // to preset the integrated error value when the flow is deployed in + // order to assist in homing in on the setpoint the first time. It should + // be set to an estimate of what the power requirement might be in order + // to maintain the process at the setpoint. For example for a domestic + // room heating application it might be set to 0.2 indicating that 20% of + // the available power might be required to maintain the setpoint. The + // value is of no consequence apart from device restart. + + #define PID_MAX_INTERVAL 300 // This is the maximum time in seconds that is expected between samples. + // It is provided to cope with unusual situations such as a faulty sensor + // that might prevent the node from being supplied with a process value. + // If no new process value is received for this time then the power is set + // to the value defined for PID_MANUAL_POWER. + // May be adjusted via MQTT using cmnd pid_max_interval + + #define PID_DERIV_SMOOTH_FACTOR 3 // In situations where the process sensor has limited resolution (such as + // the DS18B20), the use of deriviative can be problematic as when the + // process is changing only slowly the steps in the value cause spikes in + // the derivative. To reduce the effect of these this parameter can be + // set to apply a filter to the derivative term. I have found that with + // the DS18B20 that a value of 3 here can be beneficial, providing + // effectively a low pass filter on the derivative at 1/3 of the derivative + // time. This feature may also be useful if the process value is particularly + // noisy. The smaller the value the greater the filtering effect but the + // more it will reduce the effectiveness of the derivative. A value of zero + // disables this feature. + // May be adjusted via MQTT using cmnd pid_d_smooth + + #define PID_AUTO 1 // Auto mode 1 or 0 (for manual). This can be used to enable or disable + // the control (1=enable, auto mode, 0=disabled, manual mode). When in + // manual mode the output is set the value definded for PID_MANUAL_POWER + // May be adjusted via MQTT using cmnd pid_auto + + #define PID_MANUAL_POWER 0 // Power output when in manual mode or fallback mode if too long elapses + // between process values + // May be adjusted via MQTT using cmnd pid_manual_power + + #define PID_UPDATE_SECS 0 // How often to run the pid algorithm (integer secs) or 0 to run the algorithm + // each time a new pv value is received, for most applictions specify 0. + // Otherwise set this to a time + // that is short compared to the response of the process. For example, + // something like 15 seconds may well be appropriate for a domestic room + // heating application. + // May be adjusted via MQTT using cmnd pid_update_secs + + #define PID_USE_TIMPROP 1 // To use an internal relay for a time proportioned output to drive the + // process, set this to indicate which timeprop output to use. For a device + // with just one relay then this will be 1. + // It is then also necessary to define USE_TIMEPROP and set the output up as + // explained in xdrv_91_timeprop.ino + // To disable this feature leave this undefined (undefined, not defined to nothing). + + #define PID_USE_LOCAL_SENSOR // if defined then the local sensor will be used for pv. Leave undefined if + // this is not required. The rate that the sensor is read is defined by TELE_PERIOD + // If not using the sensor then you can supply process values via MQTT using + // cmnd pid_pv + + * Help with using the PID algorithm and with loop tuning can be found at + * http://blog.clanlaw.org.uk/2018/01/09/PID-tuning-with-node-red-contrib-pid.html + * This is directed towards using the algorithm in the node-red node node-red-contrib-pid but the algorithm here is based on + * the code there and the tuning techique described there should work just the same. + + * +**/ + + +#ifdef USE_PID + +# include "PID.h" + +#define D_CMND_PID "pid_" + +#define D_CMND_PID_SETPV "pv" +#define D_CMND_PID_SETSETPOINT "sp" +#define D_CMND_PID_SETPROPBAND "pb" +#define D_CMND_PID_SETINTEGRAL_TIME "ti" +#define D_CMND_PID_SETDERIVATIVE_TIME "td" +#define D_CMND_PID_SETINITIAL_INT "initint" +#define D_CMND_PID_SETDERIV_SMOOTH_FACTOR "d_smooth" +#define D_CMND_PID_SETAUTO "auto" +#define D_CMND_PID_SETMANUAL_POWER "manual_power" +#define D_CMND_PID_SETMAX_INTERVAL "max_interval" +#define D_CMND_PID_SETUPDATE_SECS "update_secs" + +enum PIDCommands { CMND_PID_SETPV, CMND_PID_SETSETPOINT, CMND_PID_SETPROPBAND, CMND_PID_SETINTEGRAL_TIME, + CMND_PID_SETDERIVATIVE_TIME, CMND_PID_SETINITIAL_INT, CMND_PID_SETDERIV_SMOOTH_FACTOR, CMND_PID_SETAUTO, + CMND_PID_SETMANUAL_POWER, CMND_PID_SETMAX_INTERVAL, CMND_PID_SETUPDATE_SECS }; +const char kPIDCommands[] PROGMEM = D_CMND_PID_SETPV "|" D_CMND_PID_SETSETPOINT "|" D_CMND_PID_SETPROPBAND "|" + D_CMND_PID_SETINTEGRAL_TIME "|" D_CMND_PID_SETDERIVATIVE_TIME "|" D_CMND_PID_SETINITIAL_INT "|" D_CMND_PID_SETDERIV_SMOOTH_FACTOR "|" + D_CMND_PID_SETAUTO "|" D_CMND_PID_SETMANUAL_POWER "|" D_CMND_PID_SETMAX_INTERVAL "|" D_CMND_PID_SETUPDATE_SECS; + +static PID pid; +static int update_secs = PID_UPDATE_SECS <= 0 ? 0 : PID_UPDATE_SECS; // how often (secs) the pid alogorithm is run +static int max_interval = PID_MAX_INTERVAL; +static unsigned long last_pv_update_secs = 0; +static boolean run_pid_now = false; // tells PID_Every_Second to run the pid algorithm + +void PID_Init() +{ + snprintf_P(log_data, sizeof(log_data), "PID Init"); + AddLog(LOG_LEVEL_INFO); + pid.initialise( PID_SETPOINT, PID_PROPBAND, PID_INTEGRAL_TIME, PID_DERIVATIVE_TIME, PID_INITIAL_INT, + PID_MAX_INTERVAL, PID_DERIV_SMOOTH_FACTOR, PID_AUTO, PID_MANUAL_POWER ); +} + +void PID_Every_Second() { + static int sec_counter = 0; + // run the pid algorithm if run_pid_now is true or if the right number of seconds has passed or if too long has + // elapsed since last pv update. If too long has elapsed the the algorithm will deal with that. + if (run_pid_now || utc_time - last_pv_update_secs > max_interval || (update_secs != 0 && sec_counter++ % update_secs == 0)) { + run_pid(); + run_pid_now = false; + } +} + +void PID_Show_Sensor() { + // Called each time new sensor data available, data in mqtt data in same format + // as published in tele/SENSOR + // Update period is specified in TELE_PERIOD + // e.g. "{"Time":"2018-03-13T16:48:05","DS18B20":{"Temperature":22.0},"TempUnit":"C"}" + snprintf_P(log_data, sizeof(log_data), "PID_Show_Sensor: mqtt_data: %s", mqtt_data); + AddLog(LOG_LEVEL_INFO); + StaticJsonBuffer<400> jsonBuffer; + // force mqtt_data to read only to stop parse from overwriting it + JsonObject& data_json = jsonBuffer.parseObject((const char*)mqtt_data); + if (data_json.success()) { + const char* value = data_json["DS18B20"]["Temperature"]; + // check that something was found and it contains a number + //if (value != NULL && strlen(value) > 0 && isdigit(value[0]) ) { + if (value != NULL && strlen(value) > 0 && isdigit(value[0]) && strcmp(value,"0.0") ) { + snprintf_P(log_data, sizeof(log_data), "PID_Show_Sensor: Temperature: %s", value); + AddLog(LOG_LEVEL_INFO); + // pass the value to the pid alogorithm to use as current pv + last_pv_update_secs = utc_time; + pid.setPv(atof(value), last_pv_update_secs); + // also trigger running the pid algorithm if we have been told to run it each pv sample + if (update_secs == 0) { + // this runs it at the next second + run_pid_now = true; + } + } else { + Timeprop_Set_Power( PID_USE_TIMPROP-1, 0 ); + snprintf_P(log_data, sizeof(log_data), "PID_Show_Sensor - no temperature found"); + AddLog(LOG_LEVEL_INFO); + } + } else { + // parse failed + Timeprop_Set_Power( PID_USE_TIMPROP-1, 0 ); + snprintf_P(log_data, sizeof(log_data), "PID_Show_Sensor - json parse failed"); + AddLog(LOG_LEVEL_INFO); + } +} + + +/* struct XDRVMAILBOX { */ +/* uint16_t valid; */ +/* uint16_t index; */ +/* uint16_t data_len; */ +/* int16_t payload; */ +/* char *topic; */ +/* char *data; */ +/* } XdrvMailbox; */ + +boolean PID_Command() +{ + char command [CMDSZ]; + boolean serviced = true; + uint8_t ua_prefix_len = strlen(D_CMND_PID); // to detect prefix of command + + snprintf_P(log_data, sizeof(log_data), "Command called: " + "index: %d data_len: %d payload: %d topic: %s data: %s", + XdrvMailbox.index, + XdrvMailbox.data_len, + XdrvMailbox.payload, + (XdrvMailbox.payload >= 0 ? XdrvMailbox.topic : ""), + (XdrvMailbox.data_len >= 0 ? XdrvMailbox.data : "")); + AddLog(LOG_LEVEL_INFO); + + if (0 == strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_PID), ua_prefix_len)) { + // command starts with pid_ + int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + ua_prefix_len, kPIDCommands); + serviced = true; + switch (command_code) { + case CMND_PID_SETPV: + snprintf_P(log_data, sizeof(log_data), "PID command setpv"); + AddLog(LOG_LEVEL_INFO); + last_pv_update_secs = utc_time; + pid.setPv(atof(XdrvMailbox.data), last_pv_update_secs); + // also trigger running the pid algorithm if we have been told to run it each pv sample + if (update_secs == 0) { + // this runs it at the next second + run_pid_now = true; + } + break; + + case CMND_PID_SETSETPOINT: + snprintf_P(log_data, sizeof(log_data), "PID command setsetpoint"); + AddLog(LOG_LEVEL_INFO); + pid.setSp(atof(XdrvMailbox.data)); + break; + + case CMND_PID_SETPROPBAND: + snprintf_P(log_data, sizeof(log_data), "PID command propband"); + AddLog(LOG_LEVEL_INFO); + pid.setPb(atof(XdrvMailbox.data)); + break; + + case CMND_PID_SETINTEGRAL_TIME: + snprintf_P(log_data, sizeof(log_data), "PID command Ti"); + AddLog(LOG_LEVEL_INFO); + pid.setTi(atof(XdrvMailbox.data)); + break; + + case CMND_PID_SETDERIVATIVE_TIME: + snprintf_P(log_data, sizeof(log_data), "PID command Td"); + AddLog(LOG_LEVEL_INFO); + pid.setTd(atof(XdrvMailbox.data)); + break; + + case CMND_PID_SETINITIAL_INT: + snprintf_P(log_data, sizeof(log_data), "PID command initial int"); + AddLog(LOG_LEVEL_INFO); + pid.setInitialInt(atof(XdrvMailbox.data)); + break; + + case CMND_PID_SETDERIV_SMOOTH_FACTOR: + snprintf_P(log_data, sizeof(log_data), "PID command deriv smooth"); + AddLog(LOG_LEVEL_INFO); + pid.setDSmooth(atof(XdrvMailbox.data)); + break; + + case CMND_PID_SETAUTO: + snprintf_P(log_data, sizeof(log_data), "PID command auto"); + AddLog(LOG_LEVEL_INFO); + pid.setAuto(atoi(XdrvMailbox.data)); + break; + + case CMND_PID_SETMANUAL_POWER: + snprintf_P(log_data, sizeof(log_data), "PID command manual power"); + AddLog(LOG_LEVEL_INFO); + pid.setManualPower(atof(XdrvMailbox.data)); + break; + + case CMND_PID_SETMAX_INTERVAL: + snprintf_P(log_data, sizeof(log_data), "PID command set max interval"); + AddLog(LOG_LEVEL_INFO); + max_interval = atoi(XdrvMailbox.data); + pid.setMaxInterval(max_interval); + break; + + case CMND_PID_SETUPDATE_SECS: + snprintf_P(log_data, sizeof(log_data), "PID command set update secs"); + AddLog(LOG_LEVEL_INFO); + update_secs = atoi(XdrvMailbox.data) ; + if (update_secs < 0) update_secs = 0; + break; + + default: + serviced = false; + } + + if (serviced) { + // set mqtt RESULT + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":\"%s\"}"), XdrvMailbox.topic, XdrvMailbox.data); + } + + } else { + serviced = false; + } + return serviced; +} + +static void run_pid() +{ + double power = pid.tick(utc_time); + char buf[10]; + dtostrfd(power, 3, buf); + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":\"%s\"}"), "power", buf); + MqttPublishPrefixTopic_P(TELE, "PID", false); +#if defined PID_USE_TIMPROP + // send power to appropriate timeprop output + Timeprop_Set_Power( PID_USE_TIMPROP-1, power ); +#endif // PID_USE_TIMPROP +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +#define XDRV_92 + +boolean Xdrv92(byte function) +{ + boolean result = false; + + switch (function) { + case FUNC_INIT: + PID_Init(); + break; + case FUNC_EVERY_SECOND: + PID_Every_Second(); + break; + case FUNC_SHOW_SENSOR: + // only use this if the pid loop is to use the local sensor for pv + #if defined PID_USE_LOCAL_SENSOR + PID_Show_Sensor(); + #endif // PID_USE_LOCAL_SENSOR + break; + case FUNC_COMMAND: + result = PID_Command(); + break; + } + return result; +} + +#endif // USE_TIMEPROP