Merge branch 'arendst/development' into development

This commit is contained in:
reloxx13 2018-04-18 22:42:56 +02:00
commit 4db8bdb661
61 changed files with 6088 additions and 241 deletions

View File

@ -1,7 +1,19 @@
## Sonoff-Tasmota
Provide ESP8266 based Sonoff by [iTead Studio](https://www.itead.cc/) and ElectroDragon IoT Relay with Serial, Web and MQTT control allowing 'Over the Air' or OTA firmware updates using Arduino IDE.
Current version is **5.12.0k** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/development/sonoff/_releasenotes.ino) for change information.
Alternative firmware for _ESP8266 based devices_ like [iTead](https://www.itead.cc/) _**Sonoff**_ with **web**, **timers**, 'Over The Air' (**OTA**) firmware updates and **sensors support**, allowing control under **Serial**, **HTTP** and **MQTT**, so as to be used on **Smart Home Systems**. Written for Arduino IDE and PlatformIO.
[![GitHub version](https://img.shields.io/github/release/arendst/Sonoff-Tasmota.svg)](https://github.com/arendst/Sonoff-Tasmota/releases/latest)
[![GitHub download](https://img.shields.io/github/downloads/arendst/Sonoff-Tasmota/total.svg)](https://github.com/arendst/Sonoff-Tasmota/releases/latest)
[![License](https://img.shields.io/github/license/arendst/Sonoff-Tasmota.svg)](https://github.com/arendst/Sonoff-Tasmota/blob/development/LICENSE.txt)
If you like **Sonoff-Tasmota**, give it a star, or fork it and contribute!
[![GitHub stars](https://img.shields.io/github/stars/arendst/Sonoff-Tasmota.svg?style=social&label=Star)](https://github.com/arendst/Sonoff-Tasmota/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/arendst/Sonoff-Tasmota.svg?style=social&label=Fork)](https://github.com/arendst/Sonoff-Tasmota/network)
### Development:
[![Build Status](https://img.shields.io/travis/arendst/Sonoff-Tasmota.svg)](https://travis-ci.org/arendst/Sonoff-Tasmota)
Current version is **5.12.0m** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/development/sonoff/_releasenotes.ino) for change information.
### Quick install
@ -58,6 +70,15 @@ The following devices are supported:
- [Luani HVIO board](https://luani.de/projekte/esp8266-hvio/)
- Wemos D1 mini, NodeMcu and Ledunia
### Contribute
You can contribute to Sonoff-Tasmota by
- providing Pull Requests (Features, Proof of Concepts, Language files or Fixes)
- testing new released features and report issues
- donating to acquire hardware for testing and implementating or out of gratitude
[![donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://paypal.me/tasmota)
### License
This program is licensed under GPL-3.0

View File

@ -0,0 +1,12 @@
# ESPAsyncUDP
_Library patched with the [PR #21](https://github.com/me-no-dev/ESPAsyncUDP/pull/21)_
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

View File

@ -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);
}

View File

@ -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?");
}

View File

@ -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?");
}

View File

@ -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)
#######################################

View File

@ -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"
}

View File

@ -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=*

View File

@ -0,0 +1,425 @@
#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)
{
if(_pcb)
{
return writeTo(data, len, &(_pcb->remote_ip), _pcb->remote_port);
}
}
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) {
return 0;
}
return writeTo(message.data(), message.length(), addr, port);
}
size_t AsyncUDP::send(AsyncUDPMessage &message)
{
if((!message) || (!_pcb)) {
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());
}

View File

@ -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

View File

@ -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
}

View File

@ -0,0 +1,72 @@
/**
* esp-knx-ip library for KNX/IP communication on an ESP8266
* Author: Nico Weichbrodt <envy>
* License: MIT
*/
typedef enum __dpt_1_001
{
DPT_1_001_OFF = 0x00,
DPT_1_001_ON = 0x01,
} dpt_1_001_t;
typedef enum __dpt_2_001
{
DPT_2_001_NO_OFF = 0b00,
DPT_2_001_NO_ON = 0b01,
DPT_2_001_YES_OFF = 0b10,
DPT_2_001_YES_ON = 0b11,
} dpt_2_001_t;
typedef enum __dpt_3_007
{
DPT_3_007_DECREASE_STOP = 0x00,
DPT_3_007_DECREASE_100 = 0x01,
DPT_3_007_DECREASE_50 = 0x02,
DPT_3_007_DECREASE_25 = 0x03,
DPT_3_007_DECREASE_12 = 0x04,
DPT_3_007_DECREASE_6 = 0x05,
DPT_3_007_DECREASE_3 = 0x06,
DPT_3_007_DECREASE_1 = 0x07,
DPT_3_007_INCREASE_STOP = 0x08,
DPT_3_007_INCREASE_100 = 0x09,
DPT_3_007_INCREASE_50 = 0x0A,
DPT_3_007_INCREASE_25 = 0x0B,
DPT_3_007_INCREASE_12 = 0x0C,
DPT_3_007_INCREASE_6 = 0x0D,
DPT_3_007_INCREASE_3 = 0x0E,
DPT_3_007_INCREASE_1 = 0x0F,
} dpt_3_007_t;
typedef enum __weekday
{
DPT_10_001_WEEKDAY_MONDAY = 1,
DPT_10_001_WEEKDAY_TUESDAY = 2,
DPT_10_001_WEEKDAY_WEDNESDAY = 3,
DPT_10_001_WEEKDAY_THURSDAY = 4,
DPT_10_001_WEEKDAY_FRIDAY = 5,
DPT_10_001_WEEKDAY_SATURDAY = 6,
DPT_10_001_WEEKDAY_SUNDAY = 8,
} weekday_t;
typedef struct __time_of_day
{
weekday_t weekday;
uint8_t hours;
uint8_t minutes;
uint8_t seconds;
} time_of_day_t;
typedef struct __date
{
uint8_t day;
uint8_t month;
uint8_t year;
} date_t;
typedef struct __color
{
uint8_t red;
uint8_t green;
uint8_t blue;
} color_t;

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Nico Weichbrodt <nico@weichbrodt.me>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,107 @@
# ESP-KNX-IP #
This is a library for the ESP8266 to enable KNXnet/IP communication. It uses UDP multicast on 224.0.23.12:3671.
It is intended to be used with the Arduino platform for the ESP8266.
## Prerequisities / Dependencies ##
* You need version 2.4.0 of the esp8266 board libraries.
* I only tested with lwip v1.4. v2 might work, you need to test yourself.
* You need the [ESPAsyncUDP](https://github.com/me-no-dev/ESPAsyncUDP) library.
* You need a KNXnet/IP **router**. A gateway will **not** work. Alternatively use [knxd](https://github.com/knxd/knxd).
## Caveats ##
Receiving packets should work immediately.
Sending sometimes only works after a substantial amount of time (max 5 minutes in my experiments). In my case, this was fixed by disabling IGMP snooping on the switch(es).
## How to use ##
The library is under development. API may change multiple times in the future.
API documentation is available [here](https://github.com/envy/esp-knx-ip/wiki/API)
A simple example:
```c++
#include <esp-knx-ip.h>
const char* ssid = "my-ssid"; // your network SSID (name)
const char* pass = "my-pw"; // your network password
config_id_t my_GA;
config_id_t param_id;
int8_t some_var = 0;
void setup()
{
// Register a callback that is called when a configurable group address is receiving a telegram
knx.register_callback("Set/Get callback", my_callback);
knx.register_callback("Write callback", my_other_callback);
int default_val = 21;
param_id = knx.config_register_int("My Parameter", default_val);
// Register a configurable group address for sending out answers
my_GA = knx.config_register_ga("Answer GA");
knx.load(); // Try to load a config from EEPROM
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
knx.start(); // Start everything. Must be called after WiFi connection has been established
}
void loop()
{
knx.loop();
}
void my_callback(message_t const &msg, void *arg)
{
switch (msg.ct)
{
case KNX_CT_WRITE:
// Save received data
some_var = knx.data_to_1byte_int(msg.data);
break;
case KNX_CT_READ:
// Answer with saved data
knx.answer1ByteInt(msg.received_on, some_var);
break;
}
}
void my_other_callback(message_t const &msg, void *arg)
{
switch (msg.ct)
{
case KNX_CT_WRITE:
// Write an answer somewhere else
int value = knx.config_get_int(param_id);
address_t ga = knx.config_get_ga(my_GA);
knx.answer1ByteInt(ga, (int8_t)value);
break;
}
}
```
## How to configure (buildtime) ##
Open the `esp-knx-ip.h` and take a look at the config options at the top inside the block marked `CONFIG`
## How to configure (runtime) ##
Simply visit the IP of your ESP with a webbrowser. You can configure the following:
* KNX physical address
* Which group address should trigger which callback
* Which group address are to be used by the program (e.g. for status replies)
The configuration is dynamically generated from the code.

View File

@ -0,0 +1,358 @@
/**
* esp-knx-ip library for KNX/IP communication on an ESP8266
* Author: Nico Weichbrodt <envy>
* License: MIT
*/
#include "esp-knx-ip.h"
/**
* Physical address functions
*/
void ESPKNXIP::physical_address_set(address_t const &addr)
{
physaddr = addr;
}
address_t ESPKNXIP::physical_address_get()
{
return physaddr;
}
/**
* Configuration functions start here
*/
config_id_t ESPKNXIP::config_register_string(String name, uint8_t len, String _default, enable_condition_t cond)
{
if (registered_configs >= MAX_CONFIGS)
return -1;
if (_default.length() >= len)
return -1;
config_id_t id = registered_configs;
custom_configs[id].name = name;
custom_configs[id].type = CONFIG_TYPE_STRING;
custom_configs[id].len = sizeof(uint8_t) + len;
custom_configs[id].cond = cond;
if (id == 0)
custom_configs[id].offset = 0;
else
custom_configs[id].offset = custom_configs[id - 1].offset + custom_configs[id - 1].len;
__config_set_string(id, _default);
registered_configs++;
DEBUG_PRINT("Registered config >");
DEBUG_PRINT(name);
DEBUG_PRINT("< @ ");
DEBUG_PRINT(id);
DEBUG_PRINT("/string[");
DEBUG_PRINT(custom_configs[id].offset);
DEBUG_PRINT("+");
DEBUG_PRINT(custom_configs[id].len);
DEBUG_PRINTLN("]");
return id;
}
config_id_t ESPKNXIP::config_register_int(String name, int32_t _default, enable_condition_t cond)
{
if (registered_configs >= MAX_CONFIGS)
return -1;
config_id_t id = registered_configs;
custom_configs[id].name = name;
custom_configs[id].type = CONFIG_TYPE_INT;
custom_configs[id].len = sizeof(uint8_t) + sizeof(int32_t);
custom_configs[id].cond = cond;
if (id == 0)
custom_configs[id].offset = 0;
else
custom_configs[id].offset = custom_configs[id - 1].offset + custom_configs[id - 1].len;
__config_set_int(id, _default);
registered_configs++;
DEBUG_PRINT("Registered config >");
DEBUG_PRINT(name);
DEBUG_PRINT("< @ ");
DEBUG_PRINT(id);
DEBUG_PRINT("/int[");
DEBUG_PRINT(custom_configs[id].offset);
DEBUG_PRINT("+");
DEBUG_PRINT(custom_configs[id].len);
DEBUG_PRINTLN("]");
return id;
}
config_id_t ESPKNXIP::config_register_bool(String name, bool _default, enable_condition_t cond)
{
if (registered_configs >= MAX_CONFIGS)
return -1;
config_id_t id = registered_configs;
custom_configs[id].name = name;
custom_configs[id].type = CONFIG_TYPE_BOOL;
custom_configs[id].len = sizeof(uint8_t) + sizeof(uint8_t);
custom_configs[id].cond = cond;
if (id == 0)
custom_configs[id].offset = 0;
else
custom_configs[id].offset = custom_configs[id - 1].offset + custom_configs[id - 1].len;
__config_set_bool(id, _default);
registered_configs++;
DEBUG_PRINT("Registered config >");
DEBUG_PRINT(name);
DEBUG_PRINT("< @ ");
DEBUG_PRINT(id);
DEBUG_PRINT("/bool[");
DEBUG_PRINT(custom_configs[id].offset);
DEBUG_PRINT("+");
DEBUG_PRINT(custom_configs[id].len);
DEBUG_PRINTLN("]");
return id;
}
config_id_t ESPKNXIP::config_register_options(String name, option_entry_t *options, uint8_t _default, enable_condition_t cond)
{
if (registered_configs >= MAX_CONFIGS)
return -1;
if (options == nullptr || options->name == nullptr)
return -1;
config_id_t id = registered_configs;
custom_configs[id].name = name;
custom_configs[id].type = CONFIG_TYPE_OPTIONS;
custom_configs[id].len = sizeof(uint8_t) + sizeof(uint8_t);
custom_configs[id].cond = cond;
if (id == 0)
custom_configs[id].offset = 0;
else
custom_configs[id].offset = custom_configs[id - 1].offset + custom_configs[id - 1].len;
custom_configs[id].data.options = options;
__config_set_options(id, _default);
registered_configs++;
DEBUG_PRINT("Registered config >");
DEBUG_PRINT(name);
DEBUG_PRINT("< @ ");
DEBUG_PRINT(id);
DEBUG_PRINT("/opt[");
DEBUG_PRINT(custom_configs[id].offset);
DEBUG_PRINT("+");
DEBUG_PRINT(custom_configs[id].len);
DEBUG_PRINTLN("]");
return id;
}
config_id_t ESPKNXIP::config_register_ga(String name, enable_condition_t cond)
{
if (registered_configs >= MAX_CONFIGS)
return -1;
config_id_t id = registered_configs;
custom_configs[id].name = name;
custom_configs[id].type = CONFIG_TYPE_GA;
custom_configs[id].len = sizeof(uint8_t) + sizeof(address_t);
custom_configs[id].cond = cond;
if (id == 0)
custom_configs[id].offset = 0;
else
custom_configs[id].offset = custom_configs[id - 1].offset + custom_configs[id - 1].len;
address_t t;
t.value = 0;
__config_set_ga(id, t);
registered_configs++;
DEBUG_PRINT("Registered config >");
DEBUG_PRINT(name);
DEBUG_PRINT("< @ ");
DEBUG_PRINT(id);
DEBUG_PRINT("/ga[");
DEBUG_PRINT(custom_configs[id].offset);
DEBUG_PRINT("+");
DEBUG_PRINT(custom_configs[id].len);
DEBUG_PRINTLN("]");
return id;
}
void ESPKNXIP::__config_set_flags(config_id_t id, config_flags_t flags)
{
DEBUG_PRINT("Setting flag @ ");
DEBUG_PRINT(custom_configs[id].offset);
DEBUG_PRINT(" to ");
DEBUG_PRINT(custom_config_data[custom_configs[id].offset], BIN);
DEBUG_PRINT(" | ");
DEBUG_PRINT(flags, BIN);
custom_config_data[custom_configs[id].offset] |= (uint8_t)flags;
DEBUG_PRINT(" = ");
DEBUG_PRINTLN(custom_config_data[custom_configs[id].offset], BIN);
}
void ESPKNXIP::config_set_string(config_id_t id, String val)
{
if (id >= registered_configs)
return;
if (custom_configs[id].type != CONFIG_TYPE_STRING)
return;
if (val.length() >= custom_configs[id].len)
return;
__config_set_flags(id, CONFIG_FLAGS_VALUE_SET);
__config_set_string(id, val);
}
void ESPKNXIP::__config_set_string(config_id_t id, String &val)
{
memcpy(&custom_config_data[custom_configs[id].offset + sizeof(uint8_t)], val.c_str(), val.length()+1);
}
void ESPKNXIP::config_set_int(config_id_t id, int32_t val)
{
if (id >= registered_configs)
return;
if (custom_configs[id].type != CONFIG_TYPE_INT)
return;
__config_set_flags(id, CONFIG_FLAGS_VALUE_SET);
__config_set_int(id, val);
}
void ESPKNXIP::__config_set_int(config_id_t id, int32_t val)
{
// This does not work for some reason:
// Could be due to pointer alignment
//int32_t *v = (int32_t *)(custom_config_data + custom_configs[id].offset);
//*v = val;
custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 0] = (uint8_t)((val & 0xFF000000) >> 24);
custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 1] = (uint8_t)((val & 0x00FF0000) >> 16);
custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 2] = (uint8_t)((val & 0x0000FF00) >> 8);
custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 3] = (uint8_t)((val & 0x000000FF) >> 0);
}
void ESPKNXIP::config_set_bool(config_id_t id, bool val)
{
if (id >= registered_configs)
return;
if (custom_configs[id].type != CONFIG_TYPE_BOOL)
return;
__config_set_flags(id, CONFIG_FLAGS_VALUE_SET);
__config_set_bool(id, val);
}
void ESPKNXIP::__config_set_bool(config_id_t id, bool val)
{
custom_config_data[custom_configs[id].offset + sizeof(uint8_t)] = val ? 1 : 0;
}
void ESPKNXIP::config_set_options(config_id_t id, uint8_t val)
{
if (id >= registered_configs)
return;
if (custom_configs[id].type != CONFIG_TYPE_OPTIONS)
return;
option_entry_t *cur = custom_configs[id].data.options;
while (cur->name != nullptr)
{
if (cur->value == val)
{
__config_set_flags(id, CONFIG_FLAGS_VALUE_SET);
__config_set_options(id, val);
break;
}
cur++;
}
}
void ESPKNXIP::__config_set_options(config_id_t id, uint8_t val)
{
custom_config_data[custom_configs[id].offset + sizeof(uint8_t)] = val;
}
void ESPKNXIP::config_set_ga(config_id_t id, address_t const &val)
{
if (id >= registered_configs)
return;
if (custom_configs[id].type != CONFIG_TYPE_GA)
return;
__config_set_flags(id, CONFIG_FLAGS_VALUE_SET);
__config_set_ga(id, val);
}
void ESPKNXIP::__config_set_ga(config_id_t id, address_t const &val)
{
custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 0] = val.bytes.high;
custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 1] = val.bytes.low;
}
String ESPKNXIP::config_get_string(config_id_t id)
{
if (id >= registered_configs)
return String("");
return String((char *)&custom_config_data[custom_configs[id].offset + sizeof(uint8_t)]);
}
int32_t ESPKNXIP::config_get_int(config_id_t id)
{
if (id >= registered_configs)
return 0;
int32_t v = (custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 0] << 24) +
(custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 1] << 16) +
(custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 2] << 8) +
(custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 3] << 0);
return v;
}
bool ESPKNXIP::config_get_bool(config_id_t id)
{
if (id >= registered_configs)
return false;
return custom_config_data[custom_configs[id].offset + sizeof(uint8_t)] != 0;
}
uint8_t ESPKNXIP::config_get_options(config_id_t id)
{
if (id >= registered_configs)
return false;
return custom_config_data[custom_configs[id].offset + sizeof(uint8_t)];
}
address_t ESPKNXIP::config_get_ga(config_id_t id)
{
address_t t;
if (id >= registered_configs)
{
t.value = 0;
return t;
}
t.bytes.high = custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 0];
t.bytes.low = custom_config_data[custom_configs[id].offset + sizeof(uint8_t) + 1];
return t;
}

View File

@ -0,0 +1,87 @@
/**
* esp-knx-ip library for KNX/IP communication on an ESP8266
* Author: Nico Weichbrodt <envy>
* License: MIT
*/
#include "esp-knx-ip.h"
/**
* Conversion functions
*/
bool ESPKNXIP::data_to_bool(uint8_t *data)
{
return (data[0] & 0x01) == 1 ? true : false;
}
int8_t ESPKNXIP::data_to_1byte_int(uint8_t *data)
{
return (int8_t)data[1];
}
uint8_t ESPKNXIP::data_to_1byte_uint(uint8_t *data)
{
return data[1];
}
int16_t ESPKNXIP::data_to_2byte_int(uint8_t *data)
{
return (int16_t)((data[1] << 8) | data[2]);
}
uint16_t ESPKNXIP::data_to_2byte_uint(uint8_t *data)
{
return (uint16_t)((data[1] << 8) | data[2]);
}
float ESPKNXIP::data_to_2byte_float(uint8_t *data)
{
//uint8_t sign = (data[1] & 0b10000000) >> 7;
uint8_t expo = (data[1] & 0b01111000) >> 3;
int16_t mant = ((data[1] & 0b10000111) << 8) | data[2];
return 0.01f * mant * pow(2, expo);
}
time_of_day_t ESPKNXIP::data_to_3byte_time(uint8_t *data)
{
time_of_day_t time;
time.weekday = (weekday_t)((data[1] & 0b11100000) >> 5);
time.hours = (data[1] & 0b00011111);
time.minutes = (data[2] & 0b00111111);
time.seconds = (data[3] & 0b00111111);
return time;
}
date_t ESPKNXIP::data_to_3byte_data(uint8_t *data)
{
date_t date;
date.day = (data[1] & 0b00011111);
date.month = (data[2] & 0b00001111);
date.year = (data[3] & 0b01111111);
return date;
}
color_t ESPKNXIP::data_to_3byte_color(uint8_t *data)
{
color_t color;
color.red = data[1];
color.green = data[2];
color.blue = data[3];
return color;
}
int32_t ESPKNXIP::data_to_4byte_int(uint8_t *data)
{
return (int32_t)((data[1] << 24) | (data[2] << 16) | (data[3] << 8) | (data[4] << 0));
}
uint32_t ESPKNXIP::data_to_4byte_uint(uint8_t *data)
{
return (uint32_t)((data[1] << 24) | (data[2] << 16) | (data[3] << 8) | (data[4] << 0));
}
float ESPKNXIP::data_to_4byte_float(uint8_t *data)
{
return (float)((data[1] << 24) | (data[2] << 16) | (data[3] << 8) |data[4]);
}

View File

@ -0,0 +1,180 @@
/**
* esp-knx-ip library for KNX/IP communication on an ESP8266
* Author: Nico Weichbrodt <envy>
* License: MIT
*/
#include "esp-knx-ip.h"
/**
* Send functions
*/
void ESPKNXIP::send(address_t const &receiver, knx_command_type_t ct, uint8_t data_len, uint8_t *data)
{
if (receiver.value == 0)
return;
#if SEND_CHECKSUM
uint32_t len = 6 + 2 + 8 + data_len + 1; // knx_pkt + cemi_msg + cemi_service + data + checksum
#else
uint32_t len = 6 + 2 + 8 + data_len; // knx_pkt + cemi_msg + cemi_service + data
#endif
DEBUG_PRINT(F("Creating packet with len "));
DEBUG_PRINTLN(len)
uint8_t buf[len];
knx_ip_pkt_t *knx_pkt = (knx_ip_pkt_t *)buf;
knx_pkt->header_len = 0x06;
knx_pkt->protocol_version = 0x10;
knx_pkt->service_type = __ntohs(KNX_ST_ROUTING_INDICATION);
knx_pkt->total_len.len = __ntohs(len);
cemi_msg_t *cemi_msg = (cemi_msg_t *)knx_pkt->pkt_data;
cemi_msg->message_code = KNX_MT_L_DATA_IND;
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.priority = B11;
cemi_data->control_1.bits.system_broadcast = 0x01;
cemi_data->control_1.bits.repeat = 0x01;
cemi_data->control_1.bits.reserved = 0;
cemi_data->control_1.bits.frame_type = 0x01;
cemi_data->control_2.bits.extended_frame_format = 0x00;
cemi_data->control_2.bits.hop_count = 0x06;
cemi_data->control_2.bits.dest_addr_type = 0x01;
cemi_data->source = physaddr;
cemi_data->destination = receiver;
//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; // ???
memcpy(cemi_data->data, data, data_len);
cemi_data->data[0] = (cemi_data->data[0] & 0x3F) | ((ct & 0x03) << 6);
#if SEND_CHECKSUM
// Calculate checksum, which is just XOR of all bytes
uint8_t cs = buf[0] ^ buf[1];
for (uint32_t i = 2; i < len - 1; ++i)
{
cs ^= buf[i];
}
buf[len - 1] = cs;
#endif
#ifdef ESP_KNX_DEBUG
DEBUG_PRINT(F("Sending packet:"));
for (int i = 0; i < len; ++i)
{
DEBUG_PRINT(F(" 0x"));
DEBUG_PRINT(buf[i], 16);
}
DEBUG_PRINTLN(F(""));
#endif
udp.writeTo(buf, len, MULTICAST_IP, MULTICAST_PORT);
}
void ESPKNXIP::send_1bit(address_t const &receiver, knx_command_type_t ct, uint8_t bit)
{
uint8_t buf[] = {(uint8_t)(bit & 0b00000001)};
send(receiver, ct, 1, buf);
}
void ESPKNXIP::send_2bit(address_t const &receiver, knx_command_type_t ct, uint8_t twobit)
{
uint8_t buf[] = {(uint8_t)(twobit & 0b00000011)};
send(receiver, ct, 1, buf);
}
void ESPKNXIP::send_4bit(address_t const &receiver, knx_command_type_t ct, uint8_t fourbit)
{
uint8_t buf[] = {(uint8_t)(fourbit & 0b00001111)};
send(receiver, ct, 1, buf);
}
void ESPKNXIP::send_1byte_int(address_t const &receiver, knx_command_type_t ct, int8_t val)
{
uint8_t buf[] = {0x00, (uint8_t)val};
send(receiver, ct, 2, buf);
}
void ESPKNXIP::send_1byte_uint(address_t const &receiver, knx_command_type_t ct, uint8_t val)
{
uint8_t buf[] = {0x00, val};
send(receiver, ct, 2, buf);
}
void ESPKNXIP::send_2byte_int(address_t const &receiver, knx_command_type_t ct, int16_t val)
{
uint8_t buf[] = {0x00, (uint8_t)(val >> 8), (uint8_t)(val & 0x00FF)};
send(receiver, ct, 3, buf);
}
void ESPKNXIP::send_2byte_uint(address_t const &receiver, knx_command_type_t ct, uint16_t val)
{
uint8_t buf[] = {0x00, (uint8_t)(val >> 8), (uint8_t)(val & 0x00FF)};
send(receiver, ct, 3, buf);
}
void ESPKNXIP::send_2byte_float(address_t const &receiver, knx_command_type_t ct, float val)
{
float v = val * 100.0f;
int e = 0;
for (; v < -2048.0f; v /= 2)
++e;
for (; v > 2047.0f; v /= 2)
++e;
long m = round(v) & 0x7FF;
short msb = (short) (e << 3 | m >> 8);
if (val < 0.0f)
msb |= 0x80;
uint8_t buf[] = {0x00, (uint8_t)msb, (uint8_t)m};
send(receiver, ct, 3, buf);
}
void ESPKNXIP::send_3byte_time(address_t const &receiver, knx_command_type_t ct, uint8_t weekday, uint8_t hours, uint8_t minutes, uint8_t seconds)
{
weekday <<= 5;
uint8_t buf[] = {0x00, (uint8_t)(((weekday << 5) & 0xE0) | (hours & 0x1F)), (uint8_t)(minutes & 0x3F), (uint8_t)(seconds & 0x3F)};
send(receiver, ct, 4, buf);
}
void ESPKNXIP::send_3byte_date(address_t const &receiver, knx_command_type_t ct, uint8_t day, uint8_t month, uint8_t year)
{
uint8_t buf[] = {0x00, (uint8_t)(day & 0x1F), (uint8_t)(month & 0x0F), year};
send(receiver, ct, 4, buf);
}
void ESPKNXIP::send_3byte_color(address_t const &receiver, knx_command_type_t ct, uint8_t red, uint8_t green, uint8_t blue)
{
uint8_t buf[] = {0x00, red, green, blue};
send(receiver, ct, 4, buf);
}
void ESPKNXIP::send_4byte_int(address_t const &receiver, knx_command_type_t ct, int32_t val)
{
uint8_t buf[] = {0x00,
(uint8_t)((val & 0xFF000000) >> 24),
(uint8_t)((val & 0x00FF0000) >> 16),
(uint8_t)((val & 0x0000FF00) >> 8),
(uint8_t)((val & 0x000000FF) >> 0)};
send(receiver, ct, 5, buf);
}
void ESPKNXIP::send_4byte_uint(address_t const &receiver, knx_command_type_t ct, uint32_t val)
{
uint8_t buf[] = {0x00,
(uint8_t)((val & 0xFF000000) >> 24),
(uint8_t)((val & 0x00FF0000) >> 16),
(uint8_t)((val & 0x0000FF00) >> 8),
(uint8_t)((val & 0x000000FF) >> 0)};
send(receiver, ct, 5, buf);
}
void ESPKNXIP::send_4byte_float(address_t const &receiver, knx_command_type_t ct, float val)
{
uint8_t buf[] = {0x00, ((uint8_t *)&val)[3], ((uint8_t *)&val)[2], ((uint8_t *)&val)[1], ((uint8_t *)&val)[0]};
send(receiver, ct, 5, buf);
}

View File

