mirror of https://github.com/arendst/Tasmota.git
parent
a3ff1d05a9
commit
6672075481
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,51 @@
|
|||
#include <ESP8266WiFi.h>
|
||||
#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);
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
#include <ESP8266WiFi.h>
|
||||
#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?");
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
#include <ESP8266WiFi.h>
|
||||
#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?");
|
||||
}
|
|
@ -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)
|
||||
#######################################
|
|
@ -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"
|
||||
}
|
|
@ -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=*
|
|
@ -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<ip_hdr*>(data - UDP_HLEN - IP_HLEN);
|
||||
ip_addr_t daddr;
|
||||
daddr.addr = iphdr->dest.addr;
|
||||
|
||||
udp_hdr* udphdr = reinterpret_cast<udp_hdr*>(((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<AsyncUDP*>(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<uint8_t*>(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());
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
#ifndef ESPASYNCUDP_H
|
||||
#define ESPASYNCUDP_H
|
||||
|
||||
#include "IPAddress.h"
|
||||
#include "Print.h"
|
||||
#include <functional>
|
||||
#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<void(AsyncUDPPacket& packet)> AuPacketHandlerFunction;
|
||||
typedef std::function<void(void * arg, AsyncUDPPacket& packet)> 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
|
|
@ -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
|
||||
}
|
|
@ -69,4 +69,4 @@ typedef struct __color
|
|||
uint8_t red;
|
||||
uint8_t green;
|
||||
uint8_t blue;
|
||||
} color_t;
|
||||
} color_t;
|
|
@ -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)
|
|
@ -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;
|
|
@ -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 <ESPAsyncUDP.h>
|
||||
// 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://<ip>/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://<ip>/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 <EEPROM.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
|
||||
#ifdef USE_ASYNC_UDP
|
||||
//#include <ESPAsyncUDP.h>
|
||||
#else
|
||||
#include <WiFiUdp.h>
|
||||
#endif
|
||||
|
||||
#include <ESP8266WebServer.h>
|
||||
|
||||
#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;
|
|
@ -1,5 +1,5 @@
|
|||
name=ESP KNX IP Library
|
||||
version=0.5
|
||||
version=0.5.1
|
||||
author=Nico Weichbrodt <envy>
|
||||
maintainer=Nico Weichbrodt <envy>
|
||||
sentence=ESP8266 library for KNX/IP communication.
|
|
@ -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.
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
# process-control
|
||||
A C++ library of process control algorithms
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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!
|
||||
|
|
|
@ -48,11 +48,14 @@ byte Settings.knx_CB_param[MAX_KNX_CB] Type of Output (set relay, t
|
|||
|
||||
\*********************************************************************************************/
|
||||
|
||||
#include <esp-knx-ip.h>
|
||||
#include <esp-knx-ip.h> // KNX Library
|
||||
// Note: Inside the <esp-knx-ip.h> 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 <ESPAsyncUDP.h>
|
||||
|
||||
//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
|
||||
#endif // USE_KNX
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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<TIMEPROP_NUM_OUTPUTS; i++) {
|
||||
timeprops[i].initialise(cycleTimes[i], deadTimes[i], opInverts[i], fallbacks[i],
|
||||
maxIntervals[i], utc_time);
|
||||
}
|
||||
}
|
||||
|
||||
void Timeprop_Every_Second() {
|
||||
for (int i=0; i<TIMEPROP_NUM_OUTPUTS; i++) {
|
||||
int newState = timeprops[i].tick(utc_time);
|
||||
if (newState != bitRead(currentRelayStates, relayNos[i]-1)){
|
||||
ExecuteCommandPower(relayNos[i], newState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// called by the system each time a relay state is changed
|
||||
void Timeprop_Xdrv_Power() {
|
||||
// for a single relay the state is in the lsb of index, I have think that for
|
||||
// multiple outputs then succesive bits will hold the state but have not been
|
||||
// able to test that
|
||||
currentRelayStates = XdrvMailbox.index;
|
||||
}
|
||||
|
||||
/* struct XDRVMAILBOX { */
|
||||
/* uint16_t valid; */
|
||||
/* uint16_t index; */
|
||||
/* uint16_t data_len; */
|
||||
/* int16_t payload; */
|
||||
/* char *topic; */
|
||||
/* char *data; */
|
||||
/* } XdrvMailbox; */
|
||||
|
||||
// To get here post with topic cmnd/timeprop_setpower_n where n is index into timeprops 0:7
|
||||
boolean Timeprop_Command()
|
||||
{
|
||||
char command [CMDSZ];
|
||||
boolean serviced = true;
|
||||
uint8_t ua_prefix_len = strlen(D_CMND_TIMEPROP); // 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\n",
|
||||
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_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
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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
|
Loading…
Reference in New Issue