@ -0,0 +1,540 @@
/**
* esp-knx-ip library for KNX/IP communication on an ESP8266
* Author: Nico Weichbrodt <envy>
* License: MIT
*/
#include "esp-knx-ip.h"
void ESPKNXIP::__handle_root()
{
String m = F("<html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'>");
#if USE_BOOTSTRAP
m += F("<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css' integrity='sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm' crossorigin='anonymous'>");
m += F("<style>.input-group-insert > .input-group-text { border-radius: 0; }</style>");
#endif
m += F("</head><body><div class='container-fluid'>");
m += F("<h2>ESP KNX</h2>");
// Feedback
if (registered_feedbacks > 0)
{
m += F("<h4>Feedback</h4>");
for (feedback_id_t i = 0; i < registered_feedbacks; ++i)
{
if (feedbacks[i].cond && !feedbacks[i].cond())
{
continue;
}
m += F("<form action='" __FEEDBACK_PATH "' method='POST'>");
m += F("<div class='row'><div class='col-auto'><div class='input-group'>");
m += F("<div class='input-group-prepend'><span class='input-group-text'>");
m += feedbacks[i].name;
m += F("</span></div>");
switch (feedbacks[i].type)
{
case FEEDBACK_TYPE_INT:
m += F("<span class='input-group-text'>");
m += String(*(int32_t *)feedbacks[i].data);
m += F("</span>");
break;
case FEEDBACK_TYPE_FLOAT:
m += F("<span class='input-group-text'>");
m += feedbacks[i].options.float_options.prefix;
m += String(*(float *)feedbacks[i].data, feedbacks[i].options.float_options.precision);
m += feedbacks[i].options.float_options.suffix;
m += F("</span>");
break;
case FEEDBACK_TYPE_BOOL:
m += F("<span class='input-group-text'>");
m += (*(bool *)feedbacks[i].data) ? F("True") : F("False");
m += F("</span>");
break;
case FEEDBACK_TYPE_ACTION:
m += F("<input class='form-control' type='hidden' name='id' value='");
m += i;
m += F("' /><div class='input-group-append'><button type='submit' class='btn btn-primary'>");
m += feedbacks[i].options.action_options.btn_text;
m += F("</button></div>");
break;
}
m += F("</div></div></div>");
m += F("</form>");
}
}
if (registered_callbacks > 0)
m += F("<h4>Callbacks</h4>");
if (registered_callback_assignments > 0)
{
for (uint8_t i = 0; i < registered_callback_assignments; ++i)
{
// Skip empty slots
if ((callback_assignments[i].slot_flags & SLOT_FLAGS_USED) == 0)
{
continue;
}
// Skip disabled callbacks
if (callbacks[callback_assignments[i].callback_id].cond && !callbacks[callback_assignments[i].callback_id].cond())
{
continue;
}
address_t &addr = callback_assignments[i].address;
m += F("<form action='" __DELETE_PATH "' method='POST'>");
m += F("<div class='row'><div class='col-auto'><div class='input-group'>");
m += F("<div class='input-group-prepend'><span class='input-group-text'>");
m += addr.ga.area;
m += F("/");
m += addr.ga.line;
m += F("/");
m += addr.ga.member;
m += F("</span>");
m += F("<span class='input-group-text'>");
m += callbacks[callback_assignments[i].callback_id].name;
m += F("</span></div>");
m += F("<input class='form-control' type='hidden' name='id' value='");
m += i;
m += F("' /><div class='input-group-append'><button type='submit' class='btn btn-danger'>Delete</button></div>");
m += F("</div></div></div>");
m += F("</form>");
}
}
if (registered_callbacks > 0)
{
m += F("<form action='" __REGISTER_PATH "' method='POST'>");
m += F("<div class='row'><div class='col-auto'><div class='input-group'>");
m += F("<input class='form-control' type='number' name='area' min='0' max='31'/>");
m += F("<div class='input-group-insert'><span class='input-group-text'>/</span></div>");
m += F("<input class='form-control' type='number' name='line' min='0' max='7'/>");
m += F("<div class='input-group-insert'><span class='input-group-text'>/</span></div>");
m += F("<input class='form-control' type='number' name='member' min='0' max='255'/>");
m += F("<div class='input-group-insert'><span class='input-group-text'>-&gt;</span></div>");
m += F("<select class='form-control' name='cb'>");
for (callback_id_t i = 0; i < registered_callbacks; ++i)
{
// Skip empty slots
if ((callbacks[i].slot_flags & SLOT_FLAGS_USED) == 0)
{
continue;
}
// Skip disabled callbacks
if (callbacks[i].cond && !callbacks[i].cond())
{
continue;
}
m += F("<option value=\"");
m += i;
m += F("\">");
m += callbacks[i].name;
m += F("</option>");
}
m += F("</select>");
m += F("<div class='input-group-append'><button type='submit' class='btn btn-primary'>Set</button></div>");
m += F("</div></div></div>");
m += F("</form>");
}
m += F("<h4>Configuration</h4>");
// Physical address
m += F("<form action='" __PHYS_PATH "' method='POST'>");
m += F("<div class='row'><div class='col-auto'><div class='input-group'>");
m += F("<div class='input-group-prepend'><span class='input-group-text'>Physical address</span></div>");
m += F("<input class='form-control' type='number' name='area' min='0' max='15' value='");
m += physaddr.pa.area;
m += F("'/>");
m += F("<div class='input-group-insert'><span class='input-group-text'>.</span></div>");
m += F("<input class='form-control' type='number' name='line' min='0' max='15' value='");
m += physaddr.pa.line;
m += F("'/>");
m += F("<div class='input-group-insert'><span class='input-group-text'>.</span></div>");
m += F("<input class='form-control' type='number' name='member' min='0' max='255' value='");
m += physaddr.pa.member;
m += F("'/>");
m += F("<div class='input-group-append'><button type='submit' class='btn btn-primary'>Set</button></div>");
m += F("</div></div></div>");
m += F("</form>");
if (registered_configs > 0)
{
for (config_id_t i = 0; i < registered_configs; ++i)
{
// Check if this config option has a enable condition and if so check that condition
if (custom_configs[i].cond && !custom_configs[i].cond())
continue;
m += F("<form action='" __CONFIG_PATH "' method='POST'>");
m += F("<div class='row'><div class='col-auto'><div class='input-group'>");
m += F("<div class='input-group-prepend'><span class='input-group-text'>");
m += custom_configs[i].name;
m += F("</span></div>");
switch (custom_configs[i].type)
{
case CONFIG_TYPE_STRING:
m += F("<input class='form-control' type='text' name='value' value='");
m += config_get_string(i);
m += F("' maxlength='");
m += custom_configs[i].len - 1; // Subtract \0 byte
m += F("'/>");
break;
case CONFIG_TYPE_INT:
m += F("<input class='form-control' type='number' name='value' value='");
m += config_get_int(i);
m += F("'/>");
break;
case CONFIG_TYPE_BOOL:
m += F("<div class='input-group-insert'><span class='input-group-text'>");
m += F("<input type='checkbox' name='value' ");
if (config_get_bool(i))
m += F("checked ");
m += F("/>");
m += F("</span></div>");
break;
case CONFIG_TYPE_OPTIONS:
{
m += F("<select class='custom-select' name='value'>");
option_entry_t *cur = custom_configs[i].data.options;
while (cur->name != nullptr)
{
if (config_get_options(i) == cur->value)
{
m += F("<option selected value='");
}
else
{
m += F("<option value='");
}
m += cur->value;
m += F("'>");
m += String(cur->name);
m += F("</option>");
cur++;
}
m += F("");
m += F("</select>");
break;
}
case CONFIG_TYPE_GA:
address_t a = config_get_ga(i);
m += F("<input class='form-control' type='number' name='area' min='0' max='31' value='");
m += a.ga.area;
m += F("'/>");
m += F("<div class='input-group-insert'><span class='input-group-text'>/</span></div>");
m += F("<input class='form-control' type='number' name='line' min='0' max='7' value='");
m += a.ga.line;
m += F("'/>");
m += F("<div class='input-group-insert'><span class='input-group-text'>/</span></div>");
m += F("<input class='form-control' type='number' name='member' min='0' max='255' value='");
m += a.ga.member;
m += F("'/>");
break;
}
m += F("<input type='hidden' name='id' value='");
m += i;
m += F("'/>");
m += F("<div class='input-group-append'><button type='submit' class='btn btn-primary'>Set</button></div>");
m += F("</div></div></div>");
m += F("</form>");
}
}
#if !(DISABLE_EEPROM_BUTTONS && DISABLE_RESTORE_BUTTON && DISABLE_REBOOT_BUTTON)
// EEPROM save and restore
m += F("<div class='row'>");
// Save to EEPROM
#if !DISABLE_EEPROM_BUTTONS
m += F("<div class='col-auto'>");
m += F("<form action='" __EEPROM_PATH "' method='POST'>");
m += F("<input type='hidden' name='mode' value='1'>");
m += F("<button type='submit' class='btn btn-success'>Save to EEPROM</button>");
m += F("</form>");
m += F("</div>");
// Restore from EEPROM
m += F("<div class='col-auto'>");
m += F("<form action='" __EEPROM_PATH "' method='POST'>");
m += F("<input type='hidden' name='mode' value='2'>");
m += F("<button type='submit' class='btn btn-info'>Restore from EEPROM</button>");
m += F("</form>");
m += F("</div>");
#endif
#if !DISABLE_RESTORE_BUTTON
// Load Defaults
m += F("<div class='col-auto'>");
m += F("<form action='" __RESTORE_PATH "' method='POST'>");
m += F("<button type='submit' class='btn btn-warning'>Restore defaults</button>");
m += F("</form>");
m += F("</div>");
#endif
#if !DISABLE_REBOOT_BUTTON
// Reboot
m += F("<div class='col-auto'>");
m += F("<form action='" __REBOOT_PATH "' method='POST'>");
m += F("<button type='submit' class='btn btn-danger'>Reboot</button>");
m += F("</form>");
m += F("</div>");
#endif
m += F("</div>"); // row
#endif
// End of page
m += F("</div></body></html>");
server->send(200, F("text/html"), m);
}
void ESPKNXIP::__handle_register()
{
DEBUG_PRINTLN(F("Register called"));
if (server->hasArg(F("area")) && server->hasArg(F("line")) && server->hasArg(F("member")) && server->hasArg(F("cb")))
{
uint8_t area = server->arg(F("area")).toInt();
uint8_t line = server->arg(F("line")).toInt();
uint8_t member = server->arg(F("member")).toInt();
callback_id_t cb = (callback_id_t)server->arg(F("cb")).toInt();
DEBUG_PRINT(F("Got args: "));
DEBUG_PRINT(area);
DEBUG_PRINT(F("/"));
DEBUG_PRINT(line);
DEBUG_PRINT(F("/"));
DEBUG_PRINT(member);
DEBUG_PRINT(F("/"));
DEBUG_PRINT(cb);
DEBUG_PRINTLN(F(""));
if (area > 31 || line > 7)
{
DEBUG_PRINTLN(F("Area or Line wrong"));
goto end;
}
if (!__callback_is_id_valid(cb))
{
DEBUG_PRINTLN(F("Invalid callback id"));
goto end;
}
address_t ga = {.ga={line, area, member}};
__callback_register_assignment(ga, cb);
}
end:
server->sendHeader(F("Location"),F(__ROOT_PATH));
server->send(302);
}
void ESPKNXIP::__handle_delete()
{
DEBUG_PRINTLN(F("Delete called"));
if (server->hasArg(F("id")))
{
callback_assignment_id_t id = (callback_assignment_id_t)server->arg(F("id")).toInt();
DEBUG_PRINT(F("Got args: "));
DEBUG_PRINT(id);
DEBUG_PRINTLN(F(""));
if (id >= registered_callback_assignments || (callback_assignments[id].slot_flags & SLOT_FLAGS_USED) == 0)
{
DEBUG_PRINTLN(F("ID wrong"));
goto end;
}
__callback_delete_assignment(id);
}
end:
server->sendHeader(F("Location"),F(__ROOT_PATH));
server->send(302);
}
void ESPKNXIP::__handle_set()
{
DEBUG_PRINTLN(F("Set called"));
if (server->hasArg(F("area")) && server->hasArg(F("line")) && server->hasArg(F("member")))
{
uint8_t area = server->arg(F("area")).toInt();
uint8_t line = server->arg(F("line")).toInt();
uint8_t member = server->arg(F("member")).toInt();
DEBUG_PRINT(F("Got args: "));
DEBUG_PRINT(area);
DEBUG_PRINT(F("."));
DEBUG_PRINT(line);
DEBUG_PRINT(F("."));
DEBUG_PRINT(member);
DEBUG_PRINTLN(F(""));
if (area > 31 || line > 7)
{
DEBUG_PRINTLN(F("Area or Line wrong"));
goto end;
}
physaddr.bytes.high = (area << 4) | line;
physaddr.bytes.low = member;
}
end:
server->sendHeader(F("Location"),F(__ROOT_PATH));
server->send(302);
}
void ESPKNXIP::__handle_config()
{
DEBUG_PRINTLN(F("Config called"));
if (server->hasArg(F("id")))
{
config_id_t id = server->arg(F("id")).toInt();
DEBUG_PRINT(F("Got args: "));
DEBUG_PRINT(id);
DEBUG_PRINTLN(F(""));
if (id < 0 || id >= registered_configs)
{
DEBUG_PRINTLN(F("ID wrong"));
goto end;
}
switch (custom_configs[id].type)
{
case CONFIG_TYPE_STRING:
{
String v = server->arg(F("value"));
if (v.length() >= custom_configs[id].len)
goto end;
__config_set_flags(id, CONFIG_FLAGS_VALUE_SET);
__config_set_string(id, v);
break;
}
case CONFIG_TYPE_INT:
{
__config_set_flags(id, CONFIG_FLAGS_VALUE_SET);
__config_set_int(id, server->arg(F("value")).toInt());
break;
}
case CONFIG_TYPE_BOOL:
{
__config_set_flags(id, CONFIG_FLAGS_VALUE_SET);
__config_set_bool(id, server->arg(F("value")).compareTo(F("on")) == 0);
break;
}
case CONFIG_TYPE_OPTIONS:
{
uint8_t val = (uint8_t)server->arg(F("value")).toInt();
DEBUG_PRINT(F("Value: "));
DEBUG_PRINTLN(val);
config_set_options(id, val);
break;
}
case CONFIG_TYPE_GA:
{
uint8_t area = server->arg(F("area")).toInt();
uint8_t line = server->arg(F("line")).toInt();
uint8_t member = server->arg(F("member")).toInt();
if (area > 31 || line > 7)
{
DEBUG_PRINTLN(F("Area or Line wrong"));
goto end;
}
address_t tmp;
tmp.bytes.high = (area << 3) | line;
tmp.bytes.low = member;
__config_set_flags(id, CONFIG_FLAGS_VALUE_SET);
__config_set_ga(id, tmp);
break;
}
}
}
end:
server->sendHeader(F("Location"),F(__ROOT_PATH));
server->send(302);
}
void ESPKNXIP::__handle_feedback()
{
DEBUG_PRINTLN(F("Feedback called"));
if (server->hasArg(F("id")))
{
config_id_t id = server->arg(F("id")).toInt();
DEBUG_PRINT(F("Got args: "));
DEBUG_PRINT(id);
DEBUG_PRINTLN(F(""));
if (id < 0 || id >= registered_feedbacks)
{
DEBUG_PRINTLN(F("ID wrong"));
goto end;
}
switch (feedbacks[id].type)
{
case FEEDBACK_TYPE_ACTION:
{
feedback_action_fptr_t func = (feedback_action_fptr_t)feedbacks[id].data;
void *arg = feedbacks[id].options.action_options.arg;
func(arg);
break;
}
default:
DEBUG_PRINTLN(F("Feedback has no action"));
break;
}
}
end:
server->sendHeader(F("Location"),F(__ROOT_PATH));
server->send(302);
}
#if !DISABLE_RESTORE_BUTTONS
void ESPKNXIP::__handle_restore()
{
DEBUG_PRINTLN(F("Restore called"));
memcpy(custom_config_data, custom_config_default_data, MAX_CONFIG_SPACE);
end:
server->sendHeader(F("Location"),F(__ROOT_PATH));
server->send(302);
}
#endif
#if !DISABLE_REBOOT_BUTTONS
void ESPKNXIP::__handle_reboot()
{
DEBUG_PRINTLN(F("Rebooting!"));
server->sendHeader(F("Location"),F(__ROOT_PATH));
server->send(302);
delay(1000);
ESP.restart();
//while(1);
}
#endif
#if !DISABLE_EEPROM_BUTTONS
void ESPKNXIP::__handle_eeprom()
{
DEBUG_PRINTLN(F("EEPROM called"));
if (server->hasArg(F("mode")))
{
uint8_t mode = server->arg(F("mode")).toInt();
DEBUG_PRINT(F("Got args: "));
DEBUG_PRINT(mode);
DEBUG_PRINTLN(F(""));
if (mode == 1)
{
// save
save_to_eeprom();
}
else if (mode == 2)
{
// restore
restore_from_eeprom();
}
}
end:
server->sendHeader(F("Location"),F(__ROOT_PATH));
server->send(302);
}
#endif

View File

@ -0,0 +1,658 @@
/**
* esp-knx-ip library for KNX/IP communication on an ESP8266
* Author: Nico Weichbrodt <envy>
* License: MIT
*/
#include "esp-knx-ip.h"
char const *string_defaults[] =
{
"Do this",
"True",
"False",
""
};
ESPKNXIP::ESPKNXIP() : server(nullptr),
registered_callback_assignments(0),
free_callback_assignment_slots(0),
registered_callbacks(0),
free_callback_slots(0),
registered_configs(0),
registered_feedbacks(0)
{
DEBUG_PRINTLN();
DEBUG_PRINTLN("ESPKNXIP starting up");
// Default physical address is 1.1.0
physaddr.bytes.high = (/*area*/1 << 4) | /*line*/1;
physaddr.bytes.low = /*member*/0;
memset(callback_assignments, 0, MAX_CALLBACK_ASSIGNMENTS * sizeof(callback_assignment_t));
memset(callbacks, 0, MAX_CALLBACKS * sizeof(callback_fptr_t));
memset(custom_config_data, 0, MAX_CONFIG_SPACE * sizeof(uint8_t));
memset(custom_config_default_data, 0, MAX_CONFIG_SPACE * sizeof(uint8_t));
memset(custom_configs, 0, MAX_CONFIGS * sizeof(config_t));
}
void ESPKNXIP::load()
{
memcpy(custom_config_default_data, custom_config_data, MAX_CONFIG_SPACE);
EEPROM.begin(EEPROM_SIZE);
restore_from_eeprom();
}
void ESPKNXIP::start(ESP8266WebServer *srv)
{
server = srv;
__start();
}
void ESPKNXIP::start()
{
server = new ESP8266WebServer(80);
__start();
}
void ESPKNXIP::__start()
{
if (server != nullptr)
{
server->on(ROOT_PREFIX, [this](){
__handle_root();
});
server->on(__ROOT_PATH, [this](){
__handle_root();
});
server->on(__REGISTER_PATH, [this](){
__handle_register();
});
server->on(__DELETE_PATH, [this](){
__handle_delete();
});
server->on(__PHYS_PATH, [this](){
__handle_set();
});
#if !DISABLE_EEPROM_BUTTONS
server->on(__EEPROM_PATH, [this](){
__handle_eeprom();
});
#endif
server->on(__CONFIG_PATH, [this](){
__handle_config();
});
server->on(__FEEDBACK_PATH, [this](){
__handle_feedback();
});
#if !DISABLE_RESTORE_BUTTON
server->on(__RESTORE_PATH, [this](){
__handle_restore();
});
#endif
#if !DISABLE_REBOOT_BUTTON
server->on(__REBOOT_PATH, [this](){
__handle_reboot();
});
#endif
server->begin();
}
udp.listenMulticast(MULTICAST_IP, MULTICAST_PORT);
udp.onPacket([this](AsyncUDPPacket &packet) {
DEBUG_PRINTLN("got packet");
__loop_knx(packet); });
}
void ESPKNXIP::save_to_eeprom()
{
uint32_t address = 0;
uint64_t magic = EEPROM_MAGIC;
EEPROM.put(address, magic);
address += sizeof(uint64_t);
EEPROM.put(address++, registered_callback_assignments);
for (uint8_t i = 0; i < MAX_CALLBACK_ASSIGNMENTS; ++i)
{
EEPROM.put(address, callback_assignments[i].address);
address += sizeof(address_t);
}
for (uint8_t i = 0; i < MAX_CALLBACK_ASSIGNMENTS; ++i)
{
EEPROM.put(address, callback_assignments[i].callback_id);
address += sizeof(callback_id_t);
}
EEPROM.put(address, physaddr);
address += sizeof(address_t);
EEPROM.put(address, custom_config_data);
address += sizeof(custom_config_data);
EEPROM.commit();
DEBUG_PRINT("Wrote to EEPROM: 0x");
DEBUG_PRINTLN(address, HEX);
}
void ESPKNXIP::restore_from_eeprom()
{
uint32_t address = 0;
uint64_t magic = 0;
EEPROM.get(address, magic);
if (magic != EEPROM_MAGIC)
{
DEBUG_PRINTLN("No valid magic in EEPROM, aborting restore.");
DEBUG_PRINT("Expected 0x");
DEBUG_PRINT((unsigned long)(EEPROM_MAGIC >> 32), HEX);
DEBUG_PRINT(" 0x");
DEBUG_PRINT((unsigned long)(EEPROM_MAGIC), HEX);
DEBUG_PRINT(" got 0x");
DEBUG_PRINT((unsigned long)(magic >> 32), HEX);
DEBUG_PRINT(" 0x");
DEBUG_PRINTLN((unsigned long)magic, HEX);
return;
}
address += sizeof(uint64_t);
EEPROM.get(address++, registered_callback_assignments);
for (uint8_t i = 0; i < MAX_CALLBACK_ASSIGNMENTS; ++i)
{
EEPROM.get(address, callback_assignments[i].address);
if (callback_assignments[i].address.value != 0)
{
// if address is not 0/0/0 then mark slot as used
callback_assignments[i].slot_flags |= SLOT_FLAGS_USED;
DEBUG_PRINTLN("used slot");
}
else
{
// if address is 0/0/0, then we found a free slot, yay!
// however, only count those slots, if we have not reached registered_callback_assignments yet
if (i < registered_callback_assignments)
{
DEBUG_PRINTLN("free slot before reaching registered_callback_assignments");
free_callback_assignment_slots++;
}
else
{
DEBUG_PRINTLN("free slot");
}
}
address += sizeof(address_t);
}
for (uint8_t i = 0; i < MAX_CALLBACK_ASSIGNMENTS; ++i)
{
EEPROM.get(address, callback_assignments[i].callback_id);
address += sizeof(callback_id_t);
}
EEPROM.get(address, physaddr);
address += sizeof(address_t);
//EEPROM.get(address, custom_config_data);
//address += sizeof(custom_config_data);
uint32_t conf_offset = address;
for (uint8_t i = 0; i < registered_configs; ++i)
{
// First byte is flags.
config_flags_t flags = CONFIG_FLAGS_NO_FLAGS;
flags = (config_flags_t)EEPROM.read(address);
DEBUG_PRINT("Flag in EEPROM @ ");
DEBUG_PRINT(address - conf_offset);
DEBUG_PRINT(": ");
DEBUG_PRINTLN(flags, BIN);
custom_config_data[custom_configs[i].offset] = flags;
if (flags & CONFIG_FLAGS_VALUE_SET)
{
DEBUG_PRINTLN("Non-default value");
for (int j = 0; j < custom_configs[i].len - sizeof(uint8_t); ++j)
{
custom_config_data[custom_configs[i].offset + sizeof(uint8_t) + j] = EEPROM.read(address + sizeof(uint8_t) + j);
}
}
address += custom_configs[i].len;
}
DEBUG_PRINT("Restored from EEPROM: 0x");
DEBUG_PRINTLN(address, HEX);
}
uint16_t ESPKNXIP::__ntohs(uint16_t n)
{
return (uint16_t)((((uint8_t*)&n)[0] << 8) | (((uint8_t*)&n)[1]));
}
callback_assignment_id_t ESPKNXIP::__callback_register_assignment(address_t address, callback_id_t id)
{
if (registered_callback_assignments >= MAX_CALLBACK_ASSIGNMENTS)
return -1;
if (free_callback_assignment_slots == 0)
{
callback_assignment_id_t aid = registered_callback_assignments;
callback_assignments[aid].slot_flags |= SLOT_FLAGS_USED;
callback_assignments[aid].address = address;
callback_assignments[aid].callback_id = id;
registered_callback_assignments++;
return aid;
}
else
{
// find the free slot
for (callback_assignment_id_t aid = 0; aid < registered_callback_assignments; ++aid)
{
if (callback_assignments[aid].slot_flags & SLOT_FLAGS_USED)
{
// found a used slot
continue;
}
// and now an empty one
callback_assignments[aid].slot_flags |= SLOT_FLAGS_USED;
callback_assignments[aid].address = address;
callback_assignments[aid].callback_id = id;
free_callback_assignment_slots--;
return id;
}
}
}
void ESPKNXIP::__callback_delete_assignment(callback_assignment_id_t id)
{
// TODO this can be optimized if we are deleting the last element
// as then we can decrement registered_callback_assignments
// clear slot and mark it as empty
callback_assignments[id].slot_flags = SLOT_FLAGS_EMPTY;
callback_assignments[id].address.value = 0;
callback_assignments[id].callback_id = 0;
if (id == registered_callback_assignments - 1)
{
DEBUG_PRINTLN("last cba deleted");
// If this is the last callback, we can delete it by decrementing registered_callbacks.
registered_callback_assignments--;
// However, if the assignment before this slot are also empty, we can decrement even further
// First check if this was also the first element
if (id == 0)
{
DEBUG_PRINTLN("really last cba");
// If this was the last, then we are done.
return;
}
id--;
while(true)
{
DEBUG_PRINT("checking ");
DEBUG_PRINTLN((int32_t)id);
if ((callback_assignments[id].slot_flags & SLOT_FLAGS_USED) == 0)
{
DEBUG_PRINTLN("merged free slot");
// Slot before is empty
free_callback_assignment_slots--;
registered_callback_assignments--;
}
else
{
DEBUG_PRINTLN("aborted on used slot");
// Slot is used, abort
return;
}
id--;
if (id == CALLBACK_ASSIGNMENT_ID_MAX)
{
DEBUG_PRINTLN("abort on wrap");
// Wrap around, abort
return;
}
}
}
else
{
DEBUG_PRINTLN("free slot created");
// there is now one more free slot
free_callback_assignment_slots++;
}
}
bool ESPKNXIP::__callback_is_id_valid(callback_id_t id)
{
if (id < registered_callbacks)
return true;
if (callbacks[id].slot_flags & SLOT_FLAGS_USED)
return true;
return false;
}
callback_id_t ESPKNXIP::callback_register(String name, callback_fptr_t cb, void *arg, enable_condition_t cond)
{
if (registered_callbacks >= MAX_CALLBACKS)
return -1;
if (free_callback_slots == 0)
{
callback_id_t id = registered_callbacks;
callbacks[id].slot_flags |= SLOT_FLAGS_USED;
callbacks[id].name = name;
callbacks[id].fkt = cb;
callbacks[id].cond = cond;
callbacks[id].arg = arg;
registered_callbacks++;
return id;
}
else
{
// find the free slot
for (callback_id_t id = 0; id < registered_callbacks; ++id)
{
if (callbacks[id].slot_flags & SLOT_FLAGS_USED)
{
// found a used slot
continue;
}
// and now an empty one
callbacks[id].slot_flags |= SLOT_FLAGS_USED;
callbacks[id].name = name;
callbacks[id].fkt = cb;
callbacks[id].cond = cond;
callbacks[id].arg = arg;
free_callback_slots--;
return id;
}
}
}
void ESPKNXIP::callback_deregister(callback_id_t id)
{
if (!__callback_is_id_valid(id))
return;
// clear slot and mark it as empty
callbacks[id].slot_flags = SLOT_FLAGS_EMPTY;
callbacks[id].fkt = nullptr;
callbacks[id].cond = nullptr;
callbacks[id].arg = nullptr;
if (id == registered_callbacks - 1)
{
// If this is the last callback, we can delete it by decrementing registered_callbacks.
registered_callbacks--;
// However, if the callbacks before this slot are also empty, we can decrement even further
// First check if this was also the first element
if (id == 0)
{
// If this was the last, then we are done.
return;
}
id--;
while(true)
{
if ((callbacks[id].slot_flags & SLOT_FLAGS_USED) == 0)
{
// Slot is empty
free_callback_slots--;
registered_callbacks--;
}
else
{
// Slot is used, abort
return;
}
id--;
if (id == CALLBACK_ASSIGNMENT_ID_MAX)
{
// Wrap around, abort
return;
}
}
}
else
{
// there is now one more free slot
free_callback_slots++;
}
}
callback_assignment_id_t ESPKNXIP::callback_assign(callback_id_t id, address_t val)
{
if (!__callback_is_id_valid(id))
return -1;
return __callback_register_assignment(val, id);
}
void ESPKNXIP::callback_unassign(callback_assignment_id_t id)
{
if (!__callback_is_id_valid(id))
return;
__callback_delete_assignment(id);
}
/**
* Feedback functions start here
*/
feedback_id_t ESPKNXIP::feedback_register_int(String name, int32_t *value, enable_condition_t cond)
{
if (registered_feedbacks >= MAX_FEEDBACKS)
return -1;
feedback_id_t id = registered_feedbacks;
feedbacks[id].type = FEEDBACK_TYPE_INT;
feedbacks[id].name = name;
feedbacks[id].cond = cond;
feedbacks[id].data = (void *)value;
registered_feedbacks++;
return id;
}
feedback_id_t ESPKNXIP::feedback_register_float(String name, float *value, uint8_t precision, char const *prefix, char const *suffix, enable_condition_t cond)
{
if (registered_feedbacks >= MAX_FEEDBACKS)
return -1;
feedback_id_t id = registered_feedbacks;
feedbacks[id].type = FEEDBACK_TYPE_FLOAT;
feedbacks[id].name = name;
feedbacks[id].cond = cond;
feedbacks[id].data = (void *)value;
feedbacks[id].options.float_options.precision = precision;
feedbacks[id].options.float_options.prefix = prefix ? strdup(prefix) : STRING_DEFAULT_EMPTY;
feedbacks[id].options.float_options.suffix = suffix ? strdup(suffix) : STRING_DEFAULT_EMPTY;
registered_feedbacks++;
return id;
}
feedback_id_t ESPKNXIP::feedback_register_bool(String name, bool *value, char const *true_text, char const *false_text, enable_condition_t cond)
{
if (registered_feedbacks >= MAX_FEEDBACKS)
return -1;
feedback_id_t id = registered_feedbacks;
feedbacks[id].type = FEEDBACK_TYPE_BOOL;
feedbacks[id].name = name;
feedbacks[id].cond = cond;
feedbacks[id].data = (void *)value;
feedbacks[id].options.bool_options.true_text = true_text ? strdup(true_text) : STRING_DEFAULT_TRUE;
feedbacks[id].options.bool_options.false_text = false_text ? strdup(false_text) : STRING_DEFAULT_FALSE;
registered_feedbacks++;
return id;
}
feedback_id_t ESPKNXIP::feedback_register_action(String name, feedback_action_fptr_t value, const char *btn_text, void *arg, enable_condition_t cond)
{
if (registered_feedbacks >= MAX_FEEDBACKS)
return -1;
feedback_id_t id = registered_feedbacks;
feedbacks[id].type = FEEDBACK_TYPE_ACTION;
feedbacks[id].name = name;
feedbacks[id].cond = cond;
feedbacks[id].data = (void *)value;
feedbacks[id].options.action_options.arg = arg;
feedbacks[id].options.action_options.btn_text = btn_text ? strdup(btn_text) : STRING_DEFAULT_DO_THIS;
registered_feedbacks++;
return id;
}
void ESPKNXIP::loop()
{
if (server != nullptr)
{
__loop_webserver();
}
}
void ESPKNXIP::__loop_webserver()
{
server->handleClient();
}
void ESPKNXIP::__loop_knx(AsyncUDPPacket &packet)
{
size_t read = packet.length();
if (!read)
{
return;
}
DEBUG_PRINTLN(F(""));
DEBUG_PRINT(F("LEN: "));
DEBUG_PRINTLN(read);
uint8_t *buf = packet.data();
DEBUG_PRINT(F("Got packet:"));
#ifdef ESP_KNX_DEBUG
for (size_t i = 0; i < read; ++i)
{
DEBUG_PRINT(F(" 0x"));
DEBUG_PRINT(buf[i], 16);
}
#endif
DEBUG_PRINTLN(F(""));
knx_ip_pkt_t *knx_pkt = (knx_ip_pkt_t *)buf;
DEBUG_PRINT(F("ST: 0x"));
DEBUG_PRINTLN(__ntohs(knx_pkt->service_type), 16);
if (knx_pkt->header_len != 0x06 && knx_pkt->protocol_version != 0x10 && knx_pkt->service_type != KNX_ST_ROUTING_INDICATION)
return;
cemi_msg_t *cemi_msg = (cemi_msg_t *)knx_pkt->pkt_data;
DEBUG_PRINT(F("MT: 0x"));
DEBUG_PRINTLN(cemi_msg->message_code, 16);
if (cemi_msg->message_code != KNX_MT_L_DATA_IND)
return;
DEBUG_PRINT(F("ADDI: 0x"));
DEBUG_PRINTLN(cemi_msg->additional_info_len, 16);
cemi_service_t *cemi_data = &cemi_msg->data.service_information;
if (cemi_msg->additional_info_len > 0)
cemi_data = (cemi_service_t *)(((uint8_t *)cemi_data) + cemi_msg->additional_info_len);
DEBUG_PRINT(F("C1: 0x"));
DEBUG_PRINTLN(cemi_data->control_1.byte, 16);
DEBUG_PRINT(F("C2: 0x"));
DEBUG_PRINTLN(cemi_data->control_2.byte, 16);
DEBUG_PRINT(F("DT: 0x"));
DEBUG_PRINTLN(cemi_data->control_2.bits.dest_addr_type, 16);
if (cemi_data->control_2.bits.dest_addr_type != 0x01)
return;
DEBUG_PRINT(F("HC: 0x"));
DEBUG_PRINTLN(cemi_data->control_2.bits.hop_count, 16);
DEBUG_PRINT(F("EFF: 0x"));
DEBUG_PRINTLN(cemi_data->control_2.bits.extended_frame_format, 16);
DEBUG_PRINT(F("Source: 0x"));
DEBUG_PRINT(cemi_data->source.bytes.high, 16);
DEBUG_PRINT(F(" 0x"));
DEBUG_PRINTLN(cemi_data->source.bytes.low, 16);
DEBUG_PRINT(F("Dest: 0x"));
DEBUG_PRINT(cemi_data->destination.bytes.high, 16);
DEBUG_PRINT(F(" 0x"));
DEBUG_PRINTLN(cemi_data->destination.bytes.low, 16);
knx_command_type_t ct = (knx_command_type_t)(((cemi_data->data[0] & 0xC0) >> 6) | ((cemi_data->pci.apci & 0x03) << 2));
DEBUG_PRINT(F("CT: 0x"));
DEBUG_PRINTLN(ct, 16);
#ifdef ESP_KNX_DEBUG
for (int i = 0; i < cemi_data->data_len; ++i)
{
DEBUG_PRINT(F(" 0x"));
DEBUG_PRINT(cemi_data->data[i], 16);
}
#endif
DEBUG_PRINTLN(F("=="));
// Call callbacks
for (int i = 0; i < registered_callback_assignments; ++i)
{
DEBUG_PRINT(F("Testing: 0x"));
DEBUG_PRINT(callback_assignments[i].address.bytes.high, 16);
DEBUG_PRINT(F(" 0x"));
DEBUG_PRINTLN(callback_assignments[i].address.bytes.low, 16);
if (cemi_data->destination.value == callback_assignments[i].address.value)
{
DEBUG_PRINTLN(F("Found match"));
if (callbacks[callback_assignments[i].callback_id].cond && !callbacks[callback_assignments[i].callback_id].cond())
{
DEBUG_PRINTLN(F("But it's disabled"));
#if ALLOW_MULTIPLE_CALLBACKS_PER_ADDRESS
continue;
#else
return;
#endif
}
uint8_t data[cemi_data->data_len];
memcpy(data, cemi_data->data, cemi_data->data_len);
data[0] = data[0] & 0x3F;
message_t msg = {};
msg.ct = ct;
msg.received_on = cemi_data->destination;
msg.data_len = cemi_data->data_len;
msg.data = data;
callbacks[callback_assignments[i].callback_id].fkt(msg, callbacks[callback_assignments[i].callback_id].arg);
#if ALLOW_MULTIPLE_CALLBACKS_PER_ADDRESS
continue;
#else
return;
#endif
}
}
return;
}
// Global "singleton" object
ESPKNXIP knx;

View File

@ -0,0 +1,571 @@
/**
* esp-knx-ip library for KNX/IP communication on an ESP8266
* Author: Nico Weichbrodt <envy>
* License: MIT
*/
#ifndef ESP_KNX_IP_H
#define ESP_KNX_IP_H
/**
* 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!
* Config space is restriced by EEPROM_SIZE (default 1024).
* Required EEPROM size is 8 + MAX_GA_CALLBACKS * 3 + 2 + MAX_CONFIG_SPACE which is 552 by default
*/
#define EEPROM_SIZE 1024 // [Default 1024]
#define MAX_CALLBACK_ASSIGNMENTS 10 // [Default 10] Maximum number of group address callbacks that can be stored
#define MAX_CALLBACKS 10 // [Default 10] Maximum number of callbacks that can be stored
#define MAX_CONFIGS 20 // [Default 20] Maximum number of config items that can be stored
#define MAX_CONFIG_SPACE 0x0200 // [Default 0x0200] Maximum number of bytes that can be stored for custom config
#define MAX_FEEDBACKS 20 // [Default 20] Maximum number of feedbacks that can be shown
// Callbacks
#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 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.
// These values normally don't need adjustment
#define MULTICAST_PORT 3671 // [Default 3671]
#define MULTICAST_IP IPAddress(224, 0, 23, 12) // [Default IPAddress(224, 0, 23, 12)]
#define SEND_CHECKSUM 0
// Uncomment to enable printing out debug messages.
//#define ESP_KNX_DEBUG
/**
* END CONFIG
*/
#include "Arduino.h"
#include <EEPROM.h>
#include <ESP8266WiFi.h>
#include <ESPAsyncUDP.h>
#include <ESP8266WebServer.h>
#include "DPT.h"
#define EEPROM_MAGIC (0xDEADBEEF00000000 + (MAX_CONFIG_SPACE) + (MAX_CALLBACK_ASSIGNMENTS << 16) + (MAX_CALLBACKS << 8))
// Define where debug output will be printed.
#ifndef DEBUG_PRINTER
#define DEBUG_PRINTER Serial
#endif
// Setup debug printing macros.
#ifdef ESP_KNX_DEBUG
#define DEBUG_PRINT(...) { DEBUG_PRINTER.print(__VA_ARGS__); }
#define DEBUG_PRINTLN(...) { DEBUG_PRINTER.println(__VA_ARGS__); }
#else
#define DEBUG_PRINT(...) {}
#define DEBUG_PRINTLN(...) {}
#endif
#define __ROOT_PATH ROOT_PREFIX"/"
#define __REGISTER_PATH ROOT_PREFIX"/register"
#define __DELETE_PATH ROOT_PREFIX"/delete"
#define __PHYS_PATH ROOT_PREFIX"/phys"
#define __EEPROM_PATH ROOT_PREFIX"/eeprom"
#define __CONFIG_PATH ROOT_PREFIX"/config"
#define __FEEDBACK_PATH ROOT_PREFIX"/feedback"
#define __RESTORE_PATH ROOT_PREFIX"/restore"
#define __REBOOT_PATH ROOT_PREFIX"/reboot"
/**
* Different service types, we are mainly interested in KNX_ST_ROUTING_INDICATION
*/
typedef enum __knx_service_type
{
KNX_ST_SEARCH_REQUEST = 0x0201,
KNX_ST_SEARCH_RESPONSE = 0x0202,
KNX_ST_DESCRIPTION_REQUEST = 0x0203,
KNX_ST_DESCRIPTION_RESPONSE = 0x0204,
KNX_ST_CONNECT_REQUEST = 0x0205,
KNX_ST_CONNECT_RESPONSE = 0x0206,
KNX_ST_CONNECTIONSTATE_REQUEST = 0x0207,
KNX_ST_CONNECTIONSTATE_RESPONSE = 0x0208,
KNX_ST_DISCONNECT_REQUEST = 0x0209,
KNX_ST_DISCONNECT_RESPONSE = 0x020A,
KNX_ST_DEVICE_CONFIGURATION_REQUEST = 0x0310,
KNX_ST_DEVICE_CONFIGURATION_ACK = 0x0311,
KNX_ST_TUNNELING_REQUEST = 0x0420,
KNX_ST_TUNNELING_ACK = 0x0421,
KNX_ST_ROUTING_INDICATION = 0x0530,
KNX_ST_ROUTING_LOST_MESSAGE = 0x0531,
KNX_ST_ROUTING_BUSY = 0x0532,
// KNX_ST_RLOG_START = 0x0600,
// KNX_ST_RLOG_END = 0x06FF,
KNX_ST_REMOTE_DIAGNOSTIC_REQUEST = 0x0740,
KNX_ST_REMOTE_DIAGNOSTIC_RESPONSE = 0x0741,
KNX_ST_REMOTE_BASIC_CONFIGURATION_REQUEST = 0x0742,
KNX_ST_REMOTE_RESET_REQUEST = 0x0743,
// KNX_ST_OBJSRV_START = 0x0800,
// KNX_ST_OBJSRV_END = 0x08FF,
} knx_service_type_t;
/**
* Differnt command types, first three are of main interest
*/
typedef enum __knx_command_type
{
KNX_CT_READ = 0x00,
KNX_CT_ANSWER = 0x01,
KNX_CT_WRITE = 0x02,
KNX_CT_INDIVIDUAL_ADDR_WRITE = 0x03,
KNX_CT_INDIVIDUAL_ADDR_REQUEST = 0x04,
KNX_CT_INDIVIDUAL_ADDR_RESPONSE = 0x05,
KNX_CT_ADC_READ = 0x06,
KNX_CT_ADC_ANSWER = 0x07,
KNX_CT_MEM_READ = 0x08,
KNX_CT_MEM_ANSWER = 0x09,
KNX_CT_MEM_WRITE = 0x0A,
//KNX_CT_UNKNOWN = 0x0B,
KNX_CT_MASK_VERSION_READ = 0x0C,
KNX_CT_MASK_VERSION_RESPONSE = 0x0D,
KNX_CT_RESTART = 0x0E,
KNX_CT_ESCAPE = 0x0F,
} knx_command_type_t;
/**
* cEMI message types, mainly KNX_MT_L_DATA_IND is interesting
*/
typedef enum __knx_cemi_msg_type
{
KNX_MT_L_DATA_REQ = 0x11,
KNX_MT_L_DATA_IND = 0x29,
KNX_MT_L_DATA_CON = 0x2E,
} knx_cemi_msg_type_t;
/**
* TCPI communication type
*/
typedef enum __knx_communication_type {
KNX_COT_UDP = 0x00, // Unnumbered Data Packet
KNX_COT_NDP = 0x01, // Numbered Data Packet
KNX_COT_UCD = 0x02, // Unnumbered Control Data
KNX_COT_NCD = 0x03, // Numbered Control Data
} knx_communication_type_t;
/**
* KNX/IP header
*/
typedef struct __knx_ip_pkt
{
uint8_t header_len; // Should always be 0x06
uint8_t protocol_version; // Should be version 1.0, transmitted as 0x10
uint16_t service_type; // See knx_service_type_t
union
{
struct {
uint8_t first_byte;
uint8_t second_byte;
} bytes;
uint16_t len;
} total_len; // header_len + rest of pkt. This is a bit weird as the spec says this: If the total number of bytes transmitted is greater than 252 bytes, the first “Total Length” byte is set to FF (255). Only in this case the second byte includes additional length information
uint8_t pkt_data[]; // This is of type cemi_msg_t
} knx_ip_pkt_t;
typedef struct __cemi_addi
{
uint8_t type_id;
uint8_t len;
uint8_t data[];
} cemi_addi_t;
typedef union __address
{
uint16_t value;
struct
{
uint8_t high;
uint8_t low;
} bytes;
struct __attribute__((packed))
{
uint8_t line:3;
uint8_t area:5;
uint8_t member;
} ga;
struct __attribute__((packed))
{
uint8_t line:4;
uint8_t area:4;
uint8_t member;
} pa;
uint8_t array[2];
} address_t;
typedef struct __cemi_service
{
union
{
struct
{
// Struct is reversed due to bit order
uint8_t confirm:1; // 0 = no error, 1 = error
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 reserved:1; // always zero
uint8_t frame_type:1; // 0 = extended, 1 = standard
} bits;
uint8_t byte;
} control_1;
union
{
struct
{
// Struct is reversed due to bit order
uint8_t extended_frame_format:4;
uint8_t hop_count:3;
uint8_t dest_addr_type:1; // 0 = individual, 1 = group
} bits;
uint8_t byte;
} control_2;
address_t source;
address_t destination;
uint8_t data_len; // length of data, excluding the tpci byte
struct
{
uint8_t apci:2; // If tpci.comm_type == KNX_COT_UCD or KNX_COT_NCD, then this is apparently control data?
uint8_t tpci_seq_number:4;
uint8_t tpci_comm_type:2; // See knx_communication_type_t
} pci;
uint8_t data[];
} cemi_service_t;
typedef struct __cemi_msg
{
uint8_t message_code;
uint8_t additional_info_len;
union
{
cemi_addi_t additional_info[];
cemi_service_t service_information;
} data;
} cemi_msg_t;
typedef enum __config_type
{
CONFIG_TYPE_UNKNOWN,
CONFIG_TYPE_INT,
CONFIG_TYPE_BOOL,
CONFIG_TYPE_STRING,
CONFIG_TYPE_OPTIONS,
CONFIG_TYPE_GA,
} config_type_t;
typedef enum __feedback_type
{
FEEDBACK_TYPE_UNKNOWN,
FEEDBACK_TYPE_INT,
FEEDBACK_TYPE_FLOAT,
FEEDBACK_TYPE_BOOL,
FEEDBACK_TYPE_ACTION,
} feedback_type_t;
typedef enum __config_flags
{
CONFIG_FLAGS_NO_FLAGS = 0,
CONFIG_FLAGS_VALUE_SET = 1,
} config_flags_t;
typedef enum __slot_flags
{
SLOT_FLAGS_EMPTY = 0, // Empty slots have no flags
SLOT_FLAGS_USED = 1,
} slot_flags_t;
typedef struct __message
{
knx_command_type_t ct;
address_t received_on;
uint8_t data_len;
uint8_t *data;
} message_t;
typedef bool (*enable_condition_t)(void);
typedef void (*callback_fptr_t)(message_t const &msg, void *arg);
typedef void (*feedback_action_fptr_t)(void *arg);
typedef uint8_t callback_id_t;
#define CALLBACK_ID_MAX UINT8_MAX
typedef uint8_t callback_assignment_id_t;
#define CALLBACK_ASSIGNMENT_ID_MAX UINT8_MAX
typedef uint8_t config_id_t;
typedef uint8_t feedback_id_t;
typedef struct __option_entry
{
char const *name;
uint8_t value;
} option_entry_t;
typedef struct __config
{
config_type_t type;
String name;
uint8_t offset;
uint8_t len;
enable_condition_t cond;
union {
option_entry_t *options;
} data;
} config_t;
extern char const *string_defaults[];
#define STRING_DEFAULT_DO_THIS (string_defaults[0])
#define STRING_DEFAULT_TRUE (string_defaults[1])
#define STRING_DEFAULT_FALSE (string_defaults[2])
#define STRING_DEFAULT_EMPTY (string_defaults[3])
typedef struct __feedback_float_options
{
uint8_t precision;
char const *prefix;
char const *suffix;
} feedback_float_options_t;
typedef struct __feedback_bool_options
{
char const *true_text;
char const *false_text;
} feedback_bool_options_t;
typedef struct __feedback_action_options
{
void *arg;
char const *btn_text;
} feedback_action_options_t;
typedef struct __feedback
{
feedback_type_t type;
String name;
enable_condition_t cond;
void *data;
union {
feedback_bool_options_t bool_options;
feedback_float_options_t float_options;
feedback_action_options_t action_options;
} options;
} feedback_t;
typedef struct __callback
{
uint8_t slot_flags;
callback_fptr_t fkt;
enable_condition_t cond;
void *arg;
String name;
} callback_t;
typedef struct __callback_assignment
{
uint8_t slot_flags;
address_t address;
callback_id_t callback_id;
} callback_assignment_t;
class ESPKNXIP {
public:
ESPKNXIP();
void load();
void start();
void start(ESP8266WebServer *srv);
void loop();
void save_to_eeprom();
void restore_from_eeprom();
callback_id_t callback_register(String name, callback_fptr_t cb, void *arg = nullptr, enable_condition_t cond = nullptr);
callback_assignment_id_t callback_assign(callback_id_t id, address_t val);
void callback_deregister(callback_id_t id);
void callback_unassign(callback_assignment_id_t id);
void physical_address_set(address_t const &addr);
address_t physical_address_get();
// Configuration functions
config_id_t config_register_string(String name, uint8_t len, String _default, enable_condition_t cond = nullptr);
config_id_t config_register_int(String name, int32_t _default, enable_condition_t cond = nullptr);
config_id_t config_register_bool(String name, bool _default, enable_condition_t cond = nullptr);
config_id_t config_register_options(String name, option_entry_t *options, uint8_t _default, enable_condition_t cond = nullptr);
config_id_t config_register_ga(String name, enable_condition_t cond = nullptr);
String config_get_string(config_id_t id);
int32_t config_get_int(config_id_t id);
bool config_get_bool(config_id_t id);
uint8_t config_get_options(config_id_t id);
address_t config_get_ga(config_id_t id);
void config_set_string(config_id_t id, String val);
void config_set_int(config_id_t id, int32_t val);
void config_set_bool(config_id_t, bool val);
void config_set_options(config_id_t id, uint8_t val);
void config_set_ga(config_id_t id, address_t const &val);
// Feedback functions
feedback_id_t feedback_register_int(String name, int32_t *value, enable_condition_t cond = nullptr);
feedback_id_t feedback_register_float(String name, float *value, uint8_t precision = 2, char const *prefix = nullptr, char const *suffix = nullptr, enable_condition_t cond = nullptr);
feedback_id_t feedback_register_bool(String name, bool *value, char const *true_text = nullptr, char const *false_text = nullptr, enable_condition_t cond = nullptr);
feedback_id_t feedback_register_action(String name, feedback_action_fptr_t value, char const *btn_text = nullptr, void *arg = nullptr, enable_condition_t = nullptr);
// Send functions
void send(address_t const &receiver, knx_command_type_t ct, uint8_t data_len, uint8_t *data);
void send_1bit(address_t const &receiver, knx_command_type_t ct, uint8_t bit);
void send_2bit(address_t const &receiver, knx_command_type_t ct, uint8_t twobit);
void send_4bit(address_t const &receiver, knx_command_type_t ct, uint8_t fourbit);
void send_1byte_int(address_t const &receiver, knx_command_type_t ct, int8_t val);
void send_1byte_uint(address_t const &receiver, knx_command_type_t ct, uint8_t val);
void send_2byte_int(address_t const &receiver, knx_command_type_t ct, int16_t val);
void send_2byte_uint(address_t const &receiver, knx_command_type_t ct, uint16_t val);
void send_2byte_float(address_t const &receiver, knx_command_type_t ct, float val);
void send_3byte_time(address_t const &receiver, knx_command_type_t ct, uint8_t weekday, uint8_t hours, uint8_t minutes, uint8_t seconds);
void send_3byte_time(address_t const &receiver, knx_command_type_t ct, time_of_day_t const &time) { send_3byte_time(receiver, ct, time.weekday, time.hours, time.minutes, time.seconds); }
void send_3byte_date(address_t const &receiver, knx_command_type_t ct, uint8_t day, uint8_t month, uint8_t year);
void send_3byte_date(address_t const &receiver, knx_command_type_t ct, date_t const &date) { send_3byte_date(receiver, ct, date.day, date.month, date.year); }
void send_3byte_color(address_t const &receiver, knx_command_type_t ct, uint8_t red, uint8_t green, uint8_t blue);
void send_3byte_color(address_t const &receiver, knx_command_type_t ct, color_t const &color) { send_3byte_color(receiver, ct, color.red, color.green, color.blue); }
void send_4byte_int(address_t const &receiver, knx_command_type_t ct, int32_t val);
void send_4byte_uint(address_t const &receiver, knx_command_type_t ct, uint32_t val);
void send_4byte_float(address_t const &receiver, knx_command_type_t ct, float val);
void write_1bit(address_t const &receiver, uint8_t bit) { send_1bit(receiver, KNX_CT_WRITE, bit); }
void write_2bit(address_t const &receiver, uint8_t twobit) { send_2bit(receiver, KNX_CT_WRITE, twobit); }
void write_4bit(address_t const &receiver, uint8_t fourbit) { send_4bit(receiver, KNX_CT_WRITE, fourbit); }
void write_1byte_int(address_t const &receiver, int8_t val) { send_1byte_int(receiver, KNX_CT_WRITE, val); }
void write_1byte_uint(address_t const &receiver, uint8_t val) { send_1byte_uint(receiver, KNX_CT_WRITE, val); }
void write_2byte_int(address_t const &receiver, int16_t val) { send_2byte_int(receiver, KNX_CT_WRITE, val); }
void write_2byte_uint(address_t const &receiver, uint16_t val) { send_2byte_uint(receiver, KNX_CT_WRITE, val); }
void write_2byte_float(address_t const &receiver, float val) { send_2byte_float(receiver, KNX_CT_WRITE, val); }
void write_3byte_time(address_t const &receiver, uint8_t weekday, uint8_t hours, uint8_t minutes, uint8_t seconds) { send_3byte_time(receiver, KNX_CT_WRITE, weekday, hours, minutes, seconds); }
void write_3byte_time(address_t const &receiver, time_of_day_t const &time) { send_3byte_time(receiver, KNX_CT_WRITE, time.weekday, time.hours, time.minutes, time.seconds); }
void write_3byte_date(address_t const &receiver, uint8_t day, uint8_t month, uint8_t year) { send_3byte_date(receiver, KNX_CT_WRITE, day, month, year); }
void write_3byte_date(address_t const &receiver, date_t const &date) { send_3byte_date(receiver, KNX_CT_WRITE, date.day, date.month, date.year); }
void write_3byte_color(address_t const &receiver, uint8_t red, uint8_t green, uint8_t blue) { send_3byte_color(receiver, KNX_CT_WRITE, red, green, blue); }
void write_3byte_color(address_t const &receiver, color_t const &color) { send_3byte_color(receiver, KNX_CT_WRITE, color); }
void write_4byte_int(address_t const &receiver, int32_t val) { send_4byte_int(receiver, KNX_CT_WRITE, val); }
void write_4byte_uint(address_t const &receiver, uint32_t val) { send_4byte_uint(receiver, KNX_CT_WRITE, val); }
void write_4byte_float(address_t const &receiver, float val) { send_4byte_float(receiver, KNX_CT_WRITE, val);}
void answer_1bit(address_t const &receiver, uint8_t bit) { send_1bit(receiver, KNX_CT_ANSWER, bit); }
void answer_2bit(address_t const &receiver, uint8_t twobit) { send_2bit(receiver, KNX_CT_ANSWER, twobit); }
void answer_4bit(address_t const &receiver, uint8_t fourbit) { send_4bit(receiver, KNX_CT_ANSWER, fourbit); }
void answer_1byte_int(address_t const &receiver, int8_t val) { send_1byte_int(receiver, KNX_CT_ANSWER, val); }
void answer_1byte_uint(address_t const &receiver, uint8_t val) { send_1byte_uint(receiver, KNX_CT_ANSWER, val); }
void answer_2byte_int(address_t const &receiver, int16_t val) { send_2byte_int(receiver, KNX_CT_ANSWER, val); }
void answer_2byte_uint(address_t const &receiver, uint16_t val) { send_2byte_uint(receiver, KNX_CT_ANSWER, val); }
void answer_2byte_float(address_t const &receiver, float val) { send_2byte_float(receiver, KNX_CT_ANSWER, val); }
void answer_3byte_time(address_t const &receiver, uint8_t weekday, uint8_t hours, uint8_t minutes, uint8_t seconds) { send_3byte_time(receiver, KNX_CT_ANSWER, weekday, hours, minutes, seconds); }
void answer_3byte_time(address_t const &receiver, time_of_day_t const &time) { send_3byte_time(receiver, KNX_CT_ANSWER, time.weekday, time.hours, time.minutes, time.seconds); }
void answer_3byte_date(address_t const &receiver, uint8_t day, uint8_t month, uint8_t year) { send_3byte_date(receiver, KNX_CT_ANSWER, day, month, year); }
void answer_3byte_date(address_t const &receiver, date_t const &date) { send_3byte_date(receiver, KNX_CT_ANSWER, date.day, date.month, date.year); }
void answer_3byte_color(address_t const &receiver, uint8_t red, uint8_t green, uint8_t blue) { send_3byte_color(receiver, KNX_CT_ANSWER, red, green, blue); }
void answer_3byte_color(address_t const &receiver, color_t const &color) { send_3byte_color(receiver, KNX_CT_ANSWER, color); }
void answer_4byte_int(address_t const &receiver, int32_t val) { send_4byte_int(receiver, KNX_CT_ANSWER, val); }
void answer_4byte_uint(address_t const &receiver, uint32_t val) { send_4byte_uint(receiver, KNX_CT_ANSWER, val); }
void answer_4byte_float(address_t const &receiver, float val) { send_4byte_float(receiver, KNX_CT_ANSWER, val);}
bool data_to_bool(uint8_t *data);
int8_t data_to_1byte_int(uint8_t *data);
uint8_t data_to_1byte_uint(uint8_t *data);
int16_t data_to_2byte_int(uint8_t *data);
uint16_t data_to_2byte_uint(uint8_t *data);
float data_to_2byte_float(uint8_t *data);
color_t data_to_3byte_color(uint8_t *data);
time_of_day_t data_to_3byte_time(uint8_t *data);
date_t data_to_3byte_data(uint8_t *data);
int32_t data_to_4byte_int(uint8_t *data);
uint32_t data_to_4byte_uint(uint8_t *data);
float data_to_4byte_float(uint8_t *data);
static address_t GA_to_address(uint8_t area, uint8_t line, uint8_t member)
{
// Yes, the order is correct, see the struct definition above
address_t tmp = {.ga={line, area, member}};
return tmp;
}
static address_t PA_to_address(uint8_t area, uint8_t line, uint8_t member)
{
// Yes, the order is correct, see the struct definition above
address_t tmp = {.pa={line, area, member}};
return tmp;
}
private:
void __start();
void __loop_knx(AsyncUDPPacket &packet);
// Webserver functions
void __loop_webserver();
void __handle_root();
void __handle_register();
void __handle_delete();
void __handle_set();
#if !DISABLE_EEPROM_BUTTONS
void __handle_eeprom();
#endif
void __handle_config();
void __handle_feedback();
#if !DISABLE_RESTORE_BUTTONS
void __handle_restore();
#endif
#if !DISABLE_REBOOT_BUTTONS
void __handle_reboot();
#endif
void __config_set_flags(config_id_t id, config_flags_t flags);
void __config_set_string(config_id_t id, String &val);
void __config_set_int(config_id_t id, int32_t val);
void __config_set_bool(config_id_t id, bool val);
void __config_set_options(config_id_t id, uint8_t val);
void __config_set_ga(config_id_t id, address_t const &val);
bool __callback_is_id_valid(callback_id_t id);
callback_assignment_id_t __callback_register_assignment(address_t address, callback_id_t id);
void __callback_delete_assignment(callback_assignment_id_t id);
ESP8266WebServer *server;
address_t physaddr;
AsyncUDP udp;
callback_assignment_id_t registered_callback_assignments;
callback_assignment_id_t free_callback_assignment_slots;
callback_assignment_t callback_assignments[MAX_CALLBACK_ASSIGNMENTS];
callback_id_t registered_callbacks;
callback_id_t free_callback_slots;
callback_t callbacks[MAX_CALLBACKS];
config_id_t registered_configs;
uint8_t custom_config_data[MAX_CONFIG_SPACE];
uint8_t custom_config_default_data[MAX_CONFIG_SPACE];
config_t custom_configs[MAX_CONFIGS];
feedback_id_t registered_feedbacks;
feedback_t feedbacks[MAX_FEEDBACKS];
uint16_t __ntohs(uint16_t);
};
// Global "singleton" object
extern ESPKNXIP knx;
#endif

View File

@ -0,0 +1,159 @@
/*
* This is an example showing a simple environment sensor based on a BME280 attached via I2C.
* This sketch was tested on a WeMos D1 mini
*/
#include <Adafruit_BME280.h>
#include <esp-knx-ip.h>
// WiFi config here
const char* ssid = "myssid";
const char* pass = "mypassword";
#define LED_PIN D4
#define UPDATE_INTERVAL 10000
unsigned long next_change = 0;
float last_temp = 0.0;
float last_hum = 0.0;
float last_pres = 0.0;
config_id_t temp_ga, hum_ga, pres_ga;
config_id_t hostname_id;
config_id_t update_rate_id, send_rate_id;
config_id_t enable_sending_id;
config_id_t enable_reading_id;
Adafruit_BME280 bme;
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(115200);
hostname_id = knx.config_register_string("Hostname", 20, String("env"));
enable_sending_id = knx.config_register_bool("Send on update", true);
update_rate_id = knx.config_register_int("Update rate (ms)", UPDATE_INTERVAL);
temp_ga = knx.config_register_ga("Temperature", show_periodic_options);
hum_ga = knx.config_register_ga("Humidity", show_periodic_options);
pres_ga = knx.config_register_ga("Pressure", show_periodic_options);
knx.callback_register("Read Temperature", temp_cb);
knx.callback_register("Read Humidity", hum_cb);
knx.callback_register("Read Pressure", pres_cb);
knx.feedback_register_float("Temperature (°C)", &last_temp);
knx.feedback_register_float("Humidity (%)", &last_hum);
knx.feedback_register_float("Pressure (hPa)", &last_pres, 0);
// Load previous config from EEPROM
knx.load();
// Init sensor
if (!bme.begin(0x76)) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
}
// Init WiFi
WiFi.hostname(knx.config_get_string(hostname_id));
WiFi.begin(ssid, pass);
Serial.println("");
Serial.print("[Connecting]");
Serial.print(ssid);
digitalWrite(LED_PIN, LOW);
while (WiFi.status() != WL_CONNECTED) {
digitalWrite(LED_PIN, HIGH);
delay(250);
Serial.print(".");
digitalWrite(LED_PIN, LOW);
delay(250);
}
digitalWrite(LED_PIN, HIGH);
// Start knx
knx.start();
Serial.println();
Serial.println("Connected to wifi");
Serial.println(WiFi.localIP());
}
void loop() {
knx.loop();
unsigned long now = millis();
if (next_change < now)
{
next_change = now + knx.config_get_int(update_rate_id);
last_temp = bme.readTemperature();
last_hum = bme.readHumidity();
last_pres = bme.readPressure()/100.0f;
Serial.print("T: ");
Serial.print(last_temp);
Serial.print("°C H: ");
Serial.print(last_hum);
Serial.print("% P: ");
Serial.print(last_pres);
Serial.println("hPa");
if (knx.config_get_bool(enable_sending_id))
{
knx.write_2byte_float(knx.config_get_ga(temp_ga), last_temp);
knx.write_2byte_float(knx.config_get_ga(hum_ga), last_hum);
knx.write_2byte_float(knx.config_get_ga(pres_ga), last_pres);
}
}
delay(50);
}
bool show_periodic_options()
{
return knx.config_get_bool(enable_sending_id);
}
bool enable_reading_callback()
{
return knx.config_get_bool(enable_reading_id);
}
void temp_cb(message_t const &msg, void *arg)
{
switch (msg.ct)
{
case KNX_CT_READ:
{
knx.answer_2byte_float(msg.received_on, last_temp);
break;
}
}
}
void hum_cb(message_t const &msg, void *arg)
{
switch (msg.ct)
{
case KNX_CT_READ:
{
knx.answer_2byte_float(msg.received_on, last_hum);
break;
}
}
}
void pres_cb(message_t const &msg, void *arg)
{
switch (msg.ct)
{
case KNX_CT_READ:
{
knx.answer_2byte_float(msg.received_on, last_pres);
break;
}
}
}

View File

@ -0,0 +1,183 @@
#include <esp-knx-ip.h>
// WiFi config here
const char* ssid = "ssid";
const char* pass = "pass";
// Common
#define LED_PIN 13
// For Basic and S20
#define BTN1_PIN 0
#define CH1_PIN 12
// For 4CH
#define BTN2_PIN 9
#define CH2_PIN 5
#define BTN3_PIN 10
#define CH3_PIN 4
#define BTN4_PIN 14
#define CH4_PIN 15
typedef enum __type_option
{
SONOFF_TYPE_NONE = 0,
SONOFF_TYPE_BASIC = 1,
SONOFF_TYPE_S20 = 2,
SONOFF_TYPE_4CH = 3,
SONOFF_TYPE_4CH_PRO = 4,
} type_option_t;
option_entry_t type_options[] = {
{"Sonoff Basic", SONOFF_TYPE_BASIC},
{"Sonoff S20", SONOFF_TYPE_S20},
{"Sonoff 4CH", SONOFF_TYPE_4CH},
{"Sonoff 4CH Pro", SONOFF_TYPE_4CH_PRO},
{nullptr, 0}
};
config_id_t hostname_id;
config_id_t type_id;
typedef struct __sonoff_channel
{
int pin;
int btn_pin;
config_id_t status_ga_id;
bool state;
bool last_btn_state;
} sonoff_channel_t;
sonoff_channel_t channels[] = {
{CH1_PIN, BTN1_PIN, 0, false, false},
{CH2_PIN, BTN2_PIN, 0, false, false},
{CH3_PIN, BTN3_PIN, 0, false, false},
{CH4_PIN, BTN4_PIN, 0, false, false},
};
void setup()
{
pinMode(LED_PIN, OUTPUT);
pinMode(BTN1_PIN, INPUT_PULLUP);
pinMode(BTN2_PIN, INPUT_PULLUP);
pinMode(BTN3_PIN, INPUT_PULLUP);
pinMode(BTN4_PIN, INPUT_PULLUP);
pinMode(CH1_PIN, OUTPUT);
pinMode(CH2_PIN, OUTPUT);
pinMode(CH3_PIN, OUTPUT);
pinMode(CH4_PIN, OUTPUT);
Serial.begin(115200);
// Register the config options
hostname_id = knx.config_register_string("Hostname", 20, String("sonoff"));
type_id = knx.config_register_options("Type", type_options, SONOFF_TYPE_BASIC);
channels[0].status_ga_id = knx.config_register_ga("Channel 1 Status GA");
channels[1].status_ga_id = knx.config_register_ga("Channel 2 Status GA", is_4ch_or_4ch_pro);
channels[2].status_ga_id = knx.config_register_ga("Channel 3 Status GA", is_4ch_or_4ch_pro);
channels[3].status_ga_id = knx.config_register_ga("Channel 4 Status GA", is_4ch_or_4ch_pro);
knx.callback_register("Channel 1", channel_cb, &channels[0]);
knx.callback_register("Channel 2", channel_cb, &channels[1], is_4ch_or_4ch_pro);
knx.callback_register("Channel 3", channel_cb, &channels[2], is_4ch_or_4ch_pro);
knx.callback_register("Channel 4", channel_cb, &channels[3], is_4ch_or_4ch_pro);
knx.feedback_register_bool("Channel 1 is on", &(channels[0].state));
knx.feedback_register_action("Toogle channel 1", toggle_chan, &channels[0]);
knx.feedback_register_bool("Channel 2 is on", &(channels[1].state), is_4ch_or_4ch_pro);
knx.feedback_register_action("Toogle channel 2", toggle_chan, &channels[1], is_4ch_or_4ch_pro);
knx.feedback_register_bool("Channel 3 is on", &(channels[2].state), is_4ch_or_4ch_pro);
knx.feedback_register_action("Toogle channel 3", toggle_chan, &channels[2], is_4ch_or_4ch_pro);
knx.feedback_register_bool("Channel 4 is on", &(channels[3].state), is_4ch_or_4ch_pro);
knx.feedback_register_action("Toogle channel 4", toggle_chan, &channels[3], is_4ch_or_4ch_pro);
knx.load();
// Init WiFi
WiFi.hostname(knx.config_get_string(hostname_id));
WiFi.begin(ssid, pass);
Serial.println("");
Serial.print("[Connecting]");
Serial.print(ssid);
digitalWrite(LED_PIN, LOW);
while (WiFi.status() != WL_CONNECTED) {
digitalWrite(LED_PIN, HIGH);
delay(500);
Serial.print(".");
digitalWrite(LED_PIN, LOW);
}
digitalWrite(LED_PIN, HIGH);
// Start knx
knx.start();
Serial.println();
Serial.println("Connected to wifi");
Serial.println(WiFi.localIP());
}
void loop()
{
knx.loop();
// Check local buttons
check_button(&channels[0]);
if (is_4ch_or_4ch_pro())
{
check_button(&channels[1]);
check_button(&channels[2]);
check_button(&channels[3]);
}
delay(50);
}
bool is_basic_or_s20()
{
uint8_t type = knx.config_get_options(type_id);
return type == SONOFF_TYPE_BASIC || type == SONOFF_TYPE_S20;
}
bool is_4ch_or_4ch_pro()
{
uint8_t type = knx.config_get_options(type_id);
return type == SONOFF_TYPE_4CH ||type == SONOFF_TYPE_4CH_PRO;
}
void check_button(sonoff_channel_t *chan)
{
bool state_now = digitalRead(chan->btn_pin) == HIGH ? true : false;
if (state_now != chan->last_btn_state && state_now == LOW)
{
chan->state = !chan->state;
digitalWrite(chan->pin, chan->state ? HIGH : LOW);
knx.write_1bit(knx.config_get_ga(chan->status_ga_id), chan->state);
}
chan->last_btn_state = state_now;
}
void toggle_chan(void *arg)
{
sonoff_channel_t *chan = (sonoff_channel_t *)arg;
chan->state = !chan->state;
digitalWrite(chan->pin, chan->state ? HIGH : LOW);
knx.write_1bit(knx.config_get_ga(chan->status_ga_id), chan->state);
}
void channel_cb(message_t const &msg, void *arg)
{
sonoff_channel_t *chan = (sonoff_channel_t *)arg;
switch (msg.ct)
{
case KNX_CT_WRITE:
chan->state = msg.data[0];
Serial.println(chan->state ? "Toggle on" : "Toggle off");
digitalWrite(chan->pin, chan->state ? HIGH : LOW);
knx.write_1bit(knx.config_get_ga(chan->status_ga_id), chan->state);
break;
case KNX_CT_READ:
knx.answer_1bit(msg.received_on, chan->state);
}
}

View File

@ -0,0 +1,142 @@
/*
* This is an example showing a simple environment sensor based on a BME280 attached via I2C.
* It shows, how the library can used to statically configure a device without a webserver for config.
* This sketch was tested on a WeMos D1 mini
*/
#include <Adafruit_BME280.h>
#include <esp-knx-ip.h>
// WiFi config here
const char* ssid = "myssid";
const char* pass = "mypassword";
#define LED_PIN D4
#define UPDATE_INTERVAL 10000
unsigned long next_change = 0;
float last_temp = 0.0;
float last_hum = 0.0;
float last_pres = 0.0;
Adafruit_BME280 bme;
// Group addresses to send to (1/1/1, 1/1/2 and 1/1/3)
address_t temp_ga = knx.GA_to_address(1, 1, 1);
address_t hum_ga = knx.GA_to_address(1, 1, 2);
address_t pres_ga = knx.GA_to_address(1, 1, 3);
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(115200);
callback_id_t temp_cb = knx.callback_register("Read Temperature", temp_cb);
callback_id_t hum_cb =knx.callback_register("Read Humidity", hum_cb);
callback_id_t pres_cb =knx.callback_register("Read Pressure", pres_cb);
// Assign callbacks to group addresses (2/1/1, 2/1/2, 2/1/3)
knx.callback_assign(temp_cb, knx.GA_to_address(2, 1, 1));
knx.callback_assign(hum_cb, knx.GA_to_address(2, 1, 2));
knx.callback_assign(pres_cb, knx.GA_to_address(2, 1, 3));
// Set physical address (1.1.1)
knx.physical_address_set(knx.PA_to_address(1, 1, 1));
// Do not call knx.load() for static config, it will try to load config from EEPROM which we don't have here
// Init sensor
if (!bme.begin(0x76)) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
}
// Init WiFi
WiFi.hostname("env");
WiFi.begin(ssid, pass);
Serial.println("");
Serial.print("[Connecting]");
Serial.print(ssid);
digitalWrite(LED_PIN, LOW);
while (WiFi.status() != WL_CONNECTED) {
digitalWrite(LED_PIN, HIGH);
delay(250);
Serial.print(".");
digitalWrite(LED_PIN, LOW);
delay(250);
}
digitalWrite(LED_PIN, HIGH);
// Start knx, disable webserver by passing nullptr
knx.start(nullptr);
Serial.println();
Serial.println("Connected to wifi");
Serial.println(WiFi.localIP());
}
void loop() {
knx.loop();
unsigned long now = millis();
if (next_change < now)
{
next_change = now + UPDATE_INTERVAL;
last_temp = bme.readTemperature();
last_hum = bme.readHumidity();
last_pres = bme.readPressure()/100.0f;
Serial.print("T: ");
Serial.print(last_temp);
Serial.print("°C H: ");
Serial.print(last_hum);
Serial.print("% P: ");
Serial.print(last_pres);
Serial.println("hPa");
knx.write_2byte_float(temp_ga, last_temp);
knx.write_2byte_float(hum_ga, last_hum);
knx.write_2byte_float(pres_ga, last_pres);
}
delay(50);
}
void temp_cb(message_t const &msg, void *arg)
{
switch (msg.ct)
{
case KNX_CT_READ:
{
knx.answer_2byte_float(msg.received_on, last_temp);
break;
}
}
}
void hum_cb(message_t const &msg, void *arg)
{
switch (msg.ct)
{
case KNX_CT_READ:
{
knx.answer_2byte_float(msg.received_on, last_hum);
break;
}
}
}
void pres_cb(message_t const &msg, void *arg)
{
switch (msg.ct)
{
case KNX_CT_READ:
{
knx.answer_2byte_float(msg.received_on, last_pres);
break;
}
}
}

View File

@ -0,0 +1,104 @@
# datatypes
address_t DATA_TYPE
message_t DATA_TYPE
callback_id_t DATA_TYPE
callback_assignment_id_t DATA_TYPE
option_entry_t DATA_TYPE
config_id_t DATA_TYPE
enable_condition_t DATA_TYPE
callback_fptr_t DATA_TYPE
feedback_action_fptr_t DATA_TYPE
knx_command_type_t DATA_TYPE
# methods
setup KEYWORD2
loop KEYWORD2
GA_to_address KEYWORD2
PA_to_address KEYWORD2
callback_register KEYWORD2
callback_assign KEYWORD2
callback_deregister KEYWORD2
callback_unassign KEYWORD2
physical_address_set KEYWORD2
physical_address_get KEYWORD2
config_register_string KEYWORD2
config_register_int KEYWORD2
config_register_bool KEYWORD2
config_register_options KEYWORD2
config_register_ga KEYWORD2
config_get_string KEYWORD2
config_get_int KEYWORD2
config_get_bool KEYWORD2
config_get_options KEYWORD2
config_get_ga KEYWORD2
config_set_string KEYWORD2
config_set_int KEYWORD2
config_set_bool KEYWORD2
config_set_options KEYWORD2
config_set_ga KEYWORD2
feedback_register_int KEYWORD2
feedback_register_float KEYWORD2
feedback_register_bool KEYWORD2
feedback_register_action KEYWORD2
send_1bit KEYWORD2
send_2bit KEYWORD2
send_4bit KEYWORD2
send_1byte_int KEYWORD2
send_1byte_uint KEYWORD2
send_2byte_int KEYWORD2
send_2byte_uint KEYWORD2
send_2byte_float KEYWORD2
send_3byte_time KEYWORD2
send_3byte_time KEYWORD2
send_3byte_date KEYWORD2
send_3byte_date KEYWORD2
send_3byte_color KEYWORD2
send_3byte_color KEYWORD2
send_4byte_int KEYWORD2
send_4byte_uint KEYWORD2
send_4byte_float KEYWORD2
write_1bit KEYWORD2
write_2bit KEYWORD2
write_4bit KEYWORD2
write_1byte_int KEYWORD2
write_1byte_uint KEYWORD2
write_2byte_int KEYWORD2
write_2byte_uint KEYWORD2
write_2byte_float KEYWORD2
write_3byte_time KEYWORD2
write_3byte_time KEYWORD2
write_3byte_date KEYWORD2
write_3byte_date KEYWORD2
write_3byte_color KEYWORD2
write_3byte_color KEYWORD2
write_4byte_int KEYWORD2
write_4byte_uint KEYWORD2
write_4byte_float KEYWORD2
answer_1bit KEYWORD2
answer_2bit KEYWORD2
answer_4bit KEYWORD2
answer_1byte_int KEYWORD2
answer_1byte_uint KEYWORD2
answer_2byte_int KEYWORD2
answer_2byte_uint KEYWORD2
answer_2byte_float KEYWORD2
answer_3byte_time KEYWORD2
answer_3byte_time KEYWORD2
answer_3byte_date KEYWORD2
answer_3byte_date KEYWORD2
answer_3byte_color KEYWORD2
answer_3byte_color KEYWORD2
answer_4byte_int KEYWORD2
answer_4byte_uint KEYWORD2
answer_4byte_float KEYWORD2
data_to_1byte_int KEYWORD 2
data_to_2byte_int KEYWORD 2
data_to_2byte_float KEYWORD 2
data_to_4byte_float KEYWORD 2
data_to_3byte_color KEYWORD 2
data_to_3byte_time KEYWORD 2
data_to_3byte_data KEYWORD 2
# constants
knx LITERAL1

View File

@ -0,0 +1,10 @@
name=ESP KNX IP Library
version=0.4
author=Nico Weichbrodt <envy>
maintainer=Nico Weichbrodt <envy>
sentence=ESP8266 library for KNX/IP communication.
paragraph=Build your own IoT devices with KNX/IP connectivity! This library depends on the ESPAsyncUDP library.
category=Communication
url=https://github.com/envy/esp-knx-ip
architectures=esp8266
includes=esp-knx-ip.h

View File

@ -1,6 +1,19 @@
/* 5.12.0k
* Prepare for simple rules by enlarging Settings area to now 2048 bytes
/* 5.12.0m
* Reinit timers to accomodate random window (#2447)
* Add random window to timers (#2447)
* Add optional KNX IP Protocol Support (#2402)
*
* 5.12.0l
* Release rules up to 511 characters
* Prepare for feature release - call on translators to update their language files
* Add timer sunrise and sunset offset (#2378)
* Fix negative Latitude and Longitude value entry (#2461)
* Fix sunrise and sunset calculation (#2461)
*
* 5.12.0k
* Prepare for simple rules of up to 255 characters by enlarging Settings area to now 2048 bytes
* Change Timer parameter name from Power to Action
* Add commands Publish, Rule, RuleTimer and Event. See Wiki about Rule restriction, usage and examples
* Fix freeing more code space when emulation is disabled (#1592)
* Fix update temperature on DS18x20 drivers (#2328)
* Fix compile error when not defined USE_TIMERS (#2400)

View File

@ -138,28 +138,6 @@
#define D_RSLT_WARNING "WARNING"
// Commands sonoff.ino
#define D_CMND_MQTTHOST "MqttHost"
#define D_CMND_MQTTPORT "MqttPort"
#define D_CMND_MQTTRETRY "MqttRetry"
#define D_CMND_STATETEXT "StateText"
#define D_CMND_MQTTFINGERPRINT "MqttFingerprint"
#define D_CMND_MQTTCLIENT "MqttClient"
#define D_CMND_MQTTUSER "MqttUser"
#define D_CMND_MQTTPASSWORD "MqttPassword"
#define D_CMND_FULLTOPIC "FullTopic"
#define D_CMND_PREFIX "Prefix"
#define PRFX_MAX_STRING_LENGTH 5
#define D_CMND "cmnd"
#define D_STAT "stat"
#define D_TELE "tele"
#define D_CMND_GROUPTOPIC "GroupTopic"
#define D_CMND_TOPIC "Topic"
#define D_CMND_BUTTONTOPIC "ButtonTopic"
#define D_CMND_SWITCHTOPIC "SwitchTopic"
#define D_CMND_BUTTONRETAIN "ButtonRetain"
#define D_CMND_SWITCHRETAIN "SwitchRetain"
#define D_CMND_POWERRETAIN "PowerRetain"
#define D_CMND_SENSORRETAIN "SensorRetain"
#define D_CMND_BACKLOG "Backlog"
#define D_CMND_DELAY "Delay"
#define D_CMND_STATUS "Status"
@ -251,6 +229,31 @@
#define D_CMND_BAUDRATE "Baudrate"
#define D_CMND_EXCEPTION "Exception"
// Commands xdrv_00_mqtt.ino
#define D_CMND_MQTTHOST "MqttHost"
#define D_CMND_MQTTPORT "MqttPort"
#define D_CMND_MQTTRETRY "MqttRetry"
#define D_CMND_STATETEXT "StateText"
#define D_CMND_MQTTFINGERPRINT "MqttFingerprint"
#define D_CMND_MQTTCLIENT "MqttClient"
#define D_CMND_MQTTUSER "MqttUser"
#define D_CMND_MQTTPASSWORD "MqttPassword"
#define D_CMND_FULLTOPIC "FullTopic"
#define D_CMND_PREFIX "Prefix"
#define PRFX_MAX_STRING_LENGTH 5
#define D_CMND "cmnd"
#define D_STAT "stat"
#define D_TELE "tele"
#define D_CMND_GROUPTOPIC "GroupTopic"
#define D_CMND_TOPIC "Topic"
#define D_CMND_BUTTONTOPIC "ButtonTopic"
#define D_CMND_SWITCHTOPIC "SwitchTopic"
#define D_CMND_BUTTONRETAIN "ButtonRetain"
#define D_CMND_SWITCHRETAIN "SwitchRetain"
#define D_CMND_POWERRETAIN "PowerRetain"
#define D_CMND_SENSORRETAIN "SensorRetain"
#define D_CMND_PUBLISH "Publish"
// Commands xdrv_01_light.ino
#define D_CMND_CHANNEL "Channel"
#define D_CMND_COLOR "Color"
@ -358,6 +361,7 @@
#define D_JSON_TIMER_ARM "Arm"
#define D_JSON_TIMER_MODE "Mode"
#define D_JSON_TIMER_TIME "Time"
#define D_JSON_TIMER_WINDOW "Window"
#define D_JSON_TIMER_DAYS "Days"
#define D_JSON_TIMER_REPEAT "Repeat"
#define D_JSON_TIMER_OUTPUT "Output"

View File

@ -28,6 +28,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele.
*
* Updated until v5.12.0k
\*********************************************************************/
//#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -373,6 +374,26 @@
#define D_TIMER_OUTPUT "Výstup"
#define D_TIMER_ACTION "Napájení"
// xdrv_10_knx.ino
#define D_CONFIGURE_KNX "Configure KNX"
#define D_KNX_PARAMETERS "KNX Parameters"
#define D_KNX_GENERAL_CONFIG "General"
#define D_KNX_PHYSICAL_ADDRESS "Physical Address"
#define D_KNX_PHYSICAL_ADDRESS_NOTE "( Must be unique on the KNX network )"
#define D_KNX_ENABLE "Enable KNX"
#define D_KNX_GROUP_ADDRESS_TO_WRITE "Data to Send to Group Addresses"
#define D_ADD "Add"
#define D_DELETE "Delete"
#define D_REPLY "Reply"
#define D_KNX_GROUP_ADDRESS_TO_READ "Group Addresses to Receive Data from"
#define D_LOG_KNX "KNX: "
#define D_RECEIVED_FROM "Received from"
#define D_KNX_COMMAND_WRITE "Write"
#define D_KNX_COMMAND_READ "Read"
#define D_KNX_COMMAND_OTHER "Other"
#define D_SENT_TO "sent to"
#define D_KNX_WARNING "The group address ( 0 / 0 / 0 ) is reserved and can not be used."
// xdrv_03_energy.ino
#define D_ENERGY_TODAY "Spotřeba Dnes"
#define D_ENERGY_YESTERDAY "Spotřeba Včera"

View File

@ -28,6 +28,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele.
*
* Updated until v5.12.0l
\*********************************************************************/
//#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -373,6 +374,26 @@
#define D_TIMER_OUTPUT "Ausgang"
#define D_TIMER_ACTION "Aktion"
// xdrv_10_knx.ino
#define D_CONFIGURE_KNX "Configure KNX"
#define D_KNX_PARAMETERS "KNX Parameters"
#define D_KNX_GENERAL_CONFIG "General"
#define D_KNX_PHYSICAL_ADDRESS "Physical Address"
#define D_KNX_PHYSICAL_ADDRESS_NOTE "( Must be unique on the KNX network )"
#define D_KNX_ENABLE "Enable KNX"
#define D_KNX_GROUP_ADDRESS_TO_WRITE "Data to Send to Group Addresses"
#define D_ADD "Add"
#define D_DELETE "Delete"
#define D_REPLY "Reply"
#define D_KNX_GROUP_ADDRESS_TO_READ "Group Addresses to Receive Data from"
#define D_LOG_KNX "KNX: "
#define D_RECEIVED_FROM "Received from"
#define D_KNX_COMMAND_WRITE "Write"
#define D_KNX_COMMAND_READ "Read"
#define D_KNX_COMMAND_OTHER "Other"
#define D_SENT_TO "sent to"
#define D_KNX_WARNING "The group address ( 0 / 0 / 0 ) is reserved and can not be used."
// xdrv_03_energy.ino
#define D_ENERGY_TODAY "Energie heute"
#define D_ENERGY_YESTERDAY "Energie gestern"

View File

@ -28,6 +28,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele.
*
* Updated until v5.12.0l
\*********************************************************************/
//#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -373,6 +374,26 @@
#define D_TIMER_OUTPUT "Output"
#define D_TIMER_ACTION "Action"
// xdrv_10_knx.ino
#define D_CONFIGURE_KNX "Configure KNX"
#define D_KNX_PARAMETERS "KNX Parameters"
#define D_KNX_GENERAL_CONFIG "General"
#define D_KNX_PHYSICAL_ADDRESS "Physical Address"
#define D_KNX_PHYSICAL_ADDRESS_NOTE "( Must be unique on the KNX network )"
#define D_KNX_ENABLE "Enable KNX"
#define D_KNX_GROUP_ADDRESS_TO_WRITE "Data to Send to Group Addresses"
#define D_ADD "Add"
#define D_DELETE "Delete"
#define D_REPLY "Reply"
#define D_KNX_GROUP_ADDRESS_TO_READ "Group Addresses to Receive Data from"
#define D_LOG_KNX "KNX: "
#define D_RECEIVED_FROM "Received from"
#define D_KNX_COMMAND_WRITE "Write"
#define D_KNX_COMMAND_READ "Read"
#define D_KNX_COMMAND_OTHER "Other"
#define D_SENT_TO "sent to"
#define D_KNX_WARNING "The group address ( 0 / 0 / 0 ) is reserved and can not be used."
// xdrv_03_energy.ino
#define D_ENERGY_TODAY "Energy Today"
#define D_ENERGY_YESTERDAY "Energy Yesterday"

View File

@ -28,6 +28,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele.
*
* Updated until v5.12.0k
\*********************************************************************/
#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -373,6 +374,26 @@
#define D_TIMER_OUTPUT "Salida"
#define D_TIMER_ACTION "Estado"
// xdrv_10_knx.ino
#define D_CONFIGURE_KNX "Configure KNX"
#define D_KNX_PARAMETERS "KNX Parameters"
#define D_KNX_GENERAL_CONFIG "General"
#define D_KNX_PHYSICAL_ADDRESS "Physical Address"
#define D_KNX_PHYSICAL_ADDRESS_NOTE "( Must be unique on the KNX network )"
#define D_KNX_ENABLE "Enable KNX"
#define D_KNX_GROUP_ADDRESS_TO_WRITE "Data to Send to Group Addresses"
#define D_ADD "Add"
#define D_DELETE "Delete"
#define D_REPLY "Reply"
#define D_KNX_GROUP_ADDRESS_TO_READ "Group Addresses to Receive Data from"
#define D_LOG_KNX "KNX: "
#define D_RECEIVED_FROM "Received from"
#define D_KNX_COMMAND_WRITE "Write"
#define D_KNX_COMMAND_READ "Read"
#define D_KNX_COMMAND_OTHER "Other"
#define D_SENT_TO "sent to"
#define D_KNX_WARNING "The group address ( 0 / 0 / 0 ) is reserved and can not be used."
// xdrv_03_energy.ino
#define D_ENERGY_TODAY "Energía Hoy"
#define D_ENERGY_YESTERDAY "Energía Ayer"

View File

@ -28,6 +28,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele.
*
* Updated until v5.12.0l
\*********************************************************************/
#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -58,7 +59,7 @@
#define D_BLINKOFF "BlinkOff"
#define D_BOOT_COUNT "Nombre de boot"
#define D_BRIGHTLIGHT "Luminosité"
#define D_BUTTON "Boutton"
#define D_BUTTON "Bouton"
#define D_BY "par" // Written by me
#define D_BYTES "Bytes"
#define D_CELSIUS "Celsius"
@ -69,8 +70,8 @@
#define D_CONNECTED "Connecté"
#define D_COUNT "Compte"
#define D_COUNTER "Compteur"
#define D_CURRENT "Courrant" // As in Voltage and Current
#define D_DATA "Data"
#define D_CURRENT "Courant" // As in Voltage and Current
#define D_DATA "Donnée"
#define D_DARKLIGHT "Sombre"
#define D_DEBUG "Debug"
#define D_DISABLED "Désactivé"
@ -78,17 +79,17 @@
#define D_DONE "Fait"
#define D_DST_TIME "DST"
#define D_ECO2 "eCO2"
#define D_EMULATION "Emulation"
#define D_EMULATION "Émulation"
#define D_ENABLED "Activé"
#define D_ERASE "Effacer"
#define D_ERROR "Erreur"
#define D_FAHRENHEIT "Fahrenheit"
#define D_FAILED "Echoué"
#define D_FALLBACK "Fallback"
#define D_FALLBACK_TOPIC "Fallback Topic"
#define D_FAILED "Échoué"
#define D_FALLBACK "Secours"
#define D_FALLBACK_TOPIC "Topic de secours"
#define D_FALSE "Faux"
#define D_FILE "Fichier"
#define D_FREE_MEMORY "Memoire libre"
#define D_FREE_MEMORY "Mémoire libre"
#define D_GAS "Gaz"
#define D_GATEWAY "Passerelle"
#define D_GROUP "Groupe"
@ -96,7 +97,7 @@
#define D_HOSTNAME "Hostname"
#define D_HUMIDITY "Humidité"
#define D_ILLUMINANCE "Éclairement"
#define D_IMMEDIATE "immediat" // Button immediate
#define D_IMMEDIATE "immédiat" // Button immediate
#define D_INDEX "Index"
#define D_INFO "Info"
#define D_INITIALIZED "Initialisé"
@ -107,11 +108,11 @@
#define D_MQTT "MQTT"
#define D_MULTI_PRESS "multi-pression"
#define D_NOISE "Bruit"
#define D_NONE "None"
#define D_OFF "Off"
#define D_NONE "Aucun"
#define D_OFF "Arrêt"
#define D_OFFLINE "Déconnecté"
#define D_OK "Ok"
#define D_ON "On"
#define D_ON "Marche"
#define D_ONLINE "Connecté"
#define D_PASSWORD "Mot de passe"
#define D_PORT "Port"
@ -124,37 +125,37 @@
#define D_PROJECT "Projet"
#define D_RECEIVED "Reçu"
#define D_RESTART "Redémarrage"
#define D_RESTARTING "Redémarrage"
#define D_RESTART_REASON "Raison redémarrage"
#define D_RESTORE "restorer"
#define D_RESTARTING "Redémarre"
#define D_RESTART_REASON "Raison du redémarrage"
#define D_RESTORE "restaurer"
#define D_RETAINED "retenu"
#define D_RULE "Rule"
#define D_SAVE "Enregister"
#define D_RULE "Règle"
#define D_SAVE "Enregistrer"
#define D_SENSOR "Capteur"
#define D_SSID "SSID"
#define D_START "Start"
#define D_START "Départ"
#define D_STD_TIME "STD"
#define D_STOP "Stop"
#define D_SUBNET_MASK "Masque sous réseau"
#define D_SUBNET_MASK "Masque sous-réseau"
#define D_SUBSCRIBE_TO "Souscrire à"
#define D_SUCCESSFUL "Réussi"
#define D_SUNRISE "Sunrise"
#define D_SUNSET "Sunset"
#define D_TEMPERATURE "Temperature"
#define D_SUNRISE "Jour" // "Lever du soleil" <- maybe too long?
#define D_SUNSET "Nuit" // "Coucher du soleil" <- maybe too long?
#define D_TEMPERATURE "Température"
#define D_TO "à"
#define D_TOGGLE "Bascule"
#define D_TOPIC "Topic"
#define D_TOGGLE "Inverser"
#define D_TOPIC "Topic" // Keep MQTT keyword
#define D_TRANSMIT "Transmettre"
#define D_TRUE "Vrai"
#define D_TVOC "TVOC"
#define D_UPGRADE "upgrade"
#define D_UPLOAD "Upload"
#define D_UPTIME "Uptime"
#define D_UPGRADE "mise à jour"
#define D_UPLOAD "Upload" // Not better in french
#define D_UPTIME "Durée d'activité"
#define D_USER "Utilisateur"
#define D_UTC_TIME "UTC"
#define D_UV_LEVEL "Niveau UV"
#define D_VERSION "Version"
#define D_VOLTAGE "Voltage"
#define D_VOLTAGE "Tension"
#define D_WARMLIGHT "Chaud"
#define D_WEB_SERVER "Serveur web"
@ -162,36 +163,36 @@
#define D_WARNING_MINIMAL_VERSION "ATTENTION Cette version ne supporte pas les réglages persistants"
#define D_LEVEL_10 "level 1-0"
#define D_LEVEL_01 "level 0-1"
#define D_SERIAL_LOGGING_DISABLED "Journalisation série désactivé"
#define D_SYSLOG_LOGGING_REENABLED "Jounalisation syslog réactivé"
#define D_SERIAL_LOGGING_DISABLED "Journalisation série désactivée"
#define D_SYSLOG_LOGGING_REENABLED "Jounalisation syslog réactivée"
#define D_SET_BAUDRATE_TO "Definir baudrate à"
#define D_RECEIVED_TOPIC "Topic reçu"
#define D_SET_BAUDRATE_TO "Définir baudrate à"
#define D_RECEIVED_TOPIC "Topic reçu" // Terme MQTT
#define D_DATA_SIZE "Taille données"
#define D_ANALOG_INPUT "Analogique"
// support.ino
#define D_OSWATCH "osWatch"
#define D_BLOCKED_LOOP "Boucle bloquée"
#define D_WPS_FAILED_WITH_STATUS "WPSconfig ECHOUÉ avec status"
#define D_WPS_FAILED_WITH_STATUS "WPSconfig ÉCHOUÉ avec status"
#define D_ACTIVE_FOR_3_MINUTES "actif pour 3 minutes"
#define D_FAILED_TO_START "Echec de démarrage"
#define D_PATCH_ISSUE_2186 "Patch issue 2186"
#define D_FAILED_TO_START "Échec de démarrage"
#define D_PATCH_ISSUE_2186 "Correction 2186"
#define D_CONNECTING_TO_AP "Connexion à l'AP"
#define D_IN_MODE "en mode"
#define D_CONNECT_FAILED_NO_IP_ADDRESS "Echec de connexion car aucune adresse IP n'a été reçue"
#define D_CONNECT_FAILED_AP_NOT_REACHED "Echec de connexion car l'AP ne peut-être contacté"
#define D_CONNECT_FAILED_WRONG_PASSWORD "Echec de connexion car le mot de passe de l'AP est incorrect"
#define D_CONNECT_FAILED_AP_TIMEOUT "Echec de connexion avec l'AP, expiré"
#define D_CONNECT_FAILED_NO_IP_ADDRESS "Échec de connexion car aucune adresse IP n'a été reçue"
#define D_CONNECT_FAILED_AP_NOT_REACHED "Échec de connexion car l'AP ne peut-être contacté"
#define D_CONNECT_FAILED_WRONG_PASSWORD "Échec de connexion car le mot de passe de l'AP est incorrect"
#define D_CONNECT_FAILED_AP_TIMEOUT "Échec de connexion avec l'AP, expiré"
#define D_ATTEMPTING_CONNECTION "Tentative de connexion..."
#define D_CHECKING_CONNECTION "Verification connexion..."
#define D_QUERY_DONE "Requête terminé. Services MQTT trouvés"
#define D_CHECKING_CONNECTION "Vérification connexion..."
#define D_QUERY_DONE "Requête terminée. Services MQTT trouvés"
#define D_MQTT_SERVICE_FOUND "Service MQTT trouvé sur"
#define D_FOUND_AT "trouvé à"
#define D_SYSLOG_HOST_NOT_FOUND "Host syslog introuvable"
// settings.ino
#define D_SAVED_TO_FLASH_AT "Enregistré dans la flash à"
#define D_SAVED_TO_FLASH_AT "Enregistré en flash à"
#define D_LOADED_FROM_FLASH_AT "Chargé de la flash à"
#define D_USE_DEFAULTS "Utiliser par défaut"
#define D_ERASED_SECTOR "Secteur effacé"
@ -203,8 +204,8 @@
#define D_WEBSERVER_STOPPED "Serveur web éteint"
#define D_FILE_NOT_FOUND "Fichier introuvable"
#define D_REDIRECTED "Redirection sur le portail captif"
#define D_WIFIMANAGER_SET_ACCESSPOINT_AND_STATION "Wifimanager defini AccessPoint et garde station"
#define D_WIFIMANAGER_SET_ACCESSPOINT "Wifimanager defini AccessPoint"
#define D_WIFIMANAGER_SET_ACCESSPOINT_AND_STATION "Wifimanager définit AccessPoint et garde station"
#define D_WIFIMANAGER_SET_ACCESSPOINT "Wifimanager définit AccessPoint"
#define D_TRYING_TO_CONNECT "Tentative de connexion du module au réseau"
#define D_RESTART_IN "Redémarrage dans"
@ -239,7 +240,7 @@
#define D_SCAN_FOR_WIFI_NETWORKS "Scan des réseaux wifi"
#define D_SCAN_DONE "Scan terminé"
#define D_NO_NETWORKS_FOUND "Aucun réseau trouvé"
#define D_REFRESH_TO_SCAN_AGAIN "Rafraichir pour scanner à nouveau"
#define D_REFRESH_TO_SCAN_AGAIN "Rafraîchir pour scanner à nouveau"
#define D_DUPLICATE_ACCESSPOINT "AccessPoint dupliqué"
#define D_SKIPPING_LOW_QUALITY "Passe car mauvaise qualité"
#define D_RSSI "RSSI"
@ -260,9 +261,9 @@
#define D_WEB_LOG_LEVEL "Niveau de journalisation web"
#define D_SYS_LOG_LEVEL "Niveau syslog"
#define D_MORE_DEBUG "Plus de debug"
#define D_SYSLOG_HOST "Host syslog"
#define D_SYSLOG_PORT "Host syslog"
#define D_TELEMETRY_PERIOD "Période télémetrie"
#define D_SYSLOG_HOST "Hôte syslog"
#define D_SYSLOG_PORT "Port syslog"
#define D_TELEMETRY_PERIOD "Période télémétrie"
#define D_OTHER_PARAMETERS "Autres paramètres"
#define D_WEB_ADMIN_PASSWORD "Mot de passe Web Admin"
@ -274,8 +275,8 @@
#define D_MULTI_DEVICE "multi module"
#define D_SAVE_CONFIGURATION "Enregistrer configuration"
#define D_CONFIGURATION_SAVED "Configuration enregistré"
#define D_CONFIGURATION_RESET "Configuration réinitialisé"
#define D_CONFIGURATION_SAVED "Configuration enregistrée"
#define D_CONFIGURATION_RESET "Configuration réinitialisée"
#define D_PROGRAM_VERSION "Version Programme"
#define D_BUILD_DATE_AND_TIME "Date & Heure de build"
@ -301,12 +302,12 @@
#define D_START_UPGRADE "Lancer la mise à jour"
#define D_UPGRADE_BY_FILE_UPLOAD "Mise à jour par téléchargement fichier"
#define D_UPLOAD_STARTED "Téléchargement lancé"
#define D_UPGRADE_STARTED "Mise à jour lancé"
#define D_UPGRADE_STARTED "Mise à jour lancée"
#define D_UPLOAD_DONE "Téléchargement terminé"
#define D_UPLOAD_ERR_1 "Aucun fichier sélectionné"
#define D_UPLOAD_ERR_2 "Espace insuffisant"
#define D_UPLOAD_ERR_3 "Magic byte n'est pas 0xE9"
#define D_UPLOAD_ERR_4 "La taille de la flash programme est plus grande que la taille réelle de la flash"
#define D_UPLOAD_ERR_4 "La taille du programme à flasher est plus grande que la taille réelle de la mémoire flash"
#define D_UPLOAD_ERR_5 "Erreur comparaison buffer de téléchargement"
#define D_UPLOAD_ERR_6 "Téléchargement échoué. Activation logging 3"
#define D_UPLOAD_ERR_7 "Téléchargement annulé"
@ -319,25 +320,25 @@
#define D_NEED_USER_AND_PASSWORD "Nécessite utilisateur=<username>&password=<password>"
// xdrv_00_mqtt.ino
#define D_FINGERPRINT "Verification empreinte TLS ..."
#define D_TLS_CONNECT_FAILED_TO "Echec de connexion TLS à"
#define D_FINGERPRINT "Vérification empreinte TLS ..."
#define D_TLS_CONNECT_FAILED_TO "Échec de connexion TLS à"
#define D_RETRY_IN "Nouvelle tentative dans"
#define D_VERIFIED "Verifié empreinte "
#define D_INSECURE "Connexion non sécurisée car empreinte non vérifée"
#define D_CONNECT_FAILED_TO "Echec de connexion à"
#define D_VERIFIED "Vérifié par empreinte "
#define D_INSECURE "Connexion non sécurisée car empreinte non vérifiée"
#define D_CONNECT_FAILED_TO "Échec de connexion à"
// xdrv_wemohue.ino
#define D_MULTICAST_DISABLED "Multicast désactivé"
#define D_MULTICAST_REJOINED "Multicast (re)joint"
#define D_MULTICAST_JOIN_FAILED "Multicast echec abonnement"
#define D_FAILED_TO_SEND_RESPONSE "Echec d'envoi réponse"
#define D_MULTICAST_JOIN_FAILED "Multicast échec abonnement"
#define D_FAILED_TO_SEND_RESPONSE "Échec d'envoi réponse"
#define D_WEMO "WeMo"
#define D_WEMO_BASIC_EVENT "WeMo basic event"
#define D_WEMO_EVENT_SERVICE "WeMo event service"
#define D_WEMO_META_SERVICE "WeMo meta service"
#define D_WEMO_SETUP "Réglage WeMo"
#define D_RESPONSE_SENT "Response envoyé"
#define D_RESPONSE_SENT "Réponse envoyée"
#define D_HUE "Hue"
#define D_HUE_BRIDGE_SETUP "Réglage Hue"
@ -355,28 +356,48 @@
#define D_DOMOTICZ_TEMP "Temp"
#define D_DOMOTICZ_TEMP_HUM "Temp,Hum"
#define D_DOMOTICZ_TEMP_HUM_BARO "Temp,Hum,Baro"
#define D_DOMOTICZ_POWER_ENERGY "Power,Energy"
#define D_DOMOTICZ_POWER_ENERGY "Puissance,Énergie"
#define D_DOMOTICZ_ILLUMINANCE "Illuminance"
#define D_DOMOTICZ_COUNT "Count/PM1"
#define D_DOMOTICZ_VOLTAGE "Voltage/PM2,5"
#define D_DOMOTICZ_CURRENT "Current/PM10"
#define D_DOMOTICZ_AIRQUALITY "AirQuality"
#define D_DOMOTICZ_UPDATE_TIMER "Update timer"
#define D_DOMOTICZ_COUNT "Compteur/PM1"
#define D_DOMOTICZ_VOLTAGE "Tension/PM2,5"
#define D_DOMOTICZ_CURRENT "Courant/PM10"
#define D_DOMOTICZ_AIRQUALITY "Qualité de l'air"
#define D_DOMOTICZ_UPDATE_TIMER "Durée de rafraichissement"
// xdrv_09_timers.ino
#define D_CONFIGURE_TIMER "Configure Timer"
#define D_TIMER_PARAMETERS "Timer parameters"
#define D_TIMER_ARM "Arm"
#define D_TIMER_TIME "Time"
#define D_TIMER_DAYS "Days"
#define D_TIMER_REPEAT "Repeat"
#define D_TIMER_OUTPUT "Output"
#define D_CONFIGURE_TIMER "Configuration Timer"
#define D_TIMER_PARAMETERS "Paramètres Timer"
#define D_TIMER_ARM "Armer"
#define D_TIMER_TIME "Temps"
#define D_TIMER_DAYS "Jours"
#define D_TIMER_REPEAT "Répéter"
#define D_TIMER_OUTPUT "Sortie"
#define D_TIMER_ACTION "Action"
// xdrv_10_knx.ino
#define D_CONFIGURE_KNX "Configure KNX"
#define D_KNX_PARAMETERS "KNX Parameters"
#define D_KNX_GENERAL_CONFIG "General"
#define D_KNX_PHYSICAL_ADDRESS "Physical Address"
#define D_KNX_PHYSICAL_ADDRESS_NOTE "( Must be unique on the KNX network )"
#define D_KNX_ENABLE "Enable KNX"
#define D_KNX_GROUP_ADDRESS_TO_WRITE "Data to Send to Group Addresses"
#define D_ADD "Add"
#define D_DELETE "Delete"
#define D_REPLY "Reply"
#define D_KNX_GROUP_ADDRESS_TO_READ "Group Addresses to Receive Data from"
#define D_LOG_KNX "KNX: "
#define D_RECEIVED_FROM "Received from"
#define D_KNX_COMMAND_WRITE "Write"
#define D_KNX_COMMAND_READ "Read"
#define D_KNX_COMMAND_OTHER "Other"
#define D_SENT_TO "sent to"
#define D_KNX_WARNING "The group address ( 0 / 0 / 0 ) is reserved and can not be used."
// xsns_03_energy.ino
#define D_ENERGY_TODAY "Energie aujourd'hui"
#define D_ENERGY_YESTERDAY "Energie hier"
#define D_ENERGY_TOTAL "Energie totale"
#define D_ENERGY_TODAY "Énergie aujourd'hui"
#define D_ENERGY_YESTERDAY "Énergie hier"
#define D_ENERGY_TOTAL "Énergie totale"
// xsns_05_ds18b20.ino
#define D_SENSOR_BUSY "Capteur occupé"
@ -384,11 +405,11 @@
#define D_SENSORS_FOUND "Capteur trouvé"
// xsns_06_dht.ino
#define D_TIMEOUT_WAITING_FOR "Expiré attend pour"
#define D_START_SIGNAL_LOW "Signal démarrage bas"
#define D_START_SIGNAL_HIGH "Signal démarrage haut"
#define D_PULSE "pulse"
#define D_CHECKSUM_FAILURE "Echec checksum"
#define D_TIMEOUT_WAITING_FOR "Expiration du délai"
#define D_START_SIGNAL_LOW "Signal Start bas"
#define D_START_SIGNAL_HIGH "Signal Start haut"
#define D_PULSE "Impulsion"
#define D_CHECKSUM_FAILURE "Erreur checksum"
// xsns_07_sht1x.ino
#define D_SENSOR_DID_NOT_ACK_COMMAND "Le capteur n'a pas ACK la commande"
@ -400,7 +421,7 @@
#define D_PARTICALS_BEYOND "Particules"
// sonoff_template.h
#define D_SENSOR_NONE "None"
#define D_SENSOR_NONE "Aucun"
#define D_SENSOR_DHT11 "DHT11"
#define D_SENSOR_AM2301 "AM2301"
#define D_SENSOR_SI7021 "SI7021"
@ -408,14 +429,14 @@
#define D_SENSOR_I2C_SCL "I2C SCL"
#define D_SENSOR_I2C_SDA "I2C SDA"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_IRSEND "IRsend"
#define D_SENSOR_SWITCH "Switch" // Suffix "1"
#define D_SENSOR_BUTTON "Button" // Suffix "1"
#define D_SENSOR_RELAY "Relay" // Suffix "1i"
#define D_SENSOR_LED "LED" // Suffix "1i"
#define D_SENSOR_PWM "PWM" // Suffix "1"
#define D_SENSOR_COUNTER "Counter" // Suffix "1"
#define D_SENSOR_IRRECV "IRrecv"
#define D_SENSOR_IRSEND "ÉmetIR"
#define D_SENSOR_SWITCH "Inter." // Suffix "1"
#define D_SENSOR_BUTTON "Bouton" // Suffix "1"
#define D_SENSOR_RELAY "Relais" // Suffix "1i"
#define D_SENSOR_LED "LED" // Suffix "1i"
#define D_SENSOR_PWM "PWM" // Suffix "1"
#define D_SENSOR_COUNTER "Compteur" // Suffix "1"
#define D_SENSOR_IRRECV "RécptIR"
#define D_SENSOR_MHZ_RX "MHZ Rx"
#define D_SENSOR_MHZ_TX "MHZ Tx"
#define D_SENSOR_PZEM_RX "PZEM Rx"
@ -424,7 +445,7 @@
#define D_SENSOR_SAIR_TX "SAir Tx"
#define D_SENSOR_SPI_CS "SPI CS"
#define D_SENSOR_SPI_DC "SPI DC"
#define D_SENSOR_BACKLIGHT "BkLight"
#define D_SENSOR_BACKLIGHT "RétroÉcl"
#define D_SENSOR_PMS5003 "PMS5003"
#define D_SENSOR_SDS0X1 "SDS0X1"
#define D_SENSOR_SBR_RX "SerBr Rx"

View File

@ -28,6 +28,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele.
*
* Updated until v5.12.0e
\*********************************************************************/
//#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -373,6 +374,26 @@
#define D_TIMER_OUTPUT "Output"
#define D_TIMER_ACTION "Action"
// xdrv_10_knx.ino
#define D_CONFIGURE_KNX "Configure KNX"
#define D_KNX_PARAMETERS "KNX Parameters"
#define D_KNX_GENERAL_CONFIG "General"
#define D_KNX_PHYSICAL_ADDRESS "Physical Address"
#define D_KNX_PHYSICAL_ADDRESS_NOTE "( Must be unique on the KNX network )"
#define D_KNX_ENABLE "Enable KNX"
#define D_KNX_GROUP_ADDRESS_TO_WRITE "Data to Send to Group Addresses"
#define D_ADD "Add"
#define D_DELETE "Delete"
#define D_REPLY "Reply"
#define D_KNX_GROUP_ADDRESS_TO_READ "Group Addresses to Receive Data from"
#define D_LOG_KNX "KNX: "
#define D_RECEIVED_FROM "Received from"
#define D_KNX_COMMAND_WRITE "Write"
#define D_KNX_COMMAND_READ "Read"
#define D_KNX_COMMAND_OTHER "Other"
#define D_SENT_TO "sent to"
#define D_KNX_WARNING "The group address ( 0 / 0 / 0 ) is reserved and can not be used."
// xdrv_03_energy.ino
#define D_ENERGY_TODAY "Mai Energia"
#define D_ENERGY_YESTERDAY "Tegnapi Energia"

View File

@ -28,6 +28,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele.
*
* Updated until v5.12.0
\*********************************************************************/
#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -373,6 +374,26 @@
#define D_TIMER_OUTPUT "Output"
#define D_TIMER_ACTION "Action"
// xdrv_10_knx.ino
#define D_CONFIGURE_KNX "Configure KNX"
#define D_KNX_PARAMETERS "KNX Parameters"
#define D_KNX_GENERAL_CONFIG "General"
#define D_KNX_PHYSICAL_ADDRESS "Physical Address"
#define D_KNX_PHYSICAL_ADDRESS_NOTE "( Must be unique on the KNX network )"
#define D_KNX_ENABLE "Enable KNX"
#define D_KNX_GROUP_ADDRESS_TO_WRITE "Data to Send to Group Addresses"
#define D_ADD "Add"
#define D_DELETE "Delete"
#define D_REPLY "Reply"
#define D_KNX_GROUP_ADDRESS_TO_READ "Group Addresses to Receive Data from"
#define D_LOG_KNX "KNX: "
#define D_RECEIVED_FROM "Received from"
#define D_KNX_COMMAND_WRITE "Write"
#define D_KNX_COMMAND_READ "Read"
#define D_KNX_COMMAND_OTHER "Other"
#define D_SENT_TO "sent to"
#define D_KNX_WARNING "The group address ( 0 / 0 / 0 ) is reserved and can not be used."
// xdrv_03_energy.ino
#define D_ENERGY_TODAY "Energia Oggi"
#define D_ENERGY_YESTERDAY "Energia Ieri"

View File

@ -28,6 +28,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele.
*
* Updated until v5.12.0l
\*********************************************************************/
//#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -373,6 +374,26 @@
#define D_TIMER_OUTPUT "Uitgang"
#define D_TIMER_ACTION "Actie"
// xdrv_10_knx.ino
#define D_CONFIGURE_KNX "Configure KNX"
#define D_KNX_PARAMETERS "KNX Parameters"
#define D_KNX_GENERAL_CONFIG "General"
#define D_KNX_PHYSICAL_ADDRESS "Physical Address"
#define D_KNX_PHYSICAL_ADDRESS_NOTE "( Must be unique on the KNX network )"
#define D_KNX_ENABLE "Enable KNX"
#define D_KNX_GROUP_ADDRESS_TO_WRITE "Data to Send to Group Addresses"
#define D_ADD "Add"
#define D_DELETE "Delete"
#define D_REPLY "Reply"
#define D_KNX_GROUP_ADDRESS_TO_READ "Group Addresses to Receive Data from"
#define D_LOG_KNX "KNX: "
#define D_RECEIVED_FROM "Received from"
#define D_KNX_COMMAND_WRITE "Write"
#define D_KNX_COMMAND_READ "Read"
#define D_KNX_COMMAND_OTHER "Other"
#define D_SENT_TO "sent to"
#define D_KNX_WARNING "The group address ( 0 / 0 / 0 ) is reserved and can not be used."
// xdrv_03_energy.ino
#define D_ENERGY_TODAY "Verbruik vandaag"
#define D_ENERGY_YESTERDAY "Verbruik gisteren"

View File

@ -28,6 +28,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele.
*
* Updated until v5.12.0d
\*********************************************************************/
//#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -373,6 +374,26 @@
#define D_TIMER_OUTPUT "Output"
#define D_TIMER_ACTION "Action"
// xdrv_10_knx.ino
#define D_CONFIGURE_KNX "Configure KNX"
#define D_KNX_PARAMETERS "KNX Parameters"
#define D_KNX_GENERAL_CONFIG "General"
#define D_KNX_PHYSICAL_ADDRESS "Physical Address"
#define D_KNX_PHYSICAL_ADDRESS_NOTE "( Must be unique on the KNX network )"
#define D_KNX_ENABLE "Enable KNX"
#define D_KNX_GROUP_ADDRESS_TO_WRITE "Data to Send to Group Addresses"
#define D_ADD "Add"
#define D_DELETE "Delete"
#define D_REPLY "Reply"
#define D_KNX_GROUP_ADDRESS_TO_READ "Group Addresses to Receive Data from"
#define D_LOG_KNX "KNX: "
#define D_RECEIVED_FROM "Received from"
#define D_KNX_COMMAND_WRITE "Write"
#define D_KNX_COMMAND_READ "Read"
#define D_KNX_COMMAND_OTHER "Other"
#define D_SENT_TO "sent to"
#define D_KNX_WARNING "The group address ( 0 / 0 / 0 ) is reserved and can not be used."
// xdrv_03_energy.ino
#define D_ENERGY_TODAY "Energia Dzisiaj"
#define D_ENERGY_YESTERDAY "Energia Wczoraj"

View File

@ -28,6 +28,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele.
*
* Updated until v5.12.0
\*********************************************************************/
//#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -373,6 +374,26 @@
#define D_TIMER_OUTPUT "Output"
#define D_TIMER_ACTION "Action"
// xdrv_10_knx.ino
#define D_CONFIGURE_KNX "Configure KNX"
#define D_KNX_PARAMETERS "KNX Parameters"
#define D_KNX_GENERAL_CONFIG "General"
#define D_KNX_PHYSICAL_ADDRESS "Physical Address"
#define D_KNX_PHYSICAL_ADDRESS_NOTE "( Must be unique on the KNX network )"
#define D_KNX_ENABLE "Enable KNX"
#define D_KNX_GROUP_ADDRESS_TO_WRITE "Data to Send to Group Addresses"
#define D_ADD "Add"
#define D_DELETE "Delete"
#define D_REPLY "Reply"
#define D_KNX_GROUP_ADDRESS_TO_READ "Group Addresses to Receive Data from"
#define D_LOG_KNX "KNX: "
#define D_RECEIVED_FROM "Received from"
#define D_KNX_COMMAND_WRITE "Write"
#define D_KNX_COMMAND_READ "Read"
#define D_KNX_COMMAND_OTHER "Other"
#define D_SENT_TO "sent to"
#define D_KNX_WARNING "The group address ( 0 / 0 / 0 ) is reserved and can not be used."
// xdrv_03_energy.ino
#define D_ENERGY_TODAY "Consumo energético de hoje"
#define D_ENERGY_YESTERDAY "Consumo energético de ontem"

View File

@ -28,6 +28,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele.
*
* Updated until v5.12.0b
\*********************************************************************/
//#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -373,6 +374,26 @@
#define D_TIMER_OUTPUT "Output"
#define D_TIMER_ACTION "Action"
// xdrv_10_knx.ino
#define D_CONFIGURE_KNX "Configure KNX"
#define D_KNX_PARAMETERS "KNX Parameters"
#define D_KNX_GENERAL_CONFIG "General"
#define D_KNX_PHYSICAL_ADDRESS "Physical Address"
#define D_KNX_PHYSICAL_ADDRESS_NOTE "( Must be unique on the KNX network )"
#define D_KNX_ENABLE "Enable KNX"
#define D_KNX_GROUP_ADDRESS_TO_WRITE "Data to Send to Group Addresses"
#define D_ADD "Add"
#define D_DELETE "Delete"
#define D_REPLY "Reply"
#define D_KNX_GROUP_ADDRESS_TO_READ "Group Addresses to Receive Data from"
#define D_LOG_KNX "KNX: "
#define D_RECEIVED_FROM "Received from"
#define D_KNX_COMMAND_WRITE "Write"
#define D_KNX_COMMAND_READ "Read"
#define D_KNX_COMMAND_OTHER "Other"
#define D_SENT_TO "sent to"
#define D_KNX_WARNING "The group address ( 0 / 0 / 0 ) is reserved and can not be used."
// xdrv_03_energy.ino
#define D_ENERGY_TODAY "Энергия Сегодня"
#define D_ENERGY_YESTERDAY "Энергия Вчера"

View File

@ -28,6 +28,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele.
*
* Updated until v5.12.0d
\*********************************************************************/
//#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -373,6 +374,26 @@
#define D_TIMER_OUTPUT "Output"
#define D_TIMER_ACTION "Action"
// xdrv_10_knx.ino
#define D_CONFIGURE_KNX "Configure KNX"
#define D_KNX_PARAMETERS "KNX Parameters"
#define D_KNX_GENERAL_CONFIG "General"
#define D_KNX_PHYSICAL_ADDRESS "Physical Address"
#define D_KNX_PHYSICAL_ADDRESS_NOTE "( Must be unique on the KNX network )"
#define D_KNX_ENABLE "Enable KNX"
#define D_KNX_GROUP_ADDRESS_TO_WRITE "Data to Send to Group Addresses"
#define D_ADD "Add"
#define D_DELETE "Delete"
#define D_REPLY "Reply"
#define D_KNX_GROUP_ADDRESS_TO_READ "Group Addresses to Receive Data from"
#define D_LOG_KNX "KNX: "
#define D_RECEIVED_FROM "Received from"
#define D_KNX_COMMAND_WRITE "Write"
#define D_KNX_COMMAND_READ "Read"
#define D_KNX_COMMAND_OTHER "Other"
#define D_SENT_TO "sent to"
#define D_KNX_WARNING "The group address ( 0 / 0 / 0 ) is reserved and can not be used."
// xdrv_03_energy.ino
#define D_ENERGY_TODAY "今日用电量"
#define D_ENERGY_YESTERDAY "昨日用电量"

View File

@ -28,6 +28,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele.
*
* Updated until v5.12.0d
\*********************************************************************/
//#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -373,6 +374,26 @@
#define D_TIMER_OUTPUT "Output"
#define D_TIMER_ACTION "Action"
// xdrv_10_knx.ino
#define D_CONFIGURE_KNX "Configure KNX"
#define D_KNX_PARAMETERS "KNX Parameters"
#define D_KNX_GENERAL_CONFIG "General"
#define D_KNX_PHYSICAL_ADDRESS "Physical Address"
#define D_KNX_PHYSICAL_ADDRESS_NOTE "( Must be unique on the KNX network )"
#define D_KNX_ENABLE "Enable KNX"
#define D_KNX_GROUP_ADDRESS_TO_WRITE "Data to Send to Group Addresses"
#define D_ADD "Add"
#define D_DELETE "Delete"
#define D_REPLY "Reply"
#define D_KNX_GROUP_ADDRESS_TO_READ "Group Addresses to Receive Data from"
#define D_LOG_KNX "KNX: "
#define D_RECEIVED_FROM "Received from"
#define D_KNX_COMMAND_WRITE "Write"
#define D_KNX_COMMAND_READ "Read"
#define D_KNX_COMMAND_OTHER "Other"
#define D_SENT_TO "sent to"
#define D_KNX_WARNING "The group address ( 0 / 0 / 0 ) is reserved and can not be used."
// xdrv_03_energy.ino
#define D_ENERGY_TODAY "今日用電量"
#define D_ENERGY_YESTERDAY "昨日用電量"

View File

@ -49,8 +49,8 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
uint32_t no_power_on_check : 1; // bit 21 (v5.11.1i)
uint32_t mqtt_serial : 1; // bit 22 (v5.12.0f)
uint32_t rules_enabled : 1; // bit 23 (v5.12.0j)
uint32_t spare24 : 1;
uint32_t spare25 : 1;
uint32_t rules_once : 1; // bit 24 (v5.12.0k)
uint32_t knx_enabled : 1; // bit 25 (v5.12.0l) KNX
uint32_t spare26 : 1;
uint32_t spare27 : 1;
uint32_t spare28 : 1;
@ -92,14 +92,14 @@ typedef union {
typedef union {
uint32_t data;
struct {
uint32_t time : 11; // bits 0 - 10 = minutes in a day
uint32_t mode : 5; // bits 11 - 15 = timer modes - Scheduler, Sunrise, Sunset
uint32_t days : 7; // bits 16 - 22 = week day mask
uint32_t device : 4; // bits 23 - 26 = 16 devices
uint32_t power : 2; // bits 27 - 28 = 4 power states - Off, On, Toggle, Blink
uint32_t repeat : 1; // bit 29
uint32_t arm : 1; // bit 30
uint32_t spare : 1; // bit 31
uint32_t time : 11; // bits 0 - 10 = minutes in a day
uint32_t window : 4; // bits 11 - 14 = minutes random window
uint32_t repeat : 1; // bit 15
uint32_t days : 7; // bits 16 - 22 = week day mask
uint32_t device : 4; // bits 23 - 26 = 16 devices
uint32_t power : 2; // bits 27 - 28 = 4 power states - Off, On, Toggle, Blink or Rule
uint32_t mode : 2; // bits 29 - 30 = timer modes - Scheduler, Sunrise, Sunset
uint32_t arm : 1; // bit 31
};
} Timer;
@ -226,13 +226,9 @@ struct SYSCFG {
uint8_t light_speed; // 4A2
uint8_t light_scheme; // 4A3
uint8_t light_width; // 4A4
byte free_4A5[1]; // 4A5
byte knx_GA_registered; // 4A5 Number of Group Address to read
uint16_t light_wakeup; // 4A6
byte free_4A8[1]; // 4A8
byte knx_CB_registered; // 4A8 Number of Group Address to write
char web_password[33]; // 4A9
uint8_t switchmode[MAX_SWITCHES]; // 4CA
char ntp_server[3][33]; // 4CE
@ -256,11 +252,17 @@ struct SYSCFG {
int latitude; // 6B0
int longitude; // 6B4
byte free_6b8[72]; // 6B8
uint16_t knx_physsical_addr; // 6B8 (address_t is a uint16_t)
uint16_t knx_GA_addr[MAX_KNX_GA]; // 6BA (address_t is a uint16_t) x KNX_max_GA
uint16_t knx_CB_addr[MAX_KNX_CB]; // 6CE (address_t is a uint16_t) x KNX_max_CB
byte knx_GA_param[MAX_KNX_GA]; // 6E2 Type of Input (relay changed, button pressed, sensor read <-teleperiod)
byte knx_CB_param[MAX_KNX_CB]; // 6EC Type of Output (set relay, toggle relay, reply sensor value)
char rules[MAX_RULE_SIZE]; // 700
byte free_6f6[266]; // 6F6
// 800 - FFF free locations
char rules[MAX_RULE_SIZE]; // 800 uses 512 bytes in v5.12.0m
// A00 - FFF free locations
} Settings;
struct RTCMEM {

View File

@ -920,21 +920,25 @@ void SettingsDelta()
Settings.sbaudrate = SOFT_BAUDRATE / 1200;
Settings.serial_delimiter = 0xff;
}
if (Settings.version < 0x050C0009) {
memset(&Settings.timer, 0x00, sizeof(Timer) * MAX_TIMERS);
}
// if (Settings.version < 0x050C0009) {
// memset(&Settings.timer, 0x00, sizeof(Timer) * MAX_TIMERS);
// }
if (Settings.version < 0x050C000A) {
Settings.latitude = (int)((double)LATITUDE * 1000000);
Settings.longitude = (int)((double)LONGITUDE * 1000000);
}
if (Settings.version < 0x050C000B) {
memset(&Settings.free_6b8, 0x00, sizeof(Settings.free_6b8));
memset(&Settings.rules, 0x00, sizeof(Settings.rules));
}
if (Settings.version < 0x050C000D) {
memmove(Settings.rules, Settings.rules -256, sizeof(Settings.rules)); // move rules up by 256 bytes
memset(&Settings.timer, 0x00, sizeof(Timer) * MAX_TIMERS); // Reset timers as layout has changed from v5.12.0i
Settings.knx_GA_registered = 0;
Settings.knx_CB_registered = 0;
memset(&Settings.knx_physsical_addr, 0x00, 0x800 - 0x6b8); // Reset until 0x800 for future use
}
Settings.version = VERSION;
SettingsSave(1);
}
}

View File

@ -48,7 +48,9 @@ typedef unsigned long power_t; // Power (Relay) type
#define MAX_FRIENDLYNAMES 4 // Max number of Friendly names
#define MAX_DOMOTICZ_IDX 4 // Max number of Domoticz device, key and switch indices
#define MAX_DOMOTICZ_SNS_IDX 12 // Max number of Domoticz sensors indices
#define MAX_RULE_SIZE 256 // Max number of characters in rules
#define MAX_KNX_GA 10 // Max number of KNX Group Addresses to read that can be set
#define MAX_KNX_CB 10 // Max number of KNX Group Addresses to write that can be set
#define MAX_RULE_SIZE 512 // Max number of characters in rules
#define MODULE SONOFF_BASIC // [Module] Select default model
@ -86,10 +88,10 @@ typedef unsigned long power_t; // Power (Relay) type
#define SERIALLOG_TIMER 600 // Seconds to disable SerialLog
#define OTA_ATTEMPTS 5 // Number of times to try fetching the new firmware
#define INPUT_BUFFER_SIZE 255 // Max number of characters in (serial) command buffer
#define INPUT_BUFFER_SIZE 512 // Max number of characters in (serial and http) command buffer
#define CMDSZ 24 // Max number of characters in command
#define TOPSZ 100 // Max number of characters in topic string
#define LOGSZ 400 // Max number of characters in log
#define LOGSZ 512 // Max number of characters in log
#define MIN_MESSZ 893 // Min number of characters in MQTT message
#ifdef USE_MQTT_TLS
@ -164,9 +166,7 @@ enum LichtSubtypes {LST_NONE, LST_SINGLE, LST_COLDWARM, LST_RGB, LST_RGBW, LST_R
enum LichtSchemes {LS_POWER, LS_WAKEUP, LS_CYCLEUP, LS_CYCLEDN, LS_RANDOM, LS_MAX};
enum XsnsFunctions {FUNC_INIT, FUNC_LOOP, FUNC_EVERY_50_MSECOND, FUNC_EVERY_SECOND, FUNC_PREP_BEFORE_TELEPERIOD, FUNC_JSON_APPEND, FUNC_WEB_APPEND, FUNC_SAVE_BEFORE_RESTART,
FUNC_COMMAND, FUNC_NTP_INIT, FUNC_NTP_SET, FUNC_CLOCK_TIMER,
FUNC_MQTT_SUBSCRIBE, FUNC_MQTT_INIT, FUNC_MQTT_DATA, FUNC_MQTT_DISCONNECTED, FUNC_MQTT_CONNECTED,
FUNC_SET_POWER, FUNC_SHOW_SENSOR};
FUNC_COMMAND, FUNC_MQTT_SUBSCRIBE, FUNC_MQTT_INIT, FUNC_MQTT_DATA, FUNC_SET_POWER, FUNC_SHOW_SENSOR};
const uint8_t kDefaultRfCode[9] PROGMEM = { 0x21, 0x16, 0x01, 0x0E, 0x03, 0x48, 0x2E, 0x1A, 0x00 };

View File

@ -25,7 +25,7 @@
- Select IDE Tools - Flash Size: "1M (no SPIFFS)"
====================================================*/
#define VERSION 0x050C000B // 5.12.0k
#define VERSION 0x050C000D // 5.12.0m
// Location specific includes
#include <core_version.h> // Arduino_Esp8266 version information (ARDUINO_ESP8266_RELEASE and ARDUINO_ESP8266_RELEASE_2_3_0)
@ -185,6 +185,7 @@ uint8_t spi_flg = 0; // SPI configured
uint8_t light_type = 0; // Light types
bool pwm_present = false; // Any PWM channel configured with SetOption15 0
boolean mdns_begun = false;
unsigned long features = 0UL;
char my_version[33]; // Composed version string
char my_hostname[33]; // Composed Wifi hostname
@ -1101,7 +1102,7 @@ void MqttDataHandler(char* topic, byte* data, unsigned int data_len)
/********************************************************************************************/
boolean send_button_power(byte key, byte device, byte state)
boolean SendKey(byte key, byte device, byte state)
{
// key 0 = button_topic
// key 1 = switch_topic
@ -1130,14 +1131,22 @@ boolean send_button_power(byte key, byte device, byte state)
snprintf_P(mqtt_data, sizeof(mqtt_data), GetStateText(state));
}
#ifdef USE_DOMOTICZ
if (!(DomoticzButton(key, device, state, strlen(mqtt_data)))) {
if (!(DomoticzSendKey(key, device, state, strlen(mqtt_data)))) {
MqttPublishDirect(stopic, (key) ? Settings.flag.mqtt_switch_retain : Settings.flag.mqtt_button_retain);
}
#else
MqttPublishDirect(stopic, (key) ? Settings.flag.mqtt_switch_retain : Settings.flag.mqtt_button_retain);
#endif // USE_DOMOTICZ
result = true;
#ifdef USE_RULES
} else {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"%s%d\":{\"State\":%d}}"), (key) ? "Switch" : "Button", device, state);
result = RulesProcess();
#endif // USE_RULES
}
#ifdef USE_KNX
KnxSendButtonPower(key, device, state);
#endif // USE_KNX
return result;
}
@ -1188,6 +1197,9 @@ void ExecuteCommandPower(byte device, byte state)
#ifdef USE_DOMOTICZ
DomoticzUpdatePowerState(device);
#endif // USE_DOMOTICZ
#ifdef USE_KNX
KnxUpdatePowerState(device, power);
#endif // USE_KNX
if (device <= MAX_PULSETIMERS) {
// pulse_timer[(device -1)] = (power & mask) ? Settings.pulse_timer[(device -1)] : 0;
pulse_timer[(device -1)] = (((POWER_ALL_OFF_PULSETIME_ON == Settings.poweronstate) ? ~power : power) & mask) ? Settings.pulse_timer[(device -1)] : 0;
@ -1527,7 +1539,7 @@ void ButtonHandler()
if (!holdbutton[button_index]) button_pressed = true; // Do not allow within 1 second
}
if (button_pressed) {
if (!send_button_power(0, button_index +1, POWER_TOGGLE)) { // Execute Toggle command via MQTT if ButtonTopic is set
if (!SendKey(0, button_index +1, POWER_TOGGLE)) { // Execute Toggle command via MQTT if ButtonTopic is set
ExecuteCommandPower(button_index +1, POWER_TOGGLE); // Execute Toggle command internally
}
}
@ -1536,7 +1548,7 @@ void ButtonHandler()
if (Settings.flag.button_single) { // Allow only single button press for immediate action
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION D_BUTTON "%d " D_IMMEDIATE), button_index +1);
AddLog(LOG_LEVEL_DEBUG);
if (!send_button_power(0, button_index +1, POWER_TOGGLE)) { // Execute Toggle command via MQTT if ButtonTopic is set
if (!SendKey(0, button_index +1, POWER_TOGGLE)) { // Execute Toggle command via MQTT if ButtonTopic is set
ExecuteCommandPower(button_index +1, POWER_TOGGLE); // Execute Toggle command internally
}
} else {
@ -1562,7 +1574,7 @@ void ButtonHandler()
if (Settings.flag.button_restrict) { // Button restriction
if (holdbutton[button_index] == Settings.param[P_HOLD_TIME] * (STATES / 10)) { // Button hold
multipress[button_index] = 0;
send_button_power(0, button_index +1, 3); // Execute Hold command via MQTT if ButtonTopic is set
SendKey(0, button_index +1, 3); // Execute Hold command via MQTT if ButtonTopic is set
}
} else {
if (holdbutton[button_index] == (Settings.param[P_HOLD_TIME] * (STATES / 10)) * hold_time_extent) { // Button held for factor times longer
@ -1588,7 +1600,7 @@ void ButtonHandler()
multipress[button_index] = 1;
}
}
if (single_press && send_button_power(0, button_index + multipress[button_index], POWER_TOGGLE)) { // Execute Toggle command via MQTT if ButtonTopic is set
if (single_press && SendKey(0, button_index + multipress[button_index], POWER_TOGGLE)) { // Execute Toggle command via MQTT if ButtonTopic is set
// Success
} else {
if (multipress[button_index] < 3) { // Single or Double press
@ -1629,7 +1641,7 @@ void SwitchHandler()
if (holdwallswitch[i]) {
holdwallswitch[i]--;
if (0 == holdwallswitch[i]) {
send_button_power(1, i +1, 3); // Execute command via MQTT
SendKey(1, i +1, 3); // Execute command via MQTT
}
}
@ -1682,7 +1694,7 @@ void SwitchHandler()
}
if (switchflag < 3) {
if (!send_button_power(1, i +1, switchflag)) { // Execute command via MQTT
if (!SendKey(1, i +1, switchflag)) { // Execute command via MQTT
ExecuteCommandPower(i +1, switchflag); // Execute command internally (if i < devices_present)
}
}

View File

@ -33,6 +33,11 @@ void WifiWpsStatusCallback(wps_cb_status status);
}
#endif
//#ifdef USE_KNX // Enabling this will fail compilation. It has no impact if not used. (20180417)
#include <esp-knx-ip.h>
void KNX_CB_Action(message_t const &msg, void *arg);
//#endif // USE_KNX
#define USE_DHT // Default DHT11 sensor needs no external library
#ifdef USE_ALL_SENSORS // ===================== Configure sonoff-xxl.bin =========================

View File

@ -498,12 +498,12 @@ const mytmplt kModules[MAXMODULE] PROGMEM = {
GPIO_LED1, // GPIO01 Green LED
GPIO_TXD, // GPIO02 RX - Pin next to TX on the PCB
GPIO_RXD, // GPIO03 TX - Pin next to GND on the PCB
GPIO_USER, // GPIO04 W2 - PWM5
GPIO_PWM5, // GPIO04 W2 - PWM5
GPIO_LED2_INV, // GPIO05 Red LED
0, 0, 0, 0, 0, 0, // Flash connection
GPIO_PWM3, // GPIO12 Blue
GPIO_PWM2, // GPIO13 Green
GPIO_USER, // GPIO14 W1 - PWM4
GPIO_PWM4, // GPIO14 W1 - PWM4
GPIO_PWM1, // GPIO15 Red
0, 0
},

View File

@ -218,7 +218,9 @@ double CharToDouble(char *str)
right *= fac;
}
}
return left + right;
double result = left + right;
if (left < 0) { result = left - right; }
return result;
}
char* dtostrfd(double number, unsigned char prec, char *s)
@ -1376,12 +1378,14 @@ void RtcSecond()
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION "(" D_UTC_TIME ") %s, (" D_DST_TIME ") %s, (" D_STD_TIME ") %s"),
GetTime(0).c_str(), GetTime(2).c_str(), GetTime(3).c_str());
AddLog(LOG_LEVEL_DEBUG);
#ifdef USE_RULES
if (local_time < 1451602800) { // 2016-01-01
XdrvCall(FUNC_NTP_INIT);
strncpy_P(mqtt_data, PSTR("{\"Time\":{\"Initialized\":1}}"), sizeof(mqtt_data));
} else {
XdrvCall(FUNC_NTP_SET);
strncpy_P(mqtt_data, PSTR("{\"Time\":{\"Set\":1}}"), sizeof(mqtt_data));
}
RulesProcess();
#endif // USE_RULES
} else {
ntp_sync_minute++; // Try again in next minute
}
@ -1405,7 +1409,7 @@ void RtcSecond()
}
}
local_time += time_offset;
time_timezone = time_offset / (SECS_PER_HOUR / 10);
time_timezone = time_offset / 360; // (SECS_PER_HOUR / 10) fails as it is defined as UL
}
BreakTime(local_time, RtcTime);
if (!RtcTime.hour && !RtcTime.minute && !RtcTime.second && RtcTime.valid) {
@ -1591,10 +1595,10 @@ void AddLog_P(byte loglevel, const char *formatP, const char *formatP2)
AddLog(loglevel);
}
void AddLogSerial(byte loglevel, uint8_t *buffer, byte count)
void AddLogSerial(byte loglevel, uint8_t *buffer, int count)
{
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_SERIAL D_RECEIVED));
for (byte i = 0; i < count; i++) {
for (int i = 0; i < count; i++) {
snprintf_P(log_data, sizeof(log_data), PSTR("%s %02X"), log_data, *(buffer++));
}
AddLog(loglevel);

View File

@ -202,6 +202,9 @@
// !!! TLS uses a LOT OF MEMORY so be careful to enable other options at the same time !!!
//#define USE_MQTT_TLS // Use TLS for MQTT connection (+53k code, +15k mem)
// -- KNX IP Protocol -----------------------------
//#define USE_KNX // Enable KNX IP Protocol Support (+23k code, +3k3 mem)
// -- HTTP ----------------------------------------
#define USE_WEBSERVER // Enable web server and wifi manager (+66k code, +8k mem)
#define WEB_PORT 80 // Web server Port for User and Admin mode
@ -224,6 +227,9 @@
#define USE_TIMERS_WEB // Add timer webpage support (+4k5 code)
#define USE_SUNRISE // Add support for Sunrise and sunset tools (+16k)
// -- Rules ---------------------------------------
#define USE_RULES // Add support for rules (+4k4 code)
// -- Internal Analog input -----------------------
#define USE_ADC_VCC // Display Vcc in Power status. Disable for use as Analog input on selected devices

View File

@ -196,6 +196,9 @@ const char HTTP_BTN_MENU_MQTT[] PROGMEM =
#endif // USE_DOMOTICZ
"";
const char HTTP_BTN_MENU4[] PROGMEM =
#ifdef USE_KNX
"<br/><form action='kn' method='get'><button>" D_CONFIGURE_KNX "</button></form>"
#endif // USE_KNX
"<br/><form action='lg' method='get'><button>" D_CONFIGURE_LOGGING "</button></form>"
"<br/><form action='co' method='get'><button>" D_CONFIGURE_OTHER "</button></form>"
"<br/>"
@ -349,6 +352,9 @@ void StartWebserver(int type, IPAddress ipweb)
WebServer->on("/dm", HandleDomoticzConfiguration);
#endif // USE_DOMOTICZ
}
#ifdef USE_KNX
WebServer->on("/kn", HandleKNXConfiguration);
#endif // USE_KNX
WebServer->on("/lg", HandleLoggingConfiguration);
WebServer->on("/co", HandleOtherConfiguration);
WebServer->on("/dl", HandleBackupConfiguration);
@ -378,6 +384,9 @@ void StartWebserver(int type, IPAddress ipweb)
}
#endif // USE_EMULATION
WebServer->onNotFound(HandleNotFound);
#ifdef USE_KNX
KNXStart();
#endif // USE_KNX
}
reset_web_log_flag = 0;
WebServer->begin(); // Web server start

View File

@ -42,11 +42,11 @@
enum MqttCommands {
CMND_MQTTHOST, CMND_MQTTPORT, CMND_MQTTRETRY, CMND_STATETEXT, CMND_MQTTFINGERPRINT, CMND_MQTTCLIENT,
CMND_MQTTUSER, CMND_MQTTPASSWORD, CMND_FULLTOPIC, CMND_PREFIX, CMND_GROUPTOPIC, CMND_TOPIC,
CMND_MQTTUSER, CMND_MQTTPASSWORD, CMND_FULLTOPIC, CMND_PREFIX, CMND_GROUPTOPIC, CMND_TOPIC, CMND_PUBLISH,
CMND_BUTTONTOPIC, CMND_SWITCHTOPIC, CMND_BUTTONRETAIN, CMND_SWITCHRETAIN, CMND_POWERRETAIN, CMND_SENSORRETAIN };
const char kMqttCommands[] PROGMEM =
D_CMND_MQTTHOST "|" D_CMND_MQTTPORT "|" D_CMND_MQTTRETRY "|" D_CMND_STATETEXT "|" D_CMND_MQTTFINGERPRINT "|" D_CMND_MQTTCLIENT "|"
D_CMND_MQTTUSER "|" D_CMND_MQTTPASSWORD "|" D_CMND_FULLTOPIC "|" D_CMND_PREFIX "|" D_CMND_GROUPTOPIC "|" D_CMND_TOPIC "|"
D_CMND_MQTTUSER "|" D_CMND_MQTTPASSWORD "|" D_CMND_FULLTOPIC "|" D_CMND_PREFIX "|" D_CMND_GROUPTOPIC "|" D_CMND_TOPIC "|" D_CMND_PUBLISH "|"
D_CMND_BUTTONTOPIC "|" D_CMND_SWITCHTOPIC "|" D_CMND_BUTTONRETAIN "|" D_CMND_SWITCHRETAIN "|" D_CMND_POWERRETAIN "|" D_CMND_SENSORRETAIN ;
uint8_t mqtt_retry_counter = 1; // MQTT connection retry counter
@ -316,8 +316,10 @@ void MqttDisconnected(int state)
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_MQTT D_CONNECT_FAILED_TO " %s:%d, rc %d. " D_RETRY_IN " %d " D_UNIT_SECOND),
Settings.mqtt_host, Settings.mqtt_port, state, mqtt_retry_counter);
AddLog(LOG_LEVEL_INFO);
XdrvCall(FUNC_MQTT_DISCONNECTED);
#ifdef USE_RULES
strncpy_P(mqtt_data, PSTR("{\"MQTT\":{\"Disconnected\":1}}"), sizeof(mqtt_data));
RulesProcess();
#endif // USE_RULES
}
void MqttConnected()
@ -369,12 +371,17 @@ void MqttConnected()
tele_period = Settings.tele_period -9;
}
status_update_timer = 2;
#ifdef USE_RULES
strncpy_P(mqtt_data, PSTR("{\"System\":{\"Boot\":1}}"), sizeof(mqtt_data));
RulesProcess();
#endif // USE_RULES
XdrvCall(FUNC_MQTT_INIT);
}
mqtt_initial_connection_state = 0;
XdrvCall(FUNC_MQTT_CONNECTED);
#ifdef USE_RULES
strncpy_P(mqtt_data, PSTR("{\"MQTT\":{\"Connected\":1}}"), sizeof(mqtt_data));
RulesProcess();
#endif // USE_RULES
}
#ifdef USE_MQTT_TLS
@ -614,6 +621,22 @@ bool MqttCommand()
}
snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, index, Settings.mqtt_prefix[index -1]);
}
else if (CMND_PUBLISH == command_code) {
if (data_len > 0) {
char *mqtt_part = strtok(dataBuf, " ");
if (mqtt_part) {
snprintf(stemp1, sizeof(stemp1), mqtt_part);
mqtt_part = strtok(NULL, " ");
if (mqtt_part) {
snprintf(mqtt_data, sizeof(mqtt_data), mqtt_part);
} else {
mqtt_data[0] = '\0';
}
MqttPublishDirect(stemp1, false);
snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, D_JSON_DONE);
}
}
}
else if (CMND_GROUPTOPIC == command_code) {
if ((data_len > 0) && (data_len < sizeof(Settings.mqtt_grptopic))) {
MakeValidMqtt(0, dataBuf);
@ -658,7 +681,7 @@ bool MqttCommand()
strlcpy(Settings.button_topic, mqtt_topic, sizeof(Settings.button_topic));
if (!payload) {
for(i = 1; i <= MAX_KEYS; i++) {
send_button_power(0, i, 9); // Clear MQTT retain in broker
SendKey(0, i, 9); // Clear MQTT retain in broker
}
}
Settings.flag.mqtt_button_retain = payload;
@ -670,7 +693,7 @@ bool MqttCommand()
strlcpy(Settings.button_topic, mqtt_topic, sizeof(Settings.button_topic));
if (!payload) {
for(i = 1; i <= MAX_SWITCHES; i++) {
send_button_power(1, i, 9); // Clear MQTT retain in broker
SendKey(1, i, 9); // Clear MQTT retain in broker
}
}
Settings.flag.mqtt_switch_retain = payload;

View File

@ -265,7 +265,7 @@ boolean DomoticzCommand()
return serviced;
}
boolean DomoticzButton(byte key, byte device, byte state, byte svalflg)
boolean DomoticzSendKey(byte key, byte device, byte state, byte svalflg)
{
if ((Settings.domoticz_key_idx[device -1] || Settings.domoticz_switch_idx[device -1]) && (svalflg)) {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"command\":\"switchlight\",\"idx\":%d,\"switchcmd\":\"%s\"}"),

View File

@ -27,6 +27,7 @@
* Arm 0 = Off, 1 = On
* Mode 0 = Schedule, 1 = Sunrise, 2 = Sunset
* Time hours:minutes
* Window minutes (0..15)
* Days 7 day character mask starting with Sunday (SMTWTFS). 0 or - = Off, any other value = On
* Repeat 0 = Execute once, 1 = Execute again
* Output 1..16
@ -46,6 +47,7 @@ const char kTimerCommands[] PROGMEM = D_CMND_TIMER "|" D_CMND_TIMERS
;
uint16_t timer_last_minute = 60;
int8_t timer_window[MAX_TIMERS] = { 0 };
#ifdef USE_SUNRISE
/*********************************************************************************************\
@ -123,9 +125,12 @@ void DuskTillDawn(uint8_t *hour_up,uint8_t *minute_up, uint8_t *hour_down, uint8
h (D) = -18.0 astronomische Dämmerung
*/
double h = -50/60.0*RAD;
double B = ((double)Settings.latitude/1000000) * RAD; // geographische Breite
double GeographischeLaenge = (double)Settings.longitude/1000000;
double Zeitzone = (double)time_timezone / 10;
double B = (((double)Settings.latitude)/1000000) * RAD; // geographische Breite
double GeographischeLaenge = ((double)Settings.longitude)/1000000;
// double Zeitzone = 0; //Weltzeit
// double Zeitzone = 1; //Winterzeit
// double Zeitzone = 2.0; //Sommerzeit
double Zeitzone = ((double)time_timezone) / 10;
double Zeitgleichung = BerechneZeitgleichung(&DK, T);
double Minuten = Zeitgleichung * 60.0;
double Zeitdifferenz = 12.0*acos((sin(h) - sin(B)*sin(DK)) / (cos(B)*cos(DK)))/pi;
@ -175,6 +180,43 @@ void DuskTillDawn(uint8_t *hour_up,uint8_t *minute_up, uint8_t *hour_down, uint8
*minute_down = UntergangMinuten;
}
void ApplyTimerOffsets(Timer *duskdawn)
{
uint8_t hour[2];
uint8_t minute[2];
Timer stored = (Timer)*duskdawn;
// replace hours, minutes by sunrise
DuskTillDawn(&hour[0], &minute[0], &hour[1], &minute[1]);
uint8_t mode = (duskdawn->mode -1) &1;
duskdawn->time = (hour[mode] *60) + minute[mode];
// apply offsets, check for over- and underflows
uint16_t timeBuffer;
if ((uint16_t)stored.time > 720) {
// negative offset, time after 12:00
timeBuffer = (uint16_t)stored.time - 720;
// check for underflow
if (timeBuffer > (uint16_t)duskdawn->time) {
timeBuffer = 1440 - (timeBuffer - (uint16_t)duskdawn->time);
duskdawn->days = duskdawn->days >> 1;
duskdawn->days = duskdawn->days |= (stored.days << 6);
} else {
timeBuffer = (uint16_t)duskdawn->time - timeBuffer;
}
} else {
// positive offset
timeBuffer = (uint16_t)duskdawn->time + (uint16_t)stored.time;
// check for overflow
if (timeBuffer > 1440) {
timeBuffer -= 1440;
duskdawn->days = duskdawn->days << 1;
duskdawn->days = duskdawn->days |= (stored.days >> 6);
}
}
duskdawn->time = timeBuffer;
}
String GetSun(byte dawn)
{
char stime[6];
@ -202,33 +244,52 @@ uint16_t GetSunMinutes(byte dawn)
/*******************************************************************************************/
void TimerSetRandomWindow(byte index)
{
timer_window[index] = 0;
if (Settings.timer[index].window) {
timer_window[index] = (random(0, (Settings.timer[index].window << 1) +1)) - Settings.timer[index].window; // -15 .. 15
}
}
void TimerSetRandomWindows()
{
for (byte i = 0; i < MAX_TIMERS; i++) { TimerSetRandomWindow(i); }
}
void TimerEverySecond()
{
if (RtcTime.valid) {
if (!RtcTime.hour && !RtcTime.minute && !RtcTime.second) { TimerSetRandomWindows(); } // Midnight
if (RtcTime.minute != timer_last_minute) { // Execute every minute only once
timer_last_minute = RtcTime.minute;
uint16_t time = (RtcTime.hour *60) + RtcTime.minute;
int16_t time = (RtcTime.hour *60) + RtcTime.minute;
uint8_t days = 1 << (RtcTime.day_of_week -1);
for (byte i = 0; i < MAX_TIMERS; i++) {
if (Settings.timer[i].device >= devices_present) Settings.timer[i].data = 0; // Reset timer due to change in devices present
uint16_t set_time = Settings.timer[i].time;
Timer xtimer = Settings.timer[i];
uint16_t set_time = xtimer.time;
#ifdef USE_SUNRISE
if ((1 == Settings.timer[i].mode) || (2 == Settings.timer[i].mode)) { // Sunrise or Sunset
set_time = GetSunMinutes(Settings.timer[i].mode -1);
if ((1 == xtimer.mode) || (2 == xtimer.mode)) { // Sunrise or Sunset
ApplyTimerOffsets(&xtimer);
set_time = xtimer.time;
}
#endif
if (Settings.timer[i].arm) {
if (xtimer.arm) {
set_time += timer_window[i]; // Add random time offset
if (set_time < 0) { set_time == 0; } // Stay today;
if (set_time > 1439) { set_time == 1439; }
if (time == set_time) {
if (Settings.timer[i].days & days) {
Settings.timer[i].arm = Settings.timer[i].repeat;
if (xtimer.days & days) {
Settings.timer[i].arm = xtimer.repeat;
#ifdef USE_RULES
if (3 == Settings.timer[i].power) { // Blink becomes Rule disregarding device and allowing use of Backlog commands
XdrvMailbox.index = i;
XdrvCall(FUNC_CLOCK_TIMER);
if (3 == xtimer.power) { // Blink becomes Rule disregarding device and allowing use of Backlog commands
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"Clock\":{\"Timer\":%d}}"), i +1);
RulesProcess();
} else
#endif
ExecuteCommandPower(Settings.timer[i].device +1, Settings.timer[i].power);
#endif // USE_RULES
ExecuteCommandPower(xtimer.device +1, xtimer.power);
}
}
}
@ -241,17 +302,22 @@ void PrepShowTimer(uint8_t index)
{
char days[8] = { 0 };
index--;
Timer xtimer = Settings.timer[index -1];
for (byte i = 0; i < 7; i++) {
uint8_t mask = 1 << i;
snprintf(days, sizeof(days), "%s%d", days, ((Settings.timer[index].days & mask) > 0));
snprintf(days, sizeof(days), "%s%d", days, ((xtimer.days & mask) > 0));
}
#ifdef USE_SUNRISE
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s\"" D_CMND_TIMER "%d\":{\"" D_JSON_TIMER_ARM "\":%d,\"" D_JSON_TIMER_MODE "\":%d,\"" D_JSON_TIMER_TIME "\":\"%02d:%02d\",\"" D_JSON_TIMER_DAYS "\":\"%s\",\"" D_JSON_TIMER_REPEAT "\":%d,\"" D_JSON_TIMER_OUTPUT "\":%d,\"" D_JSON_TIMER_ACTION "\":%d}"),
mqtt_data, index +1, Settings.timer[index].arm, Settings.timer[index].mode, Settings.timer[index].time / 60, Settings.timer[index].time % 60, days, Settings.timer[index].repeat, Settings.timer[index].device +1, Settings.timer[index].power);
int16_t hour = xtimer.time / 60;
if ((1 == xtimer.mode) || (2 == xtimer.mode)) { // Sunrise or Sunset
if (hour > 11) { hour = (hour -12) * -1; }
}
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s\"" D_CMND_TIMER "%d\":{\"" D_JSON_TIMER_ARM "\":%d,\"" D_JSON_TIMER_MODE "\":%d,\"" D_JSON_TIMER_TIME "\":\"%02d:%02d\",\"" D_JSON_TIMER_WINDOW "\":%d,\"" D_JSON_TIMER_DAYS "\":\"%s\",\"" D_JSON_TIMER_REPEAT "\":%d,\"" D_JSON_TIMER_OUTPUT "\":%d,\"" D_JSON_TIMER_ACTION "\":%d}"),
mqtt_data, index, xtimer.arm, xtimer.mode, hour, xtimer.time % 60, xtimer.window, days, xtimer.repeat, xtimer.device +1, xtimer.power);
#else
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s\"" D_CMND_TIMER "%d\":{\"" D_JSON_TIMER_ARM "\":%d,\"" D_JSON_TIMER_TIME "\":\"%02d:%02d\",\"" D_JSON_TIMER_DAYS "\":\"%s\",\"" D_JSON_TIMER_REPEAT "\":%d,\"" D_JSON_TIMER_OUTPUT "\":%d,\"" D_JSON_TIMER_ACTION "\":%d}"),
mqtt_data, index +1, Settings.timer[index].arm, Settings.timer[index].time / 60, Settings.timer[index].time % 60, days, Settings.timer[index].repeat, Settings.timer[index].device +1, Settings.timer[index].power);
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s\"" D_CMND_TIMER "%d\":{\"" D_JSON_TIMER_ARM "\":%d,\"" D_JSON_TIMER_TIME "\":\"%02d:%02d\",\"" D_JSON_TIMER_WINDOW "\":%d,\"" D_JSON_TIMER_DAYS "\":\"%s\",\"" D_JSON_TIMER_REPEAT "\":%d,\"" D_JSON_TIMER_OUTPUT "\":%d,\"" D_JSON_TIMER_ACTION "\":%d}"),
mqtt_data, index, xtimer.arm, xtimer.time / 60, xtimer.time % 60, xtimer.window, days, xtimer.repeat, xtimer.device +1, xtimer.power);
#endif // USE_SUNRISE
}
@ -298,24 +364,30 @@ boolean TimerCommand()
#endif
if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_TIME))].success()) {
uint16_t itime = 0;
uint8_t value = 0;
int8_t value = 0;
char time_str[10];
snprintf(time_str, sizeof(time_str), root[parm_uc]);
const char *substr = strtok(time_str, ":");
if (substr != NULL) {
value = atoi(substr);
if (value > 23) value = 23;
if (value < 0) { value = abs(value) +12; } // Allow entering timer offset from -11:59 to -00:01 converted to 12:01 to 23:59
if (value > 23) { value = 23; }
itime = value * 60;
substr = strtok(NULL, ":");
if (substr != NULL) {
value = atoi(substr);
if (value > 59) value = 59;
if (value < 0) { value = 0; }
if (value > 59) { value = 59; }
itime += value;
}
}
Settings.timer[index].time = itime;
}
if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_WINDOW))].success()) {
Settings.timer[index].window = (uint8_t)root[parm_uc] & 0x0F;
TimerSetRandomWindow(index);
}
if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_DAYS))].success()) {
// SMTWTFS = 1234567 = 0011001 = 00TW00S = --TW--S
Settings.timer[index].days = 0;
@ -325,7 +397,7 @@ boolean TimerCommand()
uint8_t i = 0;
while ((ch != '\0') && (i < 7)) {
ch = *tday++;
if (ch == '-') ch = '0';
if (ch == '-') { ch = '0'; }
uint8_t mask = 1 << i++;
Settings.timer[index].days |= (ch == '0') ? 0 : mask;
}
@ -364,9 +436,9 @@ boolean TimerCommand()
} else {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,"), mqtt_data);
}
jsflg = 1;
jsflg++;
PrepShowTimer(i +1);
if ((strlen(mqtt_data) > (LOGSZ - TOPSZ - 20)) || (i == MAX_TIMERS -1)) {
if (jsflg > 3) {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}}"), mqtt_data);
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_TIMERS));
jsflg = 0;
@ -414,28 +486,57 @@ const char HTTP_TIMER_SCRIPT[] PROGMEM =
"q.appendChild(o);"
"}"
#ifdef USE_SUNRISE
"function gt(){" // Set hours and minutas according to mode
"function gt(){" // Set hours and minutes according to mode
"var m,p,q;"
"m=qs('input[name=\"rd\"]:checked').value;" // Get mode
"if(m==0){p=pt[ct]&0x7FF;}" // Schedule time
"if(m==1){p=pt[" STR(MAX_TIMERS) "];}" // Sunrise
"if(m==2){p=pt[" STR(MAX_TIMERS +1) "];}" // Sunset
"q=Math.floor(p/60);if(q<10){q='0'+q;}qs('#ho').value=q;" // Set hours
"q=p%60;if(q<10){q='0'+q;}qs('#mi').value=q;" // Set minutes
"p=pt[ct]&0x7FF;" // Get time
"if(m==0){" // Time is set
"q=Math.floor(p/60);if(q<10){q='0'+q;}qs('#ho').value=q;" // Set hours
"q=p%60;if(q<10){q='0'+q;}qs('#mi').value=q;" // Set minutes
"so(0);" // Schedule time, hide offset span
"}"
"if((m==1)||(m==2)){" // Sunrise or sunset is set
"q=Math.floor(p/60);" // Parse hours
"if(q>=12){q-=12;qs('#dr').selectedIndex=1;}" // Negative offset
"else{qs('#dr').selectedIndex=0;}"
"if(q<10){q='0'+q;}qs('#ho').value=q;" // Set offset hours
"q=p%60;if(q<10){q='0'+q;}qs('#mi').value=q;" // Set offset minutes
"so(1);" // Show offset span
"}"
"}"
"function so(b){" // Hide or show offset items
"o=qs('#ho');"
"e=o.childElementCount;"
"if(b==1){"
"qs('#dr').disabled='';"
"if(e>12){for(i=12;i<=23;i++){o.removeChild(o.lastElementChild);}}" // Create offset hours select options
"}else{"
"qs('#dr').disabled='disabled';"
"if(e<23){for(i=12;i<=23;i++){ce(i,o);}}" // Create hours select options
"}"
"}"
#endif
"function st(){" // Save parameters to hidden area
"var i,n,p,s;"
"s=0;"
"n=1<<30;if(eb('a0').checked){s|=n;}" // Get arm
"n=1<<29;if(eb('r0').checked){s|=n;}" // Get repeat
"var i,l,m,n,p,s;"
"m=0;s=0;"
"n=1<<31;if(eb('a0').checked){s|=n;}" // Get arm
"n=1<<15;if(eb('r0').checked){s|=n;}" // Get repeat
"for(i=0;i<7;i++){n=1<<(16+i);if(eb('w'+i).checked){s|=n;}}" // Get weekdays
#ifdef USE_SUNRISE
"s|=(qs('input[name=\"rd\"]:checked').value<<11);" // Get mode
"m=qs('input[name=\"rd\"]:checked').value;" // Check mode
"s|=(qs('input[name=\"rd\"]:checked').value<<29);" // Get mode
#endif
"s|=(eb('p1').value<<27);" // Get power
"s|=(qs('#d1').selectedIndex<<23);" // Get device
"s|=((qs('#ho').selectedIndex*60)+qs('#mi').selectedIndex)&0x7FF;" // Get time
"l=((qs('#ho').selectedIndex*60)+qs('#mi').selectedIndex)&0x7FF;"
"if(m==0){s|=l;}" // Get time
#ifdef USE_SUNRISE
"if((m==1)||(m==2)){"
"if(qs('#dr').selectedIndex>0){l+=720;}" // If negative offset, add 12h to given offset time
"s|=l&0x7FF;" // Save offset instead of time
"}"
#endif
"s|=((qs('#mw').selectedIndex)&0x0F)<<11;" // Get window minutes
"pt[ct]=s;"
"eb('t0').value=pt.join();" // Save parameters from array to hidden area
"}"
@ -448,26 +549,31 @@ const char HTTP_TIMER_SCRIPT[] PROGMEM =
"e.style.cssText=\"background-color:#fff;color:#000;font-weight:bold;\";" // Change style to tab/button used to open content
"s=pt[ct];" // Get parameters from array
#ifdef USE_SUNRISE
"p=(s>>11)&3;eb('b'+p).checked=1;" // Set mode
"p=(s>>29)&3;eb('b'+p).checked=1;" // Set mode
"gt();" // Set hours and minutes according to mode
#else
"p=s&0x7FF;" // Get time
"q=Math.floor(p/60);if(q<10){q='0'+q;}qs('#ho').value=q;" // Set hours
"q=p%60;if(q<10){q='0'+q;}qs('#mi').value=q;" // Set minutes
#endif
"q=(s>>11)&0xF;if(q<10){q='0'+q;}qs('#mw').value=q;" // Set window minutes
"for(i=0;i<7;i++){p=(s>>(16+i))&1;eb('w'+i).checked=p;}" // Set weekdays
"p=(s>>23)&0xF;qs('#d1').value=p+1;" // Set device
"p=(s>>27)&3;eb('p1').value=p;" // Set power
"p=(s>>29)&1;eb('r0').checked=p;" // Set repeat
"p=(s>>30)&1;eb('a0').checked=p;" // Set arm
"p=(s>>15)&1;eb('r0').checked=p;" // Set repeat
"p=(s>>31)&1;eb('a0').checked=p;" // Set arm
"}"
"function it(){" // Initialize elements and select first tab
"var b,i,o,s;"
"pt=eb('t0').value.split(',').map(Number);" // Get parameters from hidden area to array
"s='';for(i=0;i<" STR(MAX_TIMERS) ";i++){b='';if(0==i){b=\" id='dP'\";}s+=\"<button type='button' class='tl' onclick='ot(\"+i+\",this)'\"+b+\">\"+(i+1)+\"</button>\"}"
"eb('bt').innerHTML=s;" // Create tabs
#ifdef USE_SUNRISE // NEW: Create offset options (+/- up to 11h, 59m)
"o=qs('#dr');ce('+',o);ce('-',o);" // Create offset direction select options
#endif
"o=qs('#ho');for(i=0;i<=23;i++){ce((i<10)?('0'+i):i,o);}" // Create hours select options
"o=qs('#mi');for(i=0;i<=59;i++){ce((i<10)?('0'+i):i,o);}" // Create minutes select options
"o=qs('#mw');for(i=0;i<=15;i++){ce((i<10)?('0'+i):i,o);}" // Create window minutes select options
"o=qs('#d1');for(i=0;i<}1;i++){ce(i+1,o);}" // Create devices
"var a='" D_DAY3LIST "';"
"s='';for(i=0;i<7;i++){s+=\"<input style='width:5%;' id='w\"+i+\"' name='w\"+i+\"' type='checkbox'><b>\"+a.substring(i*3,(i*3)+3)+\"</b>\"}"
@ -477,7 +583,7 @@ const char HTTP_TIMER_SCRIPT[] PROGMEM =
const char HTTP_TIMER_STYLE[] PROGMEM =
".tl{float:left;border-radius:0;border:1px solid #fff;padding:1px;width:6.25%;}"
#ifdef USE_SUNRISE
"input[type='radio']{width:13px;height:34px;margin-top:-1px;margin-right:8px;vertical-align:middle;}"
"input[type='radio']{width:13px;height:24px;margin-top:-1px;margin-right:8px;vertical-align:middle;}"
#endif
"</style>";
const char HTTP_FORM_TIMER[] PROGMEM =
@ -503,22 +609,22 @@ const char HTTP_FORM_TIMER1[] PROGMEM =
"<input style='width:5%;' id='r0' name='r0' type='checkbox'><b>" D_TIMER_REPEAT "</b>"
"</div><br/>"
"<div>"
// "<b>Time</b>&nbsp;<input type='time' style='width:25%;' id='s1' name='s1' value='00:00' pattern='[0-9]{2}:[0-9]{2}'>"
#ifdef USE_SUNRISE
"<fieldset style='width:299px;margin:auto;text-align:left;border:0;'>"
"<input id='b0' name='rd' type='radio' value='0' onclick='gt();'><b>" D_TIMER_TIME "</b>&nbsp;"
"<span><select style='width:60px;' id='ho' name='ho' onclick='eb(\"b0\").checked=1;'></select></span>"
"&nbsp;" D_HOUR_MINUTE_SEPARATOR "&nbsp;"
"<span><select style='width:60px;' id='mi' name='mi' onclick='eb(\"b0\").checked=1;'></select></span><br/>"
"<input id='b1' name='rd' type='radio' value='1' onclick='gt();'><b>" D_SUNRISE "</b><br/>"
"<input id='b2' name='rd' type='radio' value='2' onclick='gt();'><b>" D_SUNSET "</b><br/>"
"<input id='b0' name='rd' type='radio' value='0' onclick='gt();'><b>" D_TIMER_TIME "</b><br/>"
"<input id='b1' name='rd' type='radio' value='1' onclick='gt();'><b>" D_SUNRISE "</b> (}8)<br/>"
"<input id='b2' name='rd' type='radio' value='2' onclick='gt();'><b>" D_SUNSET "</b> (}9)<br/>"
"</fieldset>"
"<span><select style='width:46px;' id='dr' name='dr'></select></span>"
"&nbsp;"
#else
"<b>" D_TIMER_TIME "</b>&nbsp;"
#endif // USE_SUNRISE
"<span><select style='width:60px;' id='ho' name='ho'></select></span>"
"&nbsp;" D_HOUR_MINUTE_SEPARATOR "&nbsp;"
"<span><select style='width:60px;' id='mi' name='mi'></select></span>"
#endif // USE_SUNRISE
"&emsp;<b>+/-</b>&nbsp;"
"<span><select style='width:60px;' id='mw' name='mw'></select></span>"
"</div><br/>"
"<div id='ds' name='ds'></div>";
const char HTTP_FORM_TIMER2[] PROGMEM =
@ -541,17 +647,15 @@ void HandleTimerConfiguration()
page.replace(F("</style>"), FPSTR(HTTP_TIMER_STYLE));
page += FPSTR(HTTP_FORM_TIMER);
for (byte i = 0; i < MAX_TIMERS; i++) {
if (i > 0) page += F(",");
if (i > 0) { page += F(","); }
page += String(Settings.timer[i].data);
}
#ifdef USE_SUNRISE
page += F(","); page += String(GetSunMinutes(0)); // Add Sunrise
page += F(","); page += String(GetSunMinutes(1)); // Add Sunset
#endif // USE_SUNRISE
page += FPSTR(HTTP_FORM_TIMER1);
page.replace(F("}1"), String(devices_present));
#ifdef USE_SUNRISE
page.replace(F("299"), String(180 + (strlen(D_TIMER_TIME) *10))); // Fix string length to keep radios centered
page.replace(F("}8"), GetSun(0)); // Add Sunrise
page.replace(F("}9"), GetSun(1)); // Add Sunset
page.replace(F("299"), String(100 + (strlen(D_SUNSET) *12))); // Fix string length to keep radios centered
#endif // USE_SUNRISE
page += FPSTR(HTTP_FORM_END);
page.replace(F("type='submit'"), FPSTR(HTTP_FORM_TIMER2));
@ -572,10 +676,9 @@ void TimerSaveSettings()
timer.data = strtol(p, &p, 10);
p++; // Skip comma
if (timer.time < 1440) {
#ifdef USE_SUNRISE
if ((1 == timer.mode) || (2 == timer.mode)) timer.time = Settings.timer[i].time; // Do not save time on Sunrise or Sunset
#endif
bool flag = (timer.window != Settings.timer[i].window);
Settings.timer[i].data = timer.data;
if (flag) TimerSetRandomWindow(i);
}
snprintf_P(log_data, sizeof(log_data), PSTR("%s%s0x%08X"), log_data, (i > 0)?",":"", Settings.timer[i].data);
}
@ -595,6 +698,9 @@ boolean Xdrv09(byte function)
boolean result = false;
switch (function) {
case FUNC_INIT:
TimerSetRandomWindows();
break;
case FUNC_EVERY_SECOND:
TimerEverySecond();
break;

443
sonoff/xdrv_10_rules.ino Normal file
View File

@ -0,0 +1,443 @@
/*
xdrv_10_rules.ino - rule support for Sonoff-Tasmota
Copyright (C) 2018 ESP Easy Group and Theo Arends
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_RULES
/*********************************************************************************************\
* Rules based heavily on ESP Easy implementation
*
* Inspiration: https://github.com/letscontrolit/ESPEasy
*
* Add rules using the following, case insensitive, format:
* on <trigger1> do <commands> endon on <trigger2> do <commands> endon ..
*
* Examples:
* on System#Boot do Color 001000 endon
* on INA219#Current>0.100 do Dimmer 10 endon
* on INA219#Current>0.100 do Backlog Dimmer 10;Color 10,0,0 endon
* on INA219#Current>0.100 do Backlog Dimmer 10;Color 100000 endon on System#Boot do color 001000 endon
* on ds18b20#temperature>23 do power off endon on ds18b20#temperature<22 do power on endon
* on mqtt#connected do color 000010 endon
* on mqtt#disconnected do color 00100C endon
* on time#initialized do color 001000 endon
* on time#set do color 001008 endon
* on clock#timer=3 do color 080800 endon
* on rules#timer=1 do color 080800 endon
* on mqtt#connected do color 000010 endon on mqtt#disconnected do color 001010 endon on time#initialized do color 001000 endon on time#set do backlog color 000810;ruletimer1 10 endon on rules#timer=1 do color 080800 endon
* on event#anyname do color 100000 endon
* on event#anyname do color %value% endon
* on power1#state=1 do color 001000 endon
* on button1#state do publish cmnd/ring2/power %value% endon on button2#state do publish cmnd/strip1/power %value% endon
* on switch1#state do power2 %value% endon
*
* Notes:
* Spaces after <on>, around <do> and before <endon> are mandatory
* System#Boot is initiated after MQTT is connected due to command handling preparation
* Control rule triggering with command:
* Rule 0 = Rules disabled (Off)
* Rule 1 = Rules enabled (On)
* Rule 2 = Toggle rules state
* Rule 4 = Perform commands as long as trigger is met (Once OFF)
* Rule 5 = Perform commands once until trigger is not met (Once ON)
* Rule 6 = Toggle Once state
* Execute an event like:
* Event anyname=001000
* Set a RuleTimer to 100 seconds like:
* RuleTimer2 100
\*********************************************************************************************/
#define MAX_RULE_TIMERS 8
#define ULONG_MAX 0xffffffffUL
#define D_CMND_RULE "Rule"
#define D_CMND_RULETIMER "RuleTimer"
#define D_CMND_EVENT "Event"
#define D_JSON_INITIATED "Initiated"
enum RulesCommands { CMND_RULE, CMND_RULETIMER, CMND_EVENT };
const char kRulesCommands[] PROGMEM = D_CMND_RULE "|" D_CMND_RULETIMER "|" D_CMND_EVENT ;
String rules_event_value;
unsigned long rules_timer[MAX_RULE_TIMERS] = { 0 };
uint8_t rules_quota = 0;
long rules_power = -1;
uint32_t rules_triggers = 0;
uint8_t rules_trigger_count = 0;
/*******************************************************************************************/
long TimeDifference(unsigned long prev, unsigned long next)
{
// Return the time difference as a signed value, taking into account the timers may overflow.
// Returned timediff is between -24.9 days and +24.9 days.
// Returned value is positive when "next" is after "prev"
long signed_diff = 0;
// To cast a value to a signed long, the difference may not exceed half the ULONG_MAX
const unsigned long half_max_unsigned_long = 2147483647u; // = 2^31 -1
if (next >= prev) {
const unsigned long diff = next - prev;
if (diff <= half_max_unsigned_long) { // Normal situation, just return the difference.
signed_diff = static_cast<long>(diff); // Difference is a positive value.
} else {
// prev has overflow, return a negative difference value
signed_diff = static_cast<long>((ULONG_MAX - next) + prev + 1u);
signed_diff = -1 * signed_diff;
}
} else {
// next < prev
const unsigned long diff = prev - next;
if (diff <= half_max_unsigned_long) { // Normal situation, return a negative difference value
signed_diff = static_cast<long>(diff);
signed_diff = -1 * signed_diff;
} else {
// next has overflow, return a positive difference value
signed_diff = static_cast<long>((ULONG_MAX - prev) + next + 1u);
}
}
return signed_diff;
}
long TimePassedSince(unsigned long timestamp)
{
// Compute the number of milliSeconds passed since timestamp given.
// Note: value can be negative if the timestamp has not yet been reached.
return TimeDifference(timestamp, millis());
}
bool TimeReached(unsigned long timer)
{
// Check if a certain timeout has been reached.
const long passed = TimePassedSince(timer);
return (passed >= 0);
}
/*******************************************************************************************/
bool RulesRuleMatch(String &event, String &rule)
{
// event = {"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}}
// event = {"System":{"Boot":1}}
// rule = "INA219#CURRENT>0.100"
bool match = false;
// Step1: Analyse rule
int pos = rule.indexOf('#');
if (pos == -1) { return false; } // No # sign in rule
String rule_task = rule.substring(0, pos); // "INA219" or "SYSTEM"
String rule_name = rule.substring(pos +1); // "CURRENT>0.100" or "BOOT"
char compare = ' ';
pos = rule_name.indexOf(">");
if (pos > 0) {
compare = '>';
} else {
pos = rule_name.indexOf("<");
if (pos > 0) {
compare = '<';
} else {
pos = rule_name.indexOf("=");
if (pos > 0) {
compare = '=';
}
}
}
String tmp_value = "none";
double rule_value = 0;
if (pos > 0) {
tmp_value = rule_name.substring(pos + 1); // "0.100"
rule_value = CharToDouble((char*)tmp_value.c_str()); // 0.1 - This saves 9k code over toFLoat()!
rule_name = rule_name.substring(0, pos); // "CURRENT"
}
// Step2: Search rule_task and rule_name
StaticJsonBuffer<400> jsonBuf;
JsonObject &root = jsonBuf.parseObject(event);
if (!root.success()) { return false; } // No valid JSON data
double value = 0;
const char* str_value = root[rule_task][rule_name];
// snprintf_P(log_data, sizeof(log_data), PSTR("RUL: Task %s, Name %s, Value %s, TrigCnt %d, TrigSt %d, Source %s, Json %s"),
// rule_task.c_str(), rule_name.c_str(), tmp_value.c_str(), rules_trigger_count, bitRead(rules_triggers, rules_trigger_count), event.c_str(), (str_value) ? str_value : "none");
// AddLog(LOG_LEVEL_DEBUG);
if (!root[rule_task][rule_name].success()) { return false; }
// No value but rule_name is ok
rules_event_value = str_value; // Prepare %value%
// Step 3: Compare rule (value)
if (str_value) {
value = CharToDouble((char*)str_value);
switch (compare) {
case '>':
if (value > rule_value) match = true;
break;
case '<':
if (value < rule_value) match = true;
break;
case '=':
if (value == rule_value) match = true;
break;
case ' ':
match = true; // Json value but not needed
break;
}
} else match = true;
if (Settings.flag.rules_once) {
if (match) { // Only allow match state changes
if (!bitRead(rules_triggers, rules_trigger_count)) {
bitSet(rules_triggers, rules_trigger_count);
} else {
match = false;
}
} else {
bitClear(rules_triggers, rules_trigger_count);
}
}
return match;
}
/*******************************************************************************************/
bool RulesProcess()
{
bool serviced = false;
if (!Settings.flag.rules_enabled) { return serviced; } // Not enabled
if (!strlen(Settings.rules)) { return serviced; } // No rules
String event_saved = mqtt_data;
event_saved.toUpperCase();
String rules = Settings.rules;
rules_trigger_count = 0;
int plen = 0;
while (true) {
rules = rules.substring(plen); // Select relative to last rule
rules.trim();
if (!rules.length()) { return serviced; } // No more rules
String rule = rules;
rule.toUpperCase(); // "ON INA219#CURRENT>0.100 DO BACKLOG DIMMER 10;COLOR 100000 ENDON"
if (!rule.startsWith("ON ")) { return serviced; } // Bad syntax - Nothing to start on
int pevt = rule.indexOf(" DO ");
if (pevt == -1) { return serviced; } // Bad syntax - Nothing to do
String event_trigger = rule.substring(3, pevt); // "INA219#CURRENT>0.100"
plen = rule.indexOf(" ENDON");
if (plen == -1) { return serviced; } // Bad syntax - No endon
String commands = rules.substring(pevt +4, plen); // "Backlog Dimmer 10;Color 100000"
plen += 6;
// snprintf_P(log_data, sizeof(log_data), PSTR("RUL: Trigger |%s|, Commands |%s|"), event_trigger.c_str(), commands.c_str());
// AddLog(LOG_LEVEL_DEBUG);
rules_event_value = "";
String event = event_saved;
if (RulesRuleMatch(event, event_trigger)) {
commands.replace(F("%value%"), rules_event_value);
char command[commands.length() +1];
snprintf(command, sizeof(command), commands.c_str());
snprintf_P(log_data, sizeof(log_data), PSTR("RUL: %s performs \"%s\""), event_trigger.c_str(), command);
AddLog(LOG_LEVEL_INFO);
// snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, D_CMND_RULE, D_JSON_INITIATED);
// MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_RULE));
ExecuteCommand(command);
serviced = true;
}
rules_trigger_count++;
}
return serviced;
}
/*******************************************************************************************/
void RulesInit()
{
if (Settings.rules[0] == '\0') {
Settings.flag.rules_enabled = 0;
Settings.flag.rules_once = 0;
}
}
void RulesSetPower()
{
if (Settings.flag.rules_enabled) {
uint16_t new_power = XdrvMailbox.index;
if (rules_power == -1) rules_power = new_power;
uint16_t old_power = rules_power;
rules_power = new_power;
for (byte i = 0; i < devices_present; i++) {
uint8_t new_state = new_power &1;
uint8_t old_state = old_power &1;
if (new_state != old_state) {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"Power%d\":{\"State\":%d}}"), i +1, new_state);
RulesProcess();
}
new_power >>= 1;
old_power >>= 1;
}
}
}
void RulesEvery50ms()
{
if (Settings.flag.rules_enabled) {
rules_quota++;
if (rules_quota &1) { // Every 100 ms
mqtt_data[0] = '\0';
uint16_t tele_period_save = tele_period;
tele_period = 2; // Do not allow HA updates during next function call
XsnsNextCall(FUNC_JSON_APPEND); // ,"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}
tele_period = tele_period_save;
if (strlen(mqtt_data)) {
mqtt_data[0] = '{'; // {"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data);
RulesProcess();
}
}
}
}
void RulesEverySecond()
{
if (Settings.flag.rules_enabled) {
for (byte i = 0; i < MAX_RULE_TIMERS; i++) {
if (rules_timer[i] != 0L) { // Timer active?
if (TimeReached(rules_timer[i])) { // Timer finished?
rules_timer[i] = 0L; // Turn off this timer
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"Rules\":{\"Timer\":%d}}"), i +1);
RulesProcess();
}
}
}
}
}
boolean RulesCommand()
{
char command[CMDSZ];
boolean serviced = true;
uint8_t index = XdrvMailbox.index;
int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic, kRulesCommands);
if (CMND_RULE == command_code) {
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.rules))) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6)) {
switch (XdrvMailbox.payload) {
case 0: // Off
case 1: // On
Settings.flag.rules_enabled = XdrvMailbox.payload;
break;
case 2: // Toggle
Settings.flag.rules_enabled ^= 1;
break;
case 4: // Off
case 5: // On
Settings.flag.rules_once = XdrvMailbox.payload &1;
break;
case 6: // Toggle
Settings.flag.rules_once ^= 1;
break;
}
} else {
/*
String uc_data = XdrvMailbox.data; // Do not allow Rule to be used within a rule
uc_data.toUpperCase();
String uc_command = command;
uc_command += " "; // Distuingish from RuleTimer
uc_command.toUpperCase();
if (!uc_data.indexOf(uc_command)) { strlcpy(Settings.rules, XdrvMailbox.data, sizeof(Settings.rules)); }
*/
strlcpy(Settings.rules, XdrvMailbox.data, sizeof(Settings.rules));
}
rules_triggers = 0; // Reset once flag
}
snprintf_P (mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":\"%s\",\"Once\":\"%s\",\"Rules\":\"%s\"}"), command, GetStateText(Settings.flag.rules_enabled), GetStateText(Settings.flag.rules_once), Settings.rules);
}
else if ((CMND_RULETIMER == command_code) && (index > 0) && (index <= MAX_RULE_TIMERS)) {
if (XdrvMailbox.data_len > 0) {
rules_timer[index -1] = (XdrvMailbox.payload > 0) ? millis() + (1000 * XdrvMailbox.payload) : 0;
}
snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_LVALUE, command, index, (rules_timer[index -1]) ? (rules_timer[index -1] - millis()) / 1000 : 0);
}
else if (CMND_EVENT == command_code) {
if (XdrvMailbox.data_len > 0) {
String event = XdrvMailbox.data;
String parameter = "";
int pos = event.indexOf('=');
if (pos > 0) {
parameter = event.substring(pos +1);
parameter.trim();
event = event.substring(0, pos);
}
event.trim();
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"Event\":{\"%s\":\"%s\"}}"), event.c_str(), parameter.c_str());
RulesProcess();
}
snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, D_JSON_DONE);
}
else serviced = false;
return serviced;
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
#define XDRV_10
boolean Xdrv10(byte function)
{
boolean result = false;
switch (function) {
case FUNC_INIT:
RulesInit();
break;
case FUNC_SET_POWER:
RulesSetPower();
break;
case FUNC_EVERY_50_MSECOND:
RulesEvery50ms();
break;
case FUNC_EVERY_SECOND:
RulesEverySecond();
break;
case FUNC_COMMAND:
result = RulesCommand();
break;
}
return result;
}
#endif // USE_RULES

885
sonoff/xdrv_11_knx.ino Normal file
View File

@ -0,0 +1,885 @@
/*
xdrv_11_knx.ino - KNX IP Protocol support for Sonoff-Tasmota
Copyright (C) 2018 Adrian Scillato (https://github.com/ascillato)
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/>.
*/
#ifdef USE_KNX
/*********************************************************************************************\
* KNX support
*
* Using libraries:
* ESP KNX IP library (async-udp branch) (https://github.com/envy/esp-knx-ip/tree/async-udp)
* ESPAsyncUDP library (https://github.com/me-no-dev/ESPAsyncUDP)
Constants in sonoff.h
-----------------------
#define MAX_KNX_GA 10 Max number of KNX Group Addresses to read that can be set
#define MAX_KNX_CB 10 Max number of KNX Group Addresses to write that can be set
If you change MAX_KNX_CB you also have to change on the esp-knx-ip.h file the following:
#define MAX_CALLBACK_ASSIGNMENTS 10
#define MAX_CALLBACKS 10
Both to MAX_KNX_CB
Variables in settings.h
-----------------------
bool Settings.flag.knx_enabled Enable/Disable KNX Protocol
uint16_t Settings.knx_physsical_addr Physical KNX address of this device
byte Settings.knx_GA_registered Number of group address to read
byte Settings.knx_CB_registered Number of group address to write
uint16_t Settings.knx_GA_addr[MAX_KNX_GA] Group address to read
uint16_t Settings.knx_CB_addr[MAX_KNX_CB] Group address to write
byte Settings.knx_GA_param[MAX_KNX_GA] Type of Input (relay changed, button pressed, sensor read)
byte Settings.knx_CB_param[MAX_KNX_CB] Type of Output (set relay, toggle relay, reply sensor value)
\*********************************************************************************************/
#include <esp-knx-ip.h>
//void KNX_CB_Action(message_t const &msg, void *arg); // Define function (action callback) to be called by the KNX_IP Library
// when an action is requested by another KNX Device
address_t KNX_physs_addr; // Physical KNX address of this device
address_t KNX_addr; // KNX Address converter variable
#define KNX_Empty 255
#define KNX_TEMPERATURE 17
#define KNX_HUMIDITY 18
#define KNX_MAX_device_param 18
float last_temp;
float last_hum;
typedef struct __device_parameters
{
byte type; // PARAMETER_ID. Used as type of GA = relay, button, sensor, etc, (INPUTS)
// used when an action on device triggers a MSG to send on KNX
// Needed because this is the value that the ESP_KNX_IP library will pass as parameter
// to identify the action to perform when a MSG is received
bool show; // HARDWARE related. to identify if the parameter exists on the device.
bool last_state; // LAST_STATE of relays
callback_id_t CB_id; // ACTION_ID. To store the ID value of Registered_CB to the library.
// The ESP_KNX_IP requires to register the callbacks, and then, to assign an address to the registered callback
// So CB_id is needed to store the ID of the callback to then, assign multiple addresses to the same ID (callback)
// It is used as type of CB = set relay, toggle relay, reply sensor, etc, (OUTPUTS)
// used when a MSG receive KNX triggers an action on the device
// - Multiples address to the same callback (i.e. Set Relay 1 Status) are used on scenes for example
} device_parameters_t;
// device parameters (information that can be sent)
device_parameters_t device_param[] = {
{ 1, false, false, KNX_Empty }, // device_param[ 0] = Relay 1
{ 2, false, false, KNX_Empty }, // device_param[ 1] = Relay 2
{ 3, false, false, KNX_Empty }, // device_param[ 2] = Relay 3
{ 4, false, false, KNX_Empty }, // device_param[ 3] = Relay 4
{ 5, false, false, KNX_Empty }, // device_param[ 4] = Relay 5
{ 6, false, false, KNX_Empty }, // device_param[ 5] = Relay 6
{ 7, false, false, KNX_Empty }, // device_param[ 6] = Relay 7
{ 8, false, false, KNX_Empty }, // device_param[ 7] = Relay 8
{ 9, false, false, KNX_Empty }, // device_param[ 8] = Button 1
{ 10, false, false, KNX_Empty }, // device_param[ 9] = Button 2
{ 11, false, false, KNX_Empty }, // device_param[10] = Button 3
{ 12, false, false, KNX_Empty }, // device_param[11] = Button 4
{ 13, false, false, KNX_Empty }, // device_param[12] = Button 5
{ 14, false, false, KNX_Empty }, // device_param[13] = Button 6
{ 15, false, false, KNX_Empty }, // device_param[14] = Button 7
{ 16, false, false, KNX_Empty }, // device_param[15] = Button 8
{ KNX_TEMPERATURE, false, false, KNX_Empty }, // device_param[16] = Temperature
{ KNX_HUMIDITY , false, false, KNX_Empty }, // device_param[17] = humidity
{ KNX_Empty, false, false, KNX_Empty}
};
// device parameters (information that can be sent)
const char * device_param_ga[] = {
D_SENSOR_RELAY " 1", // Relay 1
D_SENSOR_RELAY " 2", // Relay 2
D_SENSOR_RELAY " 3", // Relay 3
D_SENSOR_RELAY " 4", // Relay 4
D_SENSOR_RELAY " 5", // Relay 5
D_SENSOR_RELAY " 6", // Relay 6
D_SENSOR_RELAY " 7", // Relay 7
D_SENSOR_RELAY " 8", // Relay 8
D_SENSOR_BUTTON " 1", // Button 1
D_SENSOR_BUTTON " 2", // Button 2
D_SENSOR_BUTTON " 3", // Button 3
D_SENSOR_BUTTON " 4", // Button 4
D_SENSOR_BUTTON " 5", // Button 5
D_SENSOR_BUTTON " 6", // Button 6
D_SENSOR_BUTTON " 7", // Button 7
D_SENSOR_BUTTON " 8", // Button 8
D_TEMPERATURE , // Temperature
D_HUMIDITY , // Humidity
nullptr
};
// device actions (posible actions to be performed on the device)
const char *device_param_cb[] = {
D_SENSOR_RELAY " 1", // Set Relay 1 (1-On or 0-OFF)
D_SENSOR_RELAY " 2",
D_SENSOR_RELAY " 3",
D_SENSOR_RELAY " 4",
D_SENSOR_RELAY " 5",
D_SENSOR_RELAY " 6",
D_SENSOR_RELAY " 7",
D_SENSOR_RELAY " 8",
D_SENSOR_RELAY " 1 " D_BUTTON_TOGGLE, // Relay 1 Toggle (1 or 0 will toggle)
D_SENSOR_RELAY " 2 " D_BUTTON_TOGGLE,
D_SENSOR_RELAY " 3 " D_BUTTON_TOGGLE,
D_SENSOR_RELAY " 4 " D_BUTTON_TOGGLE,
D_SENSOR_RELAY " 5 " D_BUTTON_TOGGLE,
D_SENSOR_RELAY " 6 " D_BUTTON_TOGGLE,
D_SENSOR_RELAY " 7 " D_BUTTON_TOGGLE,
D_SENSOR_RELAY " 8 " D_BUTTON_TOGGLE,
D_REPLY " " D_TEMPERATURE, // Reply Temperature
D_REPLY " " D_HUMIDITY, // Reply Humidity
nullptr
};
byte KNX_GA_Search( byte param, byte start = 0 )
{
for (byte i = start; i < Settings.knx_GA_registered; ++i)
{
if ( Settings.knx_GA_param[i] == param )
{
if ( Settings.knx_GA_addr[i] != 0 ) // Relay has group address set? GA=0/0/0 can not be used as KNX address, so it is used here as a: not set value
{
if ( i >= start ) { return i; }
}
}
}
return KNX_Empty;
}
byte KNX_CB_Search( byte param, byte start = 0 )
{
for (byte i = start; i < Settings.knx_CB_registered; ++i)
{
if ( Settings.knx_CB_param[i] == param )
{
if ( Settings.knx_CB_addr[i] != 0 )
{
if ( i >= start ) { return i; }
}
}
}
return KNX_Empty;
}
void KNX_ADD_GA( byte GAop, byte GA_FNUM, byte GA_AREA, byte GA_FDEF )
{
// Check if all GA were assigned. If yes-> return
if ( Settings.knx_GA_registered >= MAX_KNX_GA ) { return; }
if ( GA_FNUM == 0 && GA_AREA == 0 && GA_FDEF == 0 ) { return; }
// Assign a GA to that address
Settings.knx_GA_param[Settings.knx_GA_registered] = GAop;
KNX_addr.ga.area = GA_FNUM;
KNX_addr.ga.line = GA_AREA;
KNX_addr.ga.member = GA_FDEF;
Settings.knx_GA_addr[Settings.knx_GA_registered] = KNX_addr.value;
Settings.knx_GA_registered++;
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_KNX D_ADD " GA #%d: %s " D_TO " %d/%d/%d"),
Settings.knx_GA_registered,
device_param_ga[GAop-1],
GA_FNUM, GA_AREA, GA_FDEF );
AddLog(LOG_LEVEL_DEBUG);
}
void KNX_DEL_GA( byte GAnum )
{
byte dest_offset = 0;
byte src_offset = 0;
byte len = 0;
// Delete GA
Settings.knx_GA_param[GAnum-1] = 0;
if (GAnum == 1)
{
// start of array, so delete first entry
src_offset = 1;
// Settings.knx_GA_registered will be 1 in case of only one entry
// Settings.knx_GA_registered will be 2 in case of two entries, etc..
// so only copy anything, if there is it at least more then one element
len = (Settings.knx_GA_registered - 1);
}
else if (GAnum == Settings.knx_GA_registered)
{
// last element, don't do anything, simply decrement counter
}
else
{
// somewhere in the middle
// need to calc offsets
// skip all prev elements
dest_offset = GAnum -1 ; // GAnum -1 is equal to how many element are in front of it
src_offset = dest_offset + 1; // start after the current element
len = (Settings.knx_GA_registered - GAnum);
}
if (len > 0)
{
memmove(Settings.knx_GA_param + dest_offset, Settings.knx_GA_param + src_offset, len * sizeof(byte));
memmove(Settings.knx_GA_addr + dest_offset, Settings.knx_GA_addr + src_offset, len * sizeof(uint16_t));
}
Settings.knx_GA_registered--;
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_KNX D_DELETE " GA #%d"),
GAnum );
AddLog(LOG_LEVEL_DEBUG);
}
void KNX_ADD_CB( byte CBop, byte CB_FNUM, byte CB_AREA, byte CB_FDEF )
{
// Check if all callbacks were assigned. If yes-> return
if ( Settings.knx_CB_registered >= MAX_KNX_CB ) { return; }
if ( CB_FNUM == 0 && CB_AREA == 0 && CB_FDEF == 0 ) { return; }
// Check if a CB for CBop was registered on the ESP-KNX-IP Library
if ( device_param[CBop-1].CB_id == KNX_Empty )
{
// if no, register the CB for CBop
device_param[CBop-1].CB_id = knx.callback_register("", KNX_CB_Action, &device_param[CBop-1]);
// KNX IP Library requires a parameter
// to identify which action was requested on the KNX network
// to be performed on this device (set relay, etc.)
// Is going to be used device_param[j].type that stores the type number (1: relay 1, etc)
}
// Assign a callback to CB address
Settings.knx_CB_param[Settings.knx_CB_registered] = CBop;
KNX_addr.ga.area = CB_FNUM;
KNX_addr.ga.line = CB_AREA;
KNX_addr.ga.member = CB_FDEF;
Settings.knx_CB_addr[Settings.knx_CB_registered] = KNX_addr.value;
knx.callback_assign( device_param[CBop-1].CB_id, KNX_addr );
Settings.knx_CB_registered++;
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_KNX D_ADD " CB #%d: %d/%d/%d " D_TO " %s"),
Settings.knx_CB_registered,
CB_FNUM, CB_AREA, CB_FDEF,
device_param_cb[CBop-1] );
AddLog(LOG_LEVEL_DEBUG);
}
void KNX_DEL_CB( byte CBnum )
{
byte oldparam = Settings.knx_CB_param[CBnum-1];
byte dest_offset = 0;
byte src_offset = 0;
byte len = 0;
// Delete assigment
knx.callback_unassign(CBnum-1);
Settings.knx_CB_param[CBnum-1] = 0;
if (CBnum == 1)
{
// start of array, so delete first entry
src_offset = 1;
// Settings.knx_CB_registered will be 1 in case of only one entry
// Settings.knx_CB_registered will be 2 in case of two entries, etc..
// so only copy anything, if there is it at least more then one element
len = (Settings.knx_CB_registered - 1);
}
else if (CBnum == Settings.knx_CB_registered)
{
// last element, don't do anything, simply decrement counter
}
else
{
// somewhere in the middle
// need to calc offsets
// skip all prev elements
dest_offset = CBnum -1 ; // GAnum -1 is equal to how many element are in front of it
src_offset = dest_offset + 1; // start after the current element
len = (Settings.knx_CB_registered - CBnum);
}
if (len > 0)
{
memmove(Settings.knx_CB_param + dest_offset, Settings.knx_CB_param + src_offset, len * sizeof(byte));
memmove(Settings.knx_CB_addr + dest_offset, Settings.knx_CB_addr + src_offset, len * sizeof(uint16_t));
}
Settings.knx_CB_registered--;
// Check if there is no other assigment to that callback. If there is not. delete that callback register
if ( KNX_CB_Search( oldparam ) == KNX_Empty ) {
knx.callback_deregister( device_param[oldparam-1].CB_id );
device_param[oldparam-1].CB_id = KNX_Empty;
}
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_KNX D_DELETE " CB #%d"), CBnum );
AddLog(LOG_LEVEL_DEBUG);
}
bool KNX_CONFIG_NOT_MATCH()
{
for (int i = 0; i < KNX_MAX_device_param; ++i)
{
if ( !device_param[i].show ) { // device has this parameter ?
// if not, search for all registered group address to this parameter for deletion
if ( KNX_GA_Search(i+1) != KNX_Empty ) { return true; }
if ( (i < 8) || (i > 15) ) // check relays and sensors (i from 8 to 16 are toggle relays parameters)
{
if ( KNX_CB_Search(i+1) != KNX_Empty ) { return true; }
if ( KNX_CB_Search(i+8) != KNX_Empty ) { return true; }
}
}
}
return false;
}
void KNXStart()
{
knx.start(nullptr);
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_KNX D_START));
AddLog(LOG_LEVEL_DEBUG);
}
void KNX_INIT()
{
// Check for incompatible config
if (Settings.knx_GA_registered > MAX_KNX_GA) { Settings.knx_GA_registered = MAX_KNX_GA; }
if (Settings.knx_CB_registered > MAX_KNX_CB) { Settings.knx_CB_registered = MAX_KNX_CB; }
// Set Physical KNX Address of the device
KNX_physs_addr.value = Settings.knx_physsical_addr;
knx.physical_address_set( KNX_physs_addr );
// Read Configuration
// Check which relays, buttons and sensors where configured for this device
// and activate options according to the hardware
for (int i = GPIO_REL1; i < GPIO_REL8 + 1; ++i)
{
if (GetUsedInModule(i, my_module.gp.io)) { device_param[i - GPIO_REL1].show = true; }
}
for (int i = GPIO_REL1_INV; i < GPIO_REL8_INV + 1; ++i)
{
if (GetUsedInModule(i, my_module.gp.io)) { device_param[i - GPIO_REL1_INV].show = true; }
}
for (int i = GPIO_SWT1; i < GPIO_SWT4 + 1; ++i)
{
if (GetUsedInModule(i, my_module.gp.io)) { device_param[i - GPIO_SWT1 + 8].show = true; }
}
for (int i = GPIO_KEY1; i < GPIO_KEY4 + 1; ++i)
{
if (GetUsedInModule(i, my_module.gp.io)) { device_param[i - GPIO_KEY1 + 8].show = true; }
}
if (GetUsedInModule(GPIO_DHT11, my_module.gp.io)) { device_param[KNX_TEMPERATURE-1].show = true; }
if (GetUsedInModule(GPIO_DHT22, my_module.gp.io)) { device_param[KNX_TEMPERATURE-1].show = true; }
if (GetUsedInModule(GPIO_SI7021, my_module.gp.io)) { device_param[KNX_TEMPERATURE-1].show = true; }
if (GetUsedInModule(GPIO_DHT11, my_module.gp.io)) { device_param[KNX_HUMIDITY-1].show = true; }
if (GetUsedInModule(GPIO_DHT22, my_module.gp.io)) { device_param[KNX_HUMIDITY-1].show = true; }
if (GetUsedInModule(GPIO_SI7021, my_module.gp.io)) { device_param[KNX_HUMIDITY-1].show = true; }
// Delete from KNX settings all configuration is not anymore related to this device
if (KNX_CONFIG_NOT_MATCH()) {
Settings.knx_GA_registered = 0;
Settings.knx_CB_registered = 0;
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_KNX D_DELETE " " D_KNX_PARAMETERS ));
AddLog(LOG_LEVEL_DEBUG);
}
// Register Group Addresses to listen to
// Search on the settings if there is a group address set for receive KNX messages for the type: device_param[j].type
// If there is, register the group address on the KNX_IP Library to Receive data for Executing Callbacks
byte j;
for (byte i = 0; i < Settings.knx_CB_registered; ++i)
{
j = Settings.knx_CB_param[i];
if ( j > 0 )
{
device_param[j-1].CB_id = knx.callback_register("", KNX_CB_Action, &device_param[j-1]); // KNX IP Library requires a parameter
// to identify which action was requested on the KNX network
// to be performed on this device (set relay, etc.)
// Is going to be used device_param[j].type that stores the type number (1: relay 1, etc)
KNX_addr.value = Settings.knx_CB_addr[i];
knx.callback_assign( device_param[j-1].CB_id, KNX_addr );
}
}
}
void KNX_CB_Action(message_t const &msg, void *arg)
{
device_parameters_t *chan = (device_parameters_t *)arg;
if (!(Settings.flag.knx_enabled)) { return; }
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_KNX D_RECEIVED_FROM " %d.%d.%d " D_COMMAND " %s: %d " D_TO " %s"),
msg.received_on.ga.area, msg.received_on.ga.line, msg.received_on.ga.member,
(msg.ct == KNX_CT_WRITE) ? D_KNX_COMMAND_WRITE : (msg.ct == KNX_CT_READ) ? D_KNX_COMMAND_READ : D_KNX_COMMAND_OTHER,
msg.data[0],
device_param_cb[(chan->type)-1]);
AddLog(LOG_LEVEL_INFO);
switch (msg.ct)
{
case KNX_CT_WRITE:
if (chan->type < 9) // Set Relays
{
ExecuteCommandPower(chan->type, msg.data[0]);
}
else if (chan->type < 17) // Toggle Relays
{
ExecuteCommandPower((chan->type) -8, 2);
}
break;
case KNX_CT_READ:
if (chan->type < 9) // reply Relays status
{
knx.answer_1bit(msg.received_on, chan->last_state);
}
else if (chan->type = KNX_TEMPERATURE) // Reply Temperature
{
knx.answer_2byte_float(msg.received_on, last_temp);
}
else if (chan->type = KNX_HUMIDITY) // Reply Humidity
{
knx.answer_2byte_float(msg.received_on, last_hum);
}
break;
}
}
void KnxUpdatePowerState(byte device, power_t state)
{
if (!(Settings.flag.knx_enabled)) { return; }
device_param[device -1].last_state = bitRead(state, device -1); // power state (on/off)
// Search all the registered GA that has that output (variable: device) as parameter
byte i = KNX_GA_Search(device);
while ( i != KNX_Empty ) {
KNX_addr.value = Settings.knx_GA_addr[i];
knx.write_1bit(KNX_addr, device_param[device -1].last_state);
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_KNX "%s = %d " D_SENT_TO " %d.%d.%d"),
device_param_ga[device -1], device_param[device -1].last_state,
KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member);
AddLog(LOG_LEVEL_INFO);
i = KNX_GA_Search(device, i + 1);
}
}
void KnxSendButtonPower(byte key, byte device, byte state)
{
// key 0 = button_topic
// key 1 = switch_topic
// state 0 = off
// state 1 = on
// state 2 = toggle
// state 3 = hold
// state 9 = clear retain flag
if (!(Settings.flag.knx_enabled)) { return; }
// if (key)
// {
// Search all the registered GA that has that output (variable: device) as parameter
byte i = KNX_GA_Search(device + 8);
while ( i != KNX_Empty ) {
KNX_addr.value = Settings.knx_GA_addr[i];
knx.write_1bit(KNX_addr, !(state == 0));
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_KNX "%s = %d " D_SENT_TO " %d.%d.%d"),
device_param_ga[device + 8], !(state == 0),
KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member);
AddLog(LOG_LEVEL_INFO);
i = KNX_GA_Search(device + 8, i + 1);
}
// }
}
void KnxSensor(byte sensor_type, float value)
{
if (sensor_type == KNX_TEMPERATURE)
{
last_temp = value;
} else if (sensor_type == KNX_HUMIDITY)
{
last_hum = value;
}
if (!(Settings.flag.knx_enabled)) { return; }
byte i = KNX_GA_Search(sensor_type);
while ( i != KNX_Empty ) {
KNX_addr.value = Settings.knx_GA_addr[i];
knx.write_2byte_float(KNX_addr, value);
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_KNX "%s " D_SENT_TO " %d.%d.%d "),
device_param_ga[sensor_type],
KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member);
AddLog(LOG_LEVEL_INFO);
i = KNX_GA_Search(sensor_type, i+1);
}
}
/*********************************************************************************************\
* Presentation
\*********************************************************************************************/
#ifdef USE_WEBSERVER
const char S_CONFIGURE_KNX[] PROGMEM = D_CONFIGURE_KNX;
const char HTTP_FORM_KNX[] PROGMEM =
"<fieldset><legend style='text-align:left;'><b>&nbsp;" D_KNX_PARAMETERS "&nbsp;</b></legend><form method='post' action='kn'>"
"<br/><center>"
"<b>" D_KNX_PHYSICAL_ADDRESS " </b>"
"<input style='width:12%;' type='number' name='area' min='0' max='15' value='{kna'> . "
"<input style='width:12%;' type='number' name='line' min='0' max='15' value='{knl'> . "
"<input style='width:12%;' type='number' name='member' min='0' max='255' value='{knm'>"
"<br/><br/>" D_KNX_PHYSICAL_ADDRESS_NOTE "<br/><br/>"
"<input style='width:10%;' id='b1' name='b1' type='checkbox'";
const char HTTP_FORM_KNX2[] PROGMEM =
"><b>" D_KNX_ENABLE "</b><br/></center><br/>"
"<fieldset><center>"
"<b>" D_KNX_GROUP_ADDRESS_TO_WRITE "</b><hr>"
"<select name='GAop' style='width:25%;'>";
const char HTTP_FORM_KNX_OPT[] PROGMEM =
"<option value='{vop}'>{nop}</option>";
const char HTTP_FORM_KNX_GA[] PROGMEM =
"<input style='width:12%;' type='number' id='GAfnum' name='GAfnum' min='0' max='31' value='0'> / "
"<input style='width:12%;' type='number' id='GAarea' name='GAarea' min='0' max='7' value='0'> / "
"<input style='width:12%;' type='number' id='GAfdef' name='GAfdef' min='0' max='255' value='0'> ";
const char HTTP_FORM_KNX_ADD_BTN[] PROGMEM =
"<button type='submit' onclick='fncbtnadd()' btndis name='btn_add' value='{btnval}' style='width:18%;'>" D_ADD "</button><br/><br/>"
"<table style='width:80%; font-size: 14px;'><col width='250'><col width='30'>";
const char HTTP_FORM_KNX_ADD_TABLE_ROW[] PROGMEM =
"<tr><td><b>{optex} -> GAfnum / GAarea / GAfdef </b></td>"
"<td><button type='submit' name='btn_del_ga' value='{opval}' style='background-color: #f44336;'> " D_DELETE " </button></td></tr>";
const char HTTP_FORM_KNX3[] PROGMEM =
"</table></center></fieldset><br/>"
"<fieldset><form method='post' action='kn'><center>"
"<b>" D_KNX_GROUP_ADDRESS_TO_READ "</b><hr>";
const char HTTP_FORM_KNX4[] PROGMEM =
"-> <select name='CBop' style='width:25%;'>";
const char HTTP_FORM_KNX_ADD_TABLE_ROW2[] PROGMEM =
"<tr><td><b>GAfnum / GAarea / GAfdef -> {optex}</b></td>"
"<td><button type='submit' name='btn_del_cb' value='{opval}' style='background-color: #f44336;'> " D_DELETE " </button></td></tr>";
void HandleKNXConfiguration()
{
char tmp[100];
String stmp;
if (HTTP_USER == webserver_state) {
HandleRoot();
return;
}
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_KNX);
if ( WebServer->hasArg("save") ) {
KNX_Save_Settings();
HandleConfiguration();
}
else
{
if ( WebServer->hasArg("btn_add") ) {
if ( WebServer->arg("btn_add") == "1" ) {
stmp = WebServer->arg("GAop"); //option selected
byte GAop = stmp.toInt();
stmp = WebServer->arg("GA_FNUM");
byte GA_FNUM = stmp.toInt();
stmp = WebServer->arg("GA_AREA");
byte GA_AREA = stmp.toInt();
stmp = WebServer->arg("GA_FDEF");
byte GA_FDEF = stmp.toInt();
KNX_ADD_GA( GAop, GA_FNUM, GA_AREA, GA_FDEF );
}
else
{
stmp = WebServer->arg("CBop"); //option selected
byte CBop = stmp.toInt();
stmp = WebServer->arg("CB_FNUM");
byte CB_FNUM = stmp.toInt();
stmp = WebServer->arg("CB_AREA");
byte CB_AREA = stmp.toInt();
stmp = WebServer->arg("CB_FDEF");
byte CB_FDEF = stmp.toInt();
KNX_ADD_CB( CBop, CB_FNUM, CB_AREA, CB_FDEF );
}
}
else if ( WebServer->hasArg("btn_del_ga") )
{
stmp = WebServer->arg("btn_del_ga");
byte GA_NUM = stmp.toInt();
KNX_DEL_GA(GA_NUM);
}
else if ( WebServer->hasArg("btn_del_cb") )
{
stmp = WebServer->arg("btn_del_cb");
byte CB_NUM = stmp.toInt();
KNX_DEL_CB(CB_NUM);
}
String page = FPSTR(HTTP_HEAD);
page.replace(F("{v}"), FPSTR(S_CONFIGURE_KNX));
page += FPSTR(HTTP_HEAD_STYLE);
page.replace(F("340px"), F("530px"));
page += FPSTR(HTTP_FORM_KNX);
KNX_physs_addr.value = Settings.knx_physsical_addr;
page.replace(F("{kna"), String(KNX_physs_addr.pa.area));
page.replace(F("{knl"), String(KNX_physs_addr.pa.line));
page.replace(F("{knm"), String(KNX_physs_addr.pa.member));
if ( Settings.flag.knx_enabled ) { page += F(" checked"); }
page += FPSTR(HTTP_FORM_KNX2);
for (byte i = 0; i < KNX_MAX_device_param ; i++)
{
if ( device_param[i].show )
{
page += FPSTR(HTTP_FORM_KNX_OPT);
page.replace(F("{vop}"), String(device_param[i].type));
page.replace(F("{nop}"), String(device_param_ga[i]));
}
}
page += F("</select> -> ");
page += FPSTR(HTTP_FORM_KNX_GA);
page.replace(F("GAfnum"), F("GA_FNUM"));
page.replace(F("GAarea"), F("GA_AREA"));
page.replace(F("GAfdef"), F("GA_FDEF"));
page.replace(F("GAfnum"), F("GA_FNUM"));
page.replace(F("GAarea"), F("GA_AREA"));
page.replace(F("GAfdef"), F("GA_FDEF"));
page += FPSTR(HTTP_FORM_KNX_ADD_BTN);
page.replace(F("{btnval}"), String(1));
if (Settings.knx_GA_registered < MAX_KNX_GA) {
page.replace(F("btndis"), F(" "));
}
else
{
page.replace(F("btndis"), F("disabled"));
}
page.replace(F("fncbtnadd"), F("GAwarning"));
for (byte i = 0; i < Settings.knx_GA_registered ; ++i)
{
if ( Settings.knx_GA_param[i] )
{
page += FPSTR(HTTP_FORM_KNX_ADD_TABLE_ROW);
page.replace(F("{opval}"), String(i+1));
page.replace(F("{optex}"), String(device_param_ga[Settings.knx_GA_param[i]-1]));
KNX_addr.value = Settings.knx_GA_addr[i];
page.replace(F("GAfnum"), String(KNX_addr.ga.area));
page.replace(F("GAarea"), String(KNX_addr.ga.line));
page.replace(F("GAfdef"), String(KNX_addr.ga.member));
}
}
page += FPSTR(HTTP_FORM_KNX3);
page += FPSTR(HTTP_FORM_KNX_GA);
page.replace(F("GAfnum"), F("CB_FNUM"));
page.replace(F("GAarea"), F("CB_AREA"));
page.replace(F("GAfdef"), F("CB_FDEF"));
page.replace(F("GAfnum"), F("CB_FNUM"));
page.replace(F("GAarea"), F("CB_AREA"));
page.replace(F("GAfdef"), F("CB_FDEF"));
page += FPSTR(HTTP_FORM_KNX4);
for (byte i = 0; i < KNX_MAX_device_param ; i++)
{
if ( device_param[i].show )
{
page += FPSTR(HTTP_FORM_KNX_OPT);
page.replace(F("{vop}"), String(device_param[i].type));
page.replace(F("{nop}"), String(device_param_cb[i]));
}
}
page += F("</select> ");
page += FPSTR(HTTP_FORM_KNX_ADD_BTN);
page.replace(F("{btnval}"), String(2));
if (Settings.knx_CB_registered < MAX_KNX_CB) {
page.replace(F("btndis"), F(" "));
}
else
{
page.replace(F("btndis"), F("disabled"));
}
page.replace(F("fncbtnadd"), F("CBwarning"));
for (byte i = 0; i < Settings.knx_CB_registered ; ++i)
{
if ( Settings.knx_CB_param[i] )
{
page += FPSTR(HTTP_FORM_KNX_ADD_TABLE_ROW2);
page.replace(F("{opval}"), String(i+1));
page.replace(F("{optex}"), String(device_param_cb[Settings.knx_CB_param[i]-1]));
KNX_addr.value = Settings.knx_CB_addr[i];
page.replace(F("GAfnum"), String(KNX_addr.ga.area));
page.replace(F("GAarea"), String(KNX_addr.ga.line));
page.replace(F("GAfdef"), String(KNX_addr.ga.member));
}
}
page += F("</table></center></fieldset>");
page += F("<br/><button name='save' type='submit'>" D_SAVE "</button></form></fieldset>");
page += FPSTR(HTTP_BTN_CONF);
page.replace( F("</script>"),
F("function GAwarning()"
"{"
"var GA_FNUM = document.getElementById('GA_FNUM');"
"var GA_AREA = document.getElementById('GA_AREA');"
"var GA_FDEF = document.getElementById('GA_FDEF');"
"if ( GA_FNUM != null && GA_FNUM.value == '0' && GA_AREA.value == '0' && GA_FDEF.value == '0' ) {"
"alert('" D_KNX_WARNING "');"
"}"
"}"
"function CBwarning()"
"{"
"var CB_FNUM = document.getElementById('CB_FNUM');"
"var CB_AREA = document.getElementById('CB_AREA');"
"var CB_FDEF = document.getElementById('CB_FDEF');"
"if ( CB_FNUM != null && CB_FNUM.value == '0' && CB_AREA.value == '0' && CB_FDEF.value == '0' ) {"
"alert('" D_KNX_WARNING "');"
"}"
"}"
"</script>") );
ShowPage(page);
}
}
void KNX_Save_Settings()
{
String stmp;
address_t KNX_addr;
byte i;
Settings.flag.knx_enabled = WebServer->hasArg("b1");
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_KNX D_ENABLED ": %d "),
Settings.flag.knx_enabled);
AddLog(LOG_LEVEL_DEBUG);
stmp = WebServer->arg("area");
KNX_addr.pa.area = stmp.toInt();
stmp = WebServer->arg("line");
KNX_addr.pa.line = stmp.toInt();
stmp = WebServer->arg("member");
KNX_addr.pa.member = stmp.toInt();
Settings.knx_physsical_addr = KNX_addr.value;
knx.physical_address_set( KNX_addr ); // Set Physical KNX Address of the device
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_KNX D_KNX_PHYSICAL_ADDRESS ": %d.%d.%d "),
KNX_addr.pa.area, KNX_addr.pa.line, KNX_addr.pa.member );
AddLog(LOG_LEVEL_DEBUG);
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_KNX "GA: %d"),
Settings.knx_GA_registered );
AddLog(LOG_LEVEL_DEBUG);
for (i = 0; i < Settings.knx_GA_registered ; ++i)
{
KNX_addr.value = Settings.knx_GA_addr[i];
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_KNX "GA #%d: %s " D_TO " %d/%d/%d"),
i+1, device_param_ga[Settings.knx_GA_param[i]-1],
KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member );
AddLog(LOG_LEVEL_DEBUG);
}
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_KNX "CB: %d"),
Settings.knx_CB_registered );
AddLog(LOG_LEVEL_DEBUG);
for (i = 0; i < Settings.knx_CB_registered ; ++i)
{
KNX_addr.value = Settings.knx_CB_addr[i];
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_KNX "CB #%d: %d/%d/%d " D_TO " %s"),
i+1,
KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member,
device_param_cb[Settings.knx_CB_param[i]-1] );
AddLog(LOG_LEVEL_DEBUG);
}
}
#endif // USE_WEBSERVER
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
#define XDRV_11
boolean Xdrv11(byte function)
{
boolean result = false;
switch (function) {
case FUNC_INIT:
KNX_INIT();
break;
case FUNC_LOOP:
knx.loop(); // Process knx events
// It is not used by the actual config of asyncUDP branch of ESP-KNX-IP Library,
// but is left here for compatibility with upcoming features of ESP-KNX-IP Library
break;
// case FUNC_COMMAND:
// result = KNXCommand();
// break;
// case FUNC_SET_POWER:
// break;
}
return result;
}
#endif // USE_KNX

View File

@ -182,17 +182,12 @@ boolean XdrvMqttData(char *topicBuf, uint16_t stopicBuf, char *dataBuf, uint16_t
* FUNC_LOOP
* FUNC_MQTT_SUBSCRIBE
* FUNC_MQTT_INIT
* FUNC_MQTT_DISCONNECTED
* FUNC_MQTT_CONNECTED
* return FUNC_MQTT_DATA
* return FUNC_COMMAND
* FUNC_SET_POWER
* FUNC_SHOW_SENSOR
* FUNC_EVERY_SECOND
* FUNC_EVERY_50_MSECOND
* FUNC_NTP_INIT
* FUNC_NTP_SET
* FUNC_CLOCK_TIMER
\*********************************************************************************************/
boolean XdrvCall(byte Function)

View File

@ -130,6 +130,14 @@ void SonoffScShow(boolean json)
DomoticzSensor(DZ_AIRQUALITY, 500 + ((100 - sc_value[4]) * 20));
}
#endif // USE_DOMOTICZ
#ifdef USE_KNX
if (0 == tele_period) {
KnxSensor(KNX_TEMPERATURE, t);
KnxSensor(KNX_HUMIDITY, h);
}
#endif // USE_KNX
#ifdef USE_WEBSERVER
} else {
snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_TEMP, mqtt_data, "", temperature, TempUnit());

View File

@ -231,6 +231,14 @@ void DhtShow(boolean json)
dsxflg++;
}
#endif // USE_DOMOTICZ
#ifdef USE_KNX
if (0 == tele_period) {
KnxSensor(KNX_TEMPERATURE, t);
KnxSensor(KNX_HUMIDITY, h);
}
#endif // USE_KNX
#ifdef USE_WEBSERVER
} else {
snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_TEMP, mqtt_data, Dht[i].stype, temperature, TempUnit());

View File

@ -201,6 +201,14 @@ void ShtShow(boolean json)
#ifdef USE_DOMOTICZ
if (0 == tele_period) DomoticzTempHumSensor(temperature, humidity);
#endif // USE_DOMOTICZ
#ifdef USE_KNX
if (0 == tele_period) {
KnxSensor(KNX_TEMPERATURE, t);
KnxSensor(KNX_HUMIDITY, h);
}
#endif // USE_KNX
#ifdef USE_WEBSERVER
} else {
snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_TEMP, mqtt_data, "SHT1X", temperature, TempUnit());

View File

@ -264,6 +264,14 @@ void HtuShow(boolean json)
#ifdef USE_DOMOTICZ
if (0 == tele_period) DomoticzTempHumSensor(temperature, humidity);
#endif // USE_DOMOTICZ
#ifdef USE_KNX
if (0 == tele_period) {
KnxSensor(KNX_TEMPERATURE, t);
KnxSensor(KNX_HUMIDITY, h);
}
#endif // USE_KNX
#ifdef USE_WEBSERVER
} else {
snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_TEMP, mqtt_data, htu_types, temperature, TempUnit());

View File

@ -467,6 +467,14 @@ void BmpShow(boolean json)
#ifdef USE_DOMOTICZ
if (0 == tele_period) DomoticzTempHumPressureSensor(temperature, humidity, pressure);
#endif // USE_DOMOTICZ
#ifdef USE_KNX
if (0 == tele_period) {
KnxSensor(KNX_TEMPERATURE, t);
KnxSensor(KNX_HUMIDITY, h);
}
#endif // USE_KNX
#ifdef USE_WEBSERVER
} else {
snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_TEMP, mqtt_data, bmp_name, temperature, TempUnit());

View File

@ -111,6 +111,14 @@ void Sht3xShow(boolean json)
DomoticzTempHumSensor(temperature, humidity);
}
#endif // USE_DOMOTICZ
#ifdef USE_KNX
if (0 == tele_period) {
KnxSensor(KNX_TEMPERATURE, t);
KnxSensor(KNX_HUMIDITY, h);
}
#endif // USE_KNX
#ifdef USE_WEBSERVER
} else {
snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_TEMP, mqtt_data, types, temperature, TempUnit());