mirror of
https://github.com/IoTManagerProject/IoTManager.git
synced 2026-03-26 14:12:16 +03:00
Merge pull request #381 from Mit4el/ver4dev
Новые модули: SmartBoiler, Benchmark, BrokerMqtt, Ping, SIM800 Исправления Исправлена проблема в NTP.cpp високосного года при отображении графиков Исправления в классе IoTUart Внимание! Для модуля src/classes/IoTUart.cpp изменилось поведение функции сценария print(...) - оно теперь не делает пеервод строки в конце - используйте println(...)
This commit is contained in:
@@ -58,6 +58,8 @@ extern IoTGpio IoTgpio;
|
||||
extern IoTItem* rtcItem;
|
||||
//extern IoTItem* camItem;
|
||||
extern IoTItem* tlgrmItem;
|
||||
extern IoTBench* benchLoadItem;
|
||||
extern IoTBench* benchTaskItem;
|
||||
|
||||
extern TickerScheduler ts;
|
||||
extern WiFiClient espClient;
|
||||
|
||||
30
include/classes/IoTBench.h
Normal file
30
include/classes/IoTBench.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include "Global.h"
|
||||
#include "classes/IoTItem.h"
|
||||
#include <map>
|
||||
|
||||
struct ItemBench
|
||||
{
|
||||
uint32_t sumloopTime = 0;
|
||||
uint32_t loopTime = 0;
|
||||
uint32_t loopTimeMax_p = 0;
|
||||
uint32_t loopTimeMax_glob = 0;
|
||||
uint32_t count = 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class IoTBench : public IoTItem
|
||||
{
|
||||
public:
|
||||
IoTBench(const String ¶meters);
|
||||
~IoTBench();
|
||||
|
||||
virtual void preLoadFunction();
|
||||
virtual void postLoadFunction();
|
||||
virtual void preTaskFunction(const String &id);
|
||||
virtual void postTaskFunction(const String &id);
|
||||
protected:
|
||||
std::map<String, ItemBench *> banchItems;
|
||||
};
|
||||
@@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
#include "classes/IoTGpio.h"
|
||||
//#include "classes/IoTBench.h"
|
||||
|
||||
class IoTBench;
|
||||
|
||||
struct IoTValue {
|
||||
float valD = 0;
|
||||
@@ -53,6 +56,9 @@ class IoTItem {
|
||||
virtual IoTItem* getRtcDriver();
|
||||
//virtual IoTItem* getCAMDriver();
|
||||
virtual IoTItem* getTlgrmDriver();
|
||||
//virtual IoTBench* getBenchmark();
|
||||
virtual IoTBench*getBenchmarkTask();
|
||||
virtual IoTBench*getBenchmarkLoad();
|
||||
virtual unsigned long getRtcUnixTime();
|
||||
|
||||
// делаем доступным модулям отправку сообщений в телеграм
|
||||
|
||||
20
lib/PicoMQTT/src/PicoMQTT.h
Normal file
20
lib/PicoMQTT/src/PicoMQTT.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#if defined(ESP8266)
|
||||
|
||||
#if (ARDUINO_ESP8266_MAJOR != 3) || (ARDUINO_ESP8266_MINOR < 1)
|
||||
#error PicoMQTT requires ESP8266 board core version >= 3.1
|
||||
#endif
|
||||
|
||||
#elif defined(ESP32)
|
||||
|
||||
//#if ESP_ARDUINO_VERSION < ESP_ARDUINO_VERSION_VAL(2, 0, 7)
|
||||
//#error PicoMQTT requires ESP32 board core version >= 2.0.7
|
||||
//#endif
|
||||
|
||||
#endif
|
||||
|
||||
#include "PicoMQTT/client.h"
|
||||
#include "PicoMQTT/server.h"
|
||||
21
lib/PicoMQTT/src/PicoMQTT/autoid.h
Normal file
21
lib/PicoMQTT/src/PicoMQTT/autoid.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
class AutoId {
|
||||
public:
|
||||
typedef unsigned int Id;
|
||||
|
||||
AutoId(): id(generate_id()) {}
|
||||
AutoId(const AutoId &) = default;
|
||||
|
||||
const Id id;
|
||||
|
||||
private:
|
||||
static Id generate_id() {
|
||||
static Id next_id = 1;
|
||||
return next_id++;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
288
lib/PicoMQTT/src/PicoMQTT/client.cpp
Normal file
288
lib/PicoMQTT/src/PicoMQTT/client.cpp
Normal file
@@ -0,0 +1,288 @@
|
||||
#include "client.h"
|
||||
#include "debug.h"
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
BasicClient::BasicClient(unsigned long keep_alive_seconds, unsigned long socket_timeout_seconds)
|
||||
: Connection(keep_alive_seconds, socket_timeout_seconds) {
|
||||
TRACE_FUNCTION
|
||||
}
|
||||
|
||||
BasicClient::BasicClient(const ::WiFiClient & client, unsigned long keep_alive_seconds,
|
||||
unsigned long socket_timeout_seconds)
|
||||
: Connection(client, keep_alive_seconds, socket_timeout_seconds) {
|
||||
TRACE_FUNCTION
|
||||
}
|
||||
|
||||
bool BasicClient::connect(
|
||||
const char * host,
|
||||
uint16_t port,
|
||||
const char * id,
|
||||
const char * user,
|
||||
const char * pass,
|
||||
const char * will_topic,
|
||||
const char * will_message,
|
||||
const size_t will_message_length,
|
||||
uint8_t will_qos,
|
||||
bool will_retain,
|
||||
const bool clean_session,
|
||||
ConnectReturnCode * connect_return_code) {
|
||||
TRACE_FUNCTION
|
||||
|
||||
if (connect_return_code) {
|
||||
*connect_return_code = CRC_UNDEFINED;
|
||||
}
|
||||
|
||||
client.stop();
|
||||
|
||||
if (!client.connect(host, port)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
message_id_generator.reset();
|
||||
|
||||
const bool will = will_topic && will_message;
|
||||
|
||||
const uint8_t connect_flags =
|
||||
(user ? 1 : 0) << 7
|
||||
| (user && pass ? 1 : 0) << 6
|
||||
| (will && will_retain ? 1 : 0) << 5
|
||||
| (will && will_qos ? 1 : 0) << 3
|
||||
| (will ? 1 : 0) << 2
|
||||
| (clean_session ? 1 : 0) << 1;
|
||||
|
||||
const size_t client_id_length = strlen(id);
|
||||
const size_t will_topic_length = (will && will_topic) ? strlen(will_topic) : 0;
|
||||
const size_t user_length = user ? strlen(user) : 0;
|
||||
const size_t pass_length = pass ? strlen(pass) : 0;
|
||||
|
||||
const size_t total_size = 6 // protocol name
|
||||
+ 1 // protocol level
|
||||
+ 1 // connect flags
|
||||
+ 2 // keep-alive
|
||||
+ client_id_length + 2
|
||||
+ (will ? will_topic_length + 2 : 0)
|
||||
+ (will ? will_message_length + 2 : 0)
|
||||
+ (user ? user_length + 2 : 0)
|
||||
+ (user && pass ? pass_length + 2 : 0);
|
||||
|
||||
auto packet = build_packet(Packet::CONNECT, 0, total_size);
|
||||
packet.write_string("MQTT", 4);
|
||||
packet.write_u8(4);
|
||||
packet.write_u8(connect_flags);
|
||||
packet.write_u16(keep_alive_millis / 1000);
|
||||
packet.write_string(id, client_id_length);
|
||||
|
||||
if (will) {
|
||||
packet.write_string(will_topic, will_topic_length);
|
||||
packet.write_string(will_message, will_message_length);
|
||||
}
|
||||
|
||||
if (user) {
|
||||
packet.write_string(user, user_length);
|
||||
if (pass) {
|
||||
packet.write_string(pass, pass_length);
|
||||
}
|
||||
}
|
||||
|
||||
if (!packet.send()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
wait_for_reply(Packet::CONNACK, [this, connect_return_code](IncomingPacket & packet) {
|
||||
TRACE_FUNCTION
|
||||
if (packet.size != 2) {
|
||||
on_protocol_violation();
|
||||
return;
|
||||
}
|
||||
|
||||
/* const uint8_t connect_ack_flags = */ packet.read_u8();
|
||||
const uint8_t crc = packet.read_u8();
|
||||
|
||||
if (connect_return_code) {
|
||||
*connect_return_code = (ConnectReturnCode) crc;
|
||||
}
|
||||
|
||||
if (crc != 0) {
|
||||
// connection refused
|
||||
client.stop();
|
||||
}
|
||||
});
|
||||
|
||||
return client.connected();
|
||||
}
|
||||
|
||||
void BasicClient::loop() {
|
||||
TRACE_FUNCTION
|
||||
|
||||
if (client.connected() && get_millis_since_last_write() >= keep_alive_millis) {
|
||||
// ping time!
|
||||
build_packet(Packet::PINGREQ).send();
|
||||
wait_for_reply(Packet::PINGRESP, [](IncomingPacket &) {});
|
||||
}
|
||||
|
||||
Connection::loop();
|
||||
}
|
||||
|
||||
Publisher::Publish BasicClient::begin_publish(const char * topic, const size_t payload_size,
|
||||
uint8_t qos, bool retain, uint16_t message_id) {
|
||||
TRACE_FUNCTION
|
||||
return Publish(
|
||||
*this,
|
||||
client.status() ? client : PrintMux(),
|
||||
topic, payload_size,
|
||||
(qos >= 1) ? 1 : 0,
|
||||
retain,
|
||||
message_id, // dup if message_id is non-zero
|
||||
message_id ? message_id : message_id_generator.generate() // generate only if message_id == 0
|
||||
);
|
||||
}
|
||||
|
||||
bool BasicClient::on_publish_complete(const Publish & publish) {
|
||||
TRACE_FUNCTION
|
||||
if (publish.qos == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool confirmed = false;
|
||||
wait_for_reply(Packet::PUBACK, [&publish, &confirmed](IncomingPacket & puback) {
|
||||
confirmed |= (puback.read_u16() == publish.message_id);
|
||||
});
|
||||
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
bool BasicClient::subscribe(const String & topic, uint8_t qos, uint8_t * qos_granted) {
|
||||
TRACE_FUNCTION
|
||||
if (qos > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t topic_size = topic.length();
|
||||
const uint16_t message_id = message_id_generator.generate();
|
||||
|
||||
auto packet = build_packet(Packet::SUBSCRIBE, 0b0010, 2 + 2 + topic_size + 1);
|
||||
packet.write_u16(message_id);
|
||||
packet.write_string(topic.c_str(), topic_size);
|
||||
packet.write_u8(qos);
|
||||
packet.send();
|
||||
|
||||
uint8_t code = 0x80;
|
||||
|
||||
wait_for_reply(Packet::SUBACK, [this, message_id, &code](IncomingPacket & packet) {
|
||||
if (packet.read_u16() != message_id) {
|
||||
on_protocol_violation();
|
||||
} else {
|
||||
code = packet.read_u8();
|
||||
}
|
||||
});
|
||||
|
||||
if (code == 0x80) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (qos_granted) {
|
||||
*qos_granted = code;
|
||||
}
|
||||
|
||||
return client.connected();
|
||||
}
|
||||
|
||||
bool BasicClient::unsubscribe(const String & topic) {
|
||||
TRACE_FUNCTION
|
||||
|
||||
const size_t topic_size = topic.length();
|
||||
const uint16_t message_id = message_id_generator.generate();
|
||||
|
||||
auto packet = build_packet(Packet::UNSUBSCRIBE, 0b0010, 2 + 2 + topic_size);
|
||||
packet.write_u16(message_id);
|
||||
packet.write_string(topic.c_str(), topic_size);
|
||||
packet.send();
|
||||
|
||||
wait_for_reply(Packet::UNSUBACK, [this, message_id](IncomingPacket & packet) {
|
||||
if (packet.read_u16() != message_id) {
|
||||
on_protocol_violation();
|
||||
}
|
||||
});
|
||||
|
||||
return client.connected();
|
||||
}
|
||||
|
||||
Client::Client(const char * host, uint16_t port, const char * id, const char * user, const char * password,
|
||||
unsigned long reconnect_interval_millis)
|
||||
: host(host), port(port), client_id(id), username(user), password(password),
|
||||
will({"", "", 0, false}),
|
||||
reconnect_interval_millis(reconnect_interval_millis),
|
||||
last_reconnect_attempt(millis() - reconnect_interval_millis) {
|
||||
TRACE_FUNCTION
|
||||
}
|
||||
|
||||
Client::SubscriptionId Client::subscribe(const String & topic_filter, MessageCallback callback) {
|
||||
TRACE_FUNCTION
|
||||
BasicClient::subscribe(topic_filter);
|
||||
return SubscribedMessageListener::subscribe(topic_filter, callback);
|
||||
}
|
||||
|
||||
void Client::unsubscribe(const String & topic_filter) {
|
||||
TRACE_FUNCTION
|
||||
BasicClient::unsubscribe(topic_filter);
|
||||
SubscribedMessageListener::unsubscribe(topic_filter);
|
||||
}
|
||||
|
||||
void Client::on_message(const char * topic, IncomingPacket & packet) {
|
||||
SubscribedMessageListener::fire_message_callbacks(topic, packet);
|
||||
}
|
||||
|
||||
void Client::loop() {
|
||||
TRACE_FUNCTION
|
||||
if (!client.connected()) {
|
||||
if (host.isEmpty() || !port) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (millis() - last_reconnect_attempt < reconnect_interval_millis) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool connection_established = connect(host.c_str(), port,
|
||||
client_id.isEmpty() ? "" : client_id.c_str(),
|
||||
username.isEmpty() ? nullptr : username.c_str(),
|
||||
password.isEmpty() ? nullptr : password.c_str(),
|
||||
will.topic.isEmpty() ? nullptr : will.topic.c_str(),
|
||||
will.payload.isEmpty() ? nullptr : will.payload.c_str(),
|
||||
will.payload.isEmpty() ? 0 : will.payload.length(),
|
||||
will.qos, will.retain);
|
||||
|
||||
last_reconnect_attempt = millis();
|
||||
|
||||
if (!connection_established) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto & kv : subscriptions) {
|
||||
BasicClient::subscribe(kv.first.c_str());
|
||||
}
|
||||
|
||||
on_connect();
|
||||
}
|
||||
|
||||
BasicClient::loop();
|
||||
}
|
||||
|
||||
void Client::on_connect() {
|
||||
TRACE_FUNCTION
|
||||
BasicClient::on_connect();
|
||||
if (connected_callback) {
|
||||
connected_callback();
|
||||
}
|
||||
}
|
||||
|
||||
void Client::on_disconnect() {
|
||||
TRACE_FUNCTION
|
||||
BasicClient::on_disconnect();
|
||||
if (disconnected_callback) {
|
||||
connected_callback();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
82
lib/PicoMQTT/src/PicoMQTT/client.h
Normal file
82
lib/PicoMQTT/src/PicoMQTT/client.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "connection.h"
|
||||
#include "incoming_packet.h"
|
||||
#include "outgoing_packet.h"
|
||||
#include "pico_interface.h"
|
||||
#include "publisher.h"
|
||||
#include "subscriber.h"
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
class BasicClient: public PicoMQTTInterface, public Connection, public Publisher {
|
||||
public:
|
||||
BasicClient(unsigned long keep_alive_seconds = 60, unsigned long socket_timeout_seconds = 10);
|
||||
|
||||
BasicClient(const ::WiFiClient & client, unsigned long keep_alive_seconds = 60,
|
||||
unsigned long socket_timeout_seconds = 10);
|
||||
|
||||
bool connect(
|
||||
const char * host, uint16_t port = 1883,
|
||||
const char * id = "", const char * user = nullptr, const char * pass = nullptr,
|
||||
const char * will_topic = nullptr, const char * will_message = nullptr,
|
||||
const size_t will_message_length = 0, uint8_t willQos = 0, bool willRetain = false,
|
||||
const bool cleanSession = true,
|
||||
ConnectReturnCode * connect_return_code = nullptr);
|
||||
|
||||
using Publisher::begin_publish;
|
||||
virtual Publish begin_publish(const char * topic, const size_t payload_size,
|
||||
uint8_t qos = 0, bool retain = false, uint16_t message_id = 0) override;
|
||||
|
||||
bool subscribe(const String & topic, uint8_t qos = 0, uint8_t * qos_granted = nullptr);
|
||||
bool unsubscribe(const String & topic);
|
||||
|
||||
void loop() override;
|
||||
|
||||
virtual void on_connect() {}
|
||||
|
||||
private:
|
||||
virtual bool on_publish_complete(const Publish & publish) override;
|
||||
};
|
||||
|
||||
class Client: public BasicClient, public SubscribedMessageListener {
|
||||
public:
|
||||
Client(const char * host = nullptr, uint16_t port = 1883, const char * id = nullptr, const char * user = nullptr,
|
||||
const char * password = nullptr, unsigned long reconnect_interval_millis = 5 * 1000);
|
||||
|
||||
using SubscribedMessageListener::subscribe;
|
||||
virtual SubscriptionId subscribe(const String & topic_filter, MessageCallback callback) override;
|
||||
virtual void unsubscribe(const String & topic_filter) override;
|
||||
|
||||
virtual void loop() override;
|
||||
|
||||
String host;
|
||||
uint16_t port;
|
||||
|
||||
String client_id;
|
||||
String username;
|
||||
String password;
|
||||
|
||||
struct {
|
||||
String topic;
|
||||
String payload;
|
||||
uint8_t qos;
|
||||
bool retain;
|
||||
} will;
|
||||
|
||||
unsigned long reconnect_interval_millis;
|
||||
|
||||
std::function<void()> connected_callback;
|
||||
std::function<void()> disconnected_callback;
|
||||
|
||||
virtual void on_connect() override;
|
||||
virtual void on_disconnect() override;
|
||||
|
||||
protected:
|
||||
unsigned long last_reconnect_attempt;
|
||||
virtual void on_message(const char * topic, IncomingPacket & packet) override;
|
||||
};
|
||||
|
||||
}
|
||||
119
lib/PicoMQTT/src/PicoMQTT/client_wrapper.cpp
Normal file
119
lib/PicoMQTT/src/PicoMQTT/client_wrapper.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
#include "Arduino.h"
|
||||
|
||||
#include "client_wrapper.h"
|
||||
|
||||
#include "debug.h"
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
ClientWrapper::ClientWrapper(unsigned long socket_timeout_seconds): socket_timeout_millis(
|
||||
socket_timeout_seconds * 1000) {
|
||||
TRACE_FUNCTION
|
||||
}
|
||||
|
||||
ClientWrapper::ClientWrapper(const ::WiFiClient & client, unsigned long socket_timeout_seconds):
|
||||
WiFiClient(client), socket_timeout_millis(socket_timeout_seconds * 1000) {
|
||||
TRACE_FUNCTION
|
||||
}
|
||||
|
||||
// reads
|
||||
int ClientWrapper::available_wait(unsigned long timeout) {
|
||||
TRACE_FUNCTION
|
||||
const unsigned long start_millis = millis();
|
||||
while (true) {
|
||||
const int ret = available();
|
||||
if (ret > 0) {
|
||||
return ret;
|
||||
}
|
||||
if (!status()) {
|
||||
// A disconnected client might still have unread data waiting in buffers. Don't move this check earlier.
|
||||
return 0;
|
||||
}
|
||||
const unsigned long elapsed = millis() - start_millis;
|
||||
if (elapsed > timeout) {
|
||||
return 0;
|
||||
}
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
int ClientWrapper::read(uint8_t * buf, size_t size) {
|
||||
TRACE_FUNCTION
|
||||
const unsigned long start_millis = millis();
|
||||
size_t ret = 0;
|
||||
|
||||
while (ret < size) {
|
||||
const unsigned long now_millis = millis();
|
||||
const unsigned long elapsed_millis = now_millis - start_millis;
|
||||
|
||||
if (elapsed_millis > socket_timeout_millis) {
|
||||
// timeout
|
||||
abort();
|
||||
break;
|
||||
}
|
||||
|
||||
const unsigned long remaining_millis = socket_timeout_millis - elapsed_millis;
|
||||
|
||||
const int available_size = available_wait(remaining_millis);
|
||||
if (available_size <= 0) {
|
||||
// timeout
|
||||
abort();
|
||||
break;
|
||||
}
|
||||
|
||||
const int chunk_size = size - ret < (size_t) available_size ? size - ret : (size_t) available_size;
|
||||
|
||||
const int bytes_read = WiFiClient::read(buf + ret, chunk_size);
|
||||
if (bytes_read <= 0) {
|
||||
// connection error
|
||||
abort();
|
||||
break;
|
||||
}
|
||||
|
||||
ret += bytes_read;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ClientWrapper::read() {
|
||||
TRACE_FUNCTION
|
||||
if (!available_wait(socket_timeout_millis)) {
|
||||
return -1;
|
||||
}
|
||||
return WiFiClient::read();
|
||||
}
|
||||
|
||||
int ClientWrapper::peek() {
|
||||
TRACE_FUNCTION
|
||||
if (!available_wait(socket_timeout_millis)) {
|
||||
return -1;
|
||||
}
|
||||
return WiFiClient::peek();
|
||||
}
|
||||
|
||||
// writes
|
||||
size_t ClientWrapper::write(const uint8_t * buffer, size_t size) {
|
||||
TRACE_FUNCTION
|
||||
size_t ret = 0;
|
||||
|
||||
while (status() && ret < size) {
|
||||
const int bytes_written = WiFiClient::write(buffer + ret, size - ret);
|
||||
if (bytes_written <= 0) {
|
||||
// connection error
|
||||
abort();
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret += bytes_written;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t ClientWrapper::write(uint8_t value) {
|
||||
TRACE_FUNCTION
|
||||
return write(&value, 1);
|
||||
}
|
||||
|
||||
}
|
||||
31
lib/PicoMQTT/src/PicoMQTT/client_wrapper.h
Normal file
31
lib/PicoMQTT/src/PicoMQTT/client_wrapper.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <WiFiClient.h>
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
class ClientWrapper: public ::WiFiClient {
|
||||
public:
|
||||
ClientWrapper(unsigned long socket_timeout_seconds);
|
||||
ClientWrapper(const WiFiClient & client, unsigned long socket_timeout_seconds);
|
||||
ClientWrapper(const ClientWrapper &) = default;
|
||||
|
||||
virtual int peek() override;
|
||||
virtual int read() override;
|
||||
virtual int read(uint8_t * buf, size_t size) override;
|
||||
virtual size_t write(const uint8_t * buffer, size_t size) override;
|
||||
virtual size_t write(uint8_t value) override final;
|
||||
|
||||
#ifdef ESP32
|
||||
// these methods are only available in WiFiClient on ESP8266
|
||||
uint8_t status() { return connected(); }
|
||||
void abort() { stop(); }
|
||||
#endif
|
||||
|
||||
const unsigned long socket_timeout_millis;
|
||||
|
||||
protected:
|
||||
int available_wait(unsigned long timeout);
|
||||
};
|
||||
|
||||
}
|
||||
29
lib/PicoMQTT/src/PicoMQTT/config.h
Normal file
29
lib/PicoMQTT/src/PicoMQTT/config.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef PICOMQTT_MAX_TOPIC_SIZE
|
||||
#define PICOMQTT_MAX_TOPIC_SIZE 256
|
||||
#endif
|
||||
|
||||
#ifndef PICOMQTT_MAX_MESSAGE_SIZE
|
||||
#define PICOMQTT_MAX_MESSAGE_SIZE 1024
|
||||
#endif
|
||||
|
||||
#ifndef PICOMQTT_MAX_CLIENT_ID_SIZE
|
||||
/*
|
||||
* The MQTT standard requires brokers to accept client ids that are
|
||||
* 1-23 chars long, but allows longer client IDs to be accepted too.
|
||||
*/
|
||||
#define PICOMQTT_MAX_CLIENT_ID_SIZE 64
|
||||
#endif
|
||||
|
||||
#ifndef PICOMQTT_MAX_USERPASS_SIZE
|
||||
#define PICOMQTT_MAX_USERPASS_SIZE 256
|
||||
#endif
|
||||
|
||||
#ifndef PICOMQTT_OUTGOING_BUFFER_SIZE
|
||||
#define PICOMQTT_OUTGOING_BUFFER_SIZE 128
|
||||
#endif
|
||||
|
||||
// #define PICOMQTT_DEBUG
|
||||
|
||||
// #define PICOMQTT_DEBUG_TRACE_FUNCTIONS
|
||||
176
lib/PicoMQTT/src/PicoMQTT/connection.cpp
Normal file
176
lib/PicoMQTT/src/PicoMQTT/connection.cpp
Normal file
@@ -0,0 +1,176 @@
|
||||
#include "config.h"
|
||||
#include "connection.h"
|
||||
#include "debug.h"
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
Connection::Connection(unsigned long keep_alive_seconds, unsigned long socket_timeout_seconds) :
|
||||
client(socket_timeout_seconds),
|
||||
keep_alive_millis(keep_alive_seconds * 1000),
|
||||
last_read(millis()), last_write(millis()) {
|
||||
TRACE_FUNCTION
|
||||
}
|
||||
|
||||
Connection::Connection(const ::WiFiClient & client, unsigned long keep_alive_seconds,
|
||||
unsigned long socket_timeout_seconds) :
|
||||
client(client, socket_timeout_seconds),
|
||||
keep_alive_millis(keep_alive_seconds * 1000),
|
||||
last_read(millis()), last_write(millis()) {
|
||||
TRACE_FUNCTION
|
||||
}
|
||||
|
||||
OutgoingPacket Connection::build_packet(Packet::Type type, uint8_t flags, size_t length) {
|
||||
TRACE_FUNCTION
|
||||
last_write = millis();
|
||||
auto ret = OutgoingPacket(client, type, flags, length);
|
||||
ret.write_header();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Connection::on_timeout() {
|
||||
TRACE_FUNCTION
|
||||
client.abort();
|
||||
on_disconnect();
|
||||
}
|
||||
|
||||
void Connection::on_protocol_violation() {
|
||||
TRACE_FUNCTION
|
||||
on_disconnect();
|
||||
}
|
||||
|
||||
void Connection::on_disconnect() {
|
||||
TRACE_FUNCTION
|
||||
client.stop();
|
||||
}
|
||||
|
||||
void Connection::disconnect() {
|
||||
TRACE_FUNCTION
|
||||
build_packet(Packet::DISCONNECT).send();
|
||||
client.stop();
|
||||
}
|
||||
|
||||
bool Connection::connected() {
|
||||
TRACE_FUNCTION
|
||||
return client.connected();
|
||||
}
|
||||
|
||||
void Connection::wait_for_reply(Packet::Type type, std::function<void(IncomingPacket & packet)> handler) {
|
||||
TRACE_FUNCTION
|
||||
|
||||
const unsigned long start = millis();
|
||||
|
||||
while (client.connected() && (millis() - start < client.socket_timeout_millis)) {
|
||||
|
||||
IncomingPacket packet(client);
|
||||
if (!packet) {
|
||||
break;
|
||||
}
|
||||
|
||||
last_read = millis();
|
||||
|
||||
if (packet.get_type() == type) {
|
||||
handler(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
handle_packet(packet);
|
||||
|
||||
}
|
||||
|
||||
if (client.connected()) {
|
||||
on_timeout();
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::send_ack(Packet::Type ack_type, uint16_t msg_id) {
|
||||
TRACE_FUNCTION
|
||||
auto ack = build_packet(ack_type, 0, 2);
|
||||
ack.write_u16(msg_id);
|
||||
ack.send();
|
||||
}
|
||||
|
||||
void Connection::handle_packet(IncomingPacket & packet) {
|
||||
TRACE_FUNCTION
|
||||
|
||||
switch (packet.get_type()) {
|
||||
case Packet::PUBLISH: {
|
||||
const uint16_t topic_size = packet.read_u16();
|
||||
|
||||
// const bool dup = (packet.get_flags() >> 3) & 0b1;
|
||||
const uint8_t qos = (packet.get_flags() >> 1) & 0b11;
|
||||
// const bool retain = packet.get_flags() & 0b1;
|
||||
|
||||
uint16_t msg_id = 0;
|
||||
|
||||
if (topic_size > PICOMQTT_MAX_TOPIC_SIZE) {
|
||||
packet.ignore(topic_size);
|
||||
on_topic_too_long(packet);
|
||||
if (qos) {
|
||||
msg_id = packet.read_u16();
|
||||
}
|
||||
} else {
|
||||
char topic[topic_size + 1];
|
||||
if (!packet.read_string(topic, topic_size)) {
|
||||
// connection error
|
||||
return;
|
||||
}
|
||||
if (qos) {
|
||||
msg_id = packet.read_u16();
|
||||
}
|
||||
on_message(topic, packet);
|
||||
}
|
||||
|
||||
if (msg_id) {
|
||||
send_ack(qos == 1 ? Packet::PUBACK : Packet::PUBREC, msg_id);
|
||||
}
|
||||
|
||||
break;
|
||||
};
|
||||
|
||||
case Packet::PUBREC:
|
||||
send_ack(Packet::PUBREL, packet.read_u16());
|
||||
break;
|
||||
|
||||
case Packet::PUBREL:
|
||||
send_ack(Packet::PUBCOMP, packet.read_u16());
|
||||
break;
|
||||
|
||||
case Packet::PUBCOMP:
|
||||
// ignore
|
||||
break;
|
||||
|
||||
case Packet::DISCONNECT:
|
||||
on_disconnect();
|
||||
break;
|
||||
|
||||
default:
|
||||
on_protocol_violation();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long Connection::get_millis_since_last_read() const {
|
||||
TRACE_FUNCTION
|
||||
return millis() - last_read;
|
||||
}
|
||||
|
||||
unsigned long Connection::get_millis_since_last_write() const {
|
||||
TRACE_FUNCTION
|
||||
return millis() - last_write;
|
||||
}
|
||||
|
||||
void Connection::loop() {
|
||||
TRACE_FUNCTION
|
||||
|
||||
// only handle 10 packets max in one go to not starve other connections
|
||||
for (unsigned int i = 0; (i < 10) && client.available(); ++i) {
|
||||
IncomingPacket packet(client);
|
||||
if (!packet.is_valid()) {
|
||||
return;
|
||||
}
|
||||
last_read = millis();
|
||||
handle_packet(packet);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
80
lib/PicoMQTT/src/PicoMQTT/connection.h
Normal file
80
lib/PicoMQTT/src/PicoMQTT/connection.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "client_wrapper.h"
|
||||
#include "incoming_packet.h"
|
||||
#include "outgoing_packet.h"
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
enum ConnectReturnCode : uint8_t {
|
||||
CRC_ACCEPTED = 0,
|
||||
CRC_UNACCEPTABLE_PROTOCOL_VERSION = 1,
|
||||
CRC_IDENTIFIER_REJECTED = 2,
|
||||
CRC_SERVER_UNAVAILABLE = 3,
|
||||
CRC_BAD_USERNAME_OR_PASSWORD = 4,
|
||||
CRC_NOT_AUTHORIZED = 5,
|
||||
|
||||
// internal
|
||||
CRC_UNDEFINED = 255,
|
||||
};
|
||||
|
||||
class Connection {
|
||||
public:
|
||||
Connection(unsigned long keep_alive_seconds = 0, unsigned long socket_timeout_seconds = 15);
|
||||
Connection(const ::WiFiClient & client, unsigned long keep_alive_seconds = 0,
|
||||
unsigned long socket_timeout_seconds = 15);
|
||||
Connection(const Connection &) = default;
|
||||
|
||||
virtual ~Connection() {}
|
||||
|
||||
bool connected();
|
||||
void disconnect();
|
||||
|
||||
virtual void loop();
|
||||
|
||||
protected:
|
||||
class MessageIdGenerator {
|
||||
public:
|
||||
MessageIdGenerator(): value(0) {}
|
||||
uint16_t generate() {
|
||||
if (++value == 0) { value = 1; }
|
||||
return value;
|
||||
}
|
||||
|
||||
void reset() { value = 0; }
|
||||
|
||||
protected:
|
||||
uint16_t value;
|
||||
} message_id_generator;
|
||||
|
||||
OutgoingPacket build_packet(Packet::Type type, uint8_t flags = 0, size_t length = 0);
|
||||
|
||||
void wait_for_reply(Packet::Type type, std::function<void(IncomingPacket & packet)> handler);
|
||||
|
||||
virtual void on_topic_too_long(const IncomingPacket & packet) {}
|
||||
virtual void on_message(const char * topic, IncomingPacket & packet) {}
|
||||
|
||||
virtual void on_timeout();
|
||||
virtual void on_protocol_violation();
|
||||
virtual void on_disconnect();
|
||||
|
||||
ClientWrapper client;
|
||||
uint16_t keep_alive_millis;
|
||||
|
||||
virtual void handle_packet(IncomingPacket & packet);
|
||||
|
||||
protected:
|
||||
unsigned long get_millis_since_last_read() const;
|
||||
unsigned long get_millis_since_last_write() const;
|
||||
|
||||
private:
|
||||
unsigned long last_read;
|
||||
unsigned long last_write;
|
||||
void send_ack(Packet::Type ack_type, uint16_t msg_id);
|
||||
};
|
||||
|
||||
}
|
||||
50
lib/PicoMQTT/src/PicoMQTT/debug.h
Normal file
50
lib/PicoMQTT/src/PicoMQTT/debug.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef PICOMQTT_DEBUG_TRACE_FUNCTIONS
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
class FunctionTracer {
|
||||
public:
|
||||
FunctionTracer(const char * function_name) : function_name(function_name) {
|
||||
indent(1);
|
||||
Serial.print(F("CALL "));
|
||||
Serial.println(function_name);
|
||||
}
|
||||
|
||||
~FunctionTracer() {
|
||||
indent(-1);
|
||||
Serial.print(F("RETURN "));
|
||||
Serial.println(function_name);
|
||||
}
|
||||
|
||||
const char * const function_name;
|
||||
|
||||
protected:
|
||||
void indent(int delta) {
|
||||
static int depth = 0;
|
||||
if (delta < 0) {
|
||||
depth += delta;
|
||||
}
|
||||
for (int i = 0; i < depth; ++i) {
|
||||
Serial.print(" ");
|
||||
}
|
||||
if (delta > 0) {
|
||||
depth += delta;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#define TRACE_FUNCTION FunctionTracer _function_tracer(__PRETTY_FUNCTION__);
|
||||
|
||||
#else
|
||||
|
||||
#define TRACE_FUNCTION
|
||||
|
||||
#endif
|
||||
171
lib/PicoMQTT/src/PicoMQTT/incoming_packet.cpp
Normal file
171
lib/PicoMQTT/src/PicoMQTT/incoming_packet.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
#include "incoming_packet.h"
|
||||
#include "debug.h"
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
IncomingPacket::IncomingPacket(Client & client)
|
||||
: Packet(read_header(client)), client(client) {
|
||||
TRACE_FUNCTION
|
||||
}
|
||||
|
||||
IncomingPacket::IncomingPacket(IncomingPacket && other)
|
||||
: Packet(other), client(other.client) {
|
||||
TRACE_FUNCTION
|
||||
other.pos = size;
|
||||
}
|
||||
|
||||
IncomingPacket::~IncomingPacket() {
|
||||
TRACE_FUNCTION
|
||||
#ifdef PICOMQTT_DEBUG
|
||||
if (pos != size) {
|
||||
Serial.print(F("IncomingPacket read incorrect number of bytes: "));
|
||||
Serial.print(pos);
|
||||
Serial.print(F("/"));
|
||||
Serial.println(size);
|
||||
}
|
||||
#endif
|
||||
// read and ignore remaining data
|
||||
while (get_remaining_size() && (read() >= 0));
|
||||
}
|
||||
|
||||
// disabled functions
|
||||
int IncomingPacket::connect(IPAddress ip, uint16_t port) {
|
||||
TRACE_FUNCTION;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int IncomingPacket::connect(const char * host, uint16_t port) {
|
||||
TRACE_FUNCTION;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t IncomingPacket::write(const uint8_t * buffer, size_t size) {
|
||||
TRACE_FUNCTION
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t IncomingPacket::write(uint8_t value) {
|
||||
TRACE_FUNCTION
|
||||
return 0;
|
||||
}
|
||||
|
||||
void IncomingPacket::flush() {
|
||||
TRACE_FUNCTION
|
||||
}
|
||||
|
||||
void IncomingPacket::stop() {
|
||||
TRACE_FUNCTION
|
||||
}
|
||||
|
||||
|
||||
// extended functions
|
||||
int IncomingPacket::available() {
|
||||
TRACE_FUNCTION;
|
||||
return get_remaining_size();
|
||||
}
|
||||
|
||||
int IncomingPacket::peek() {
|
||||
TRACE_FUNCTION
|
||||
if (!get_remaining_size()) {
|
||||
#if PICOMQTT_DEBUG
|
||||
Serial.println(F("Attempt to peek beyond end of IncomingPacket."));
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
return client.peek();
|
||||
}
|
||||
|
||||
int IncomingPacket::read() {
|
||||
TRACE_FUNCTION
|
||||
if (!get_remaining_size()) {
|
||||
#if PICOMQTT_DEBUG
|
||||
Serial.println(F("Attempt to read beyond end of IncomingPacket."));
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
const int ret = client.read();
|
||||
if (ret >= 0) {
|
||||
++pos;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int IncomingPacket::read(uint8_t * buf, size_t size) {
|
||||
TRACE_FUNCTION
|
||||
const size_t remaining = get_remaining_size();
|
||||
const size_t read_size = remaining < size ? remaining : size;
|
||||
#if PICOMQTT_DEBUG
|
||||
if (size > remaining) {
|
||||
Serial.println(F("Attempt to read buf beyond end of IncomingPacket."));
|
||||
}
|
||||
#endif
|
||||
const int ret = client.read(buf, read_size);
|
||||
if (ret > 0) {
|
||||
pos += ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
IncomingPacket::operator bool() {
|
||||
TRACE_FUNCTION
|
||||
return is_valid() && bool(client);
|
||||
}
|
||||
|
||||
uint8_t IncomingPacket::connected() {
|
||||
TRACE_FUNCTION
|
||||
return is_valid() && client.connected();
|
||||
}
|
||||
|
||||
// extra functions
|
||||
uint8_t IncomingPacket::read_u8() {
|
||||
TRACE_FUNCTION;
|
||||
return get_remaining_size() ? read() : 0;
|
||||
}
|
||||
|
||||
uint16_t IncomingPacket::read_u16() {
|
||||
TRACE_FUNCTION;
|
||||
return ((uint16_t) read_u8()) << 8 | ((uint16_t) read_u8());
|
||||
}
|
||||
|
||||
bool IncomingPacket::read_string(char * buffer, size_t len) {
|
||||
if (read((uint8_t *) buffer, len) != (int) len) {
|
||||
return false;
|
||||
}
|
||||
buffer[len] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
void IncomingPacket::ignore(size_t len) {
|
||||
while (len--) {
|
||||
read();
|
||||
}
|
||||
}
|
||||
|
||||
Packet IncomingPacket::read_header(Client & client) {
|
||||
TRACE_FUNCTION
|
||||
const int head = client.read();
|
||||
if (head <= 0) {
|
||||
return Packet();
|
||||
}
|
||||
|
||||
uint32_t size = 0;
|
||||
for (size_t length_size = 0; ; ++length_size) {
|
||||
if (length_size >= 5) {
|
||||
return Packet();
|
||||
}
|
||||
const int digit = client.read();
|
||||
if (digit < 0) {
|
||||
return Packet();
|
||||
}
|
||||
|
||||
size |= (digit & 0x7f) << (7 * length_size);
|
||||
|
||||
if (!(digit & 0x80)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Packet(head, size);
|
||||
}
|
||||
|
||||
}
|
||||
46
lib/PicoMQTT/src/PicoMQTT/incoming_packet.h
Normal file
46
lib/PicoMQTT/src/PicoMQTT/incoming_packet.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Client.h>
|
||||
|
||||
#include "packet.h"
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
class IncomingPacket: public Packet, public Client {
|
||||
public:
|
||||
IncomingPacket(Client & client);
|
||||
IncomingPacket(IncomingPacket &&);
|
||||
|
||||
IncomingPacket(const IncomingPacket &) = delete;
|
||||
const IncomingPacket & operator=(const IncomingPacket &) = delete;
|
||||
|
||||
~IncomingPacket();
|
||||
|
||||
virtual int available() override;
|
||||
virtual int connect(IPAddress ip, uint16_t port) override;
|
||||
virtual int connect(const char * host, uint16_t port) override;
|
||||
virtual int peek() override;
|
||||
virtual int read() override;
|
||||
virtual int read(uint8_t * buf, size_t size) override;
|
||||
// This operator is not marked explicit in the Client base class. Still, we're marking it explicit here
|
||||
// to block implicit conversions to integer types.
|
||||
virtual explicit operator bool() override;
|
||||
virtual size_t write(const uint8_t * buffer, size_t size) override;
|
||||
virtual size_t write(uint8_t value) override final;
|
||||
virtual uint8_t connected() override;
|
||||
virtual void flush() override;
|
||||
virtual void stop() override;
|
||||
|
||||
uint8_t read_u8();
|
||||
uint16_t read_u16();
|
||||
bool read_string(char * buffer, size_t len);
|
||||
void ignore(size_t len);
|
||||
|
||||
protected:
|
||||
static Packet read_header(Client & client);
|
||||
|
||||
Client & client;
|
||||
};
|
||||
|
||||
}
|
||||
224
lib/PicoMQTT/src/PicoMQTT/outgoing_packet.cpp
Normal file
224
lib/PicoMQTT/src/PicoMQTT/outgoing_packet.cpp
Normal file
@@ -0,0 +1,224 @@
|
||||
#include <Client.h>
|
||||
#include <Print.h>
|
||||
|
||||
#include "debug.h"
|
||||
#include "outgoing_packet.h"
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
OutgoingPacket::OutgoingPacket(Print & print, Packet::Type type, uint8_t flags, size_t payload_size)
|
||||
: Packet(type, flags, payload_size), print(print),
|
||||
#ifndef PICOMQTT_UNBUFFERED
|
||||
buffer_position(0),
|
||||
#endif
|
||||
state(State::ok) {
|
||||
TRACE_FUNCTION
|
||||
}
|
||||
|
||||
OutgoingPacket::OutgoingPacket(OutgoingPacket && other)
|
||||
: OutgoingPacket(other) {
|
||||
TRACE_FUNCTION
|
||||
other.state = State::dead;
|
||||
}
|
||||
|
||||
OutgoingPacket::~OutgoingPacket() {
|
||||
TRACE_FUNCTION
|
||||
#ifdef PICOMQTT_DEBUG
|
||||
#ifndef PICOMQTT_UNBUFFERED
|
||||
if (buffer_position) {
|
||||
Serial.printf("OutgoingPacket has unsent data in the buffer (pos=%u)\n", buffer_position);
|
||||
}
|
||||
#endif
|
||||
switch (state) {
|
||||
case State::ok:
|
||||
Serial.println(F("Unsent OutgoingPacket"));
|
||||
break;
|
||||
case State::sent:
|
||||
if (pos != size) {
|
||||
Serial.print(F("OutgoingPacket sent incorrect number of bytes: "));
|
||||
Serial.print(pos);
|
||||
Serial.print(F("/"));
|
||||
Serial.println(size);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t OutgoingPacket::write_from_client(::Client & client, size_t length) {
|
||||
TRACE_FUNCTION
|
||||
size_t written = 0;
|
||||
#ifndef PICOMQTT_UNBUFFERED
|
||||
while (written < length) {
|
||||
const size_t remaining = length - written;
|
||||
const size_t remaining_buffer_space = PICOMQTT_OUTGOING_BUFFER_SIZE - buffer_position;
|
||||
const size_t chunk_size = remaining < remaining_buffer_space ? remaining : remaining_buffer_space;
|
||||
|
||||
const int read_size = client.read(buffer + buffer_position, chunk_size);
|
||||
if (read_size <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
buffer_position += (size_t) read_size;
|
||||
written += (size_t) read_size;
|
||||
|
||||
if (buffer_position >= PICOMQTT_OUTGOING_BUFFER_SIZE) {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
#else
|
||||
uint8_t buffer[128] __attribute__((aligned(4)));
|
||||
while (written < length) {
|
||||
const size_t remain = length - written;
|
||||
const size_t chunk_size = sizeof(buffer) < remain ? sizeof(buffer) : remain;
|
||||
const int read_size = client.read(buffer, chunk_size);
|
||||
if (read_size <= 0) {
|
||||
break;
|
||||
}
|
||||
const size_t write_size = print.write(buffer, read_size);
|
||||
written += write_size;
|
||||
if (!write_size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
pos += written;
|
||||
return written;
|
||||
}
|
||||
|
||||
size_t OutgoingPacket::write_zero(size_t length) {
|
||||
TRACE_FUNCTION
|
||||
for (size_t written = 0; written < length; ++written) {
|
||||
write_u8('0');
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
#ifndef PICOMQTT_UNBUFFERED
|
||||
size_t OutgoingPacket::write(const void * data, size_t remaining, void * (*memcpy_fn)(void *, const void *, size_t n)) {
|
||||
TRACE_FUNCTION
|
||||
|
||||
const char * src = (const char *) data;
|
||||
|
||||
while (remaining) {
|
||||
const size_t remaining_buffer_space = PICOMQTT_OUTGOING_BUFFER_SIZE - buffer_position;
|
||||
const size_t chunk_size = remaining < remaining_buffer_space ? remaining : remaining_buffer_space;
|
||||
|
||||
memcpy_fn(buffer + buffer_position, src, chunk_size);
|
||||
|
||||
buffer_position += chunk_size;
|
||||
src += chunk_size;
|
||||
remaining -= chunk_size;
|
||||
|
||||
if (buffer_position >= PICOMQTT_OUTGOING_BUFFER_SIZE) {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
const size_t written = src - (const char *) data;
|
||||
pos += written;
|
||||
return written;
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t OutgoingPacket::write(const uint8_t * data, size_t length) {
|
||||
TRACE_FUNCTION
|
||||
#ifndef PICOMQTT_UNBUFFERED
|
||||
return write(data, length, memcpy);
|
||||
#else
|
||||
const size_t written = print.write(data, length);
|
||||
pos += written;
|
||||
return written;
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t OutgoingPacket::write_P(PGM_P data, size_t length) {
|
||||
TRACE_FUNCTION
|
||||
#ifndef PICOMQTT_UNBUFFERED
|
||||
return write(data, length, memcpy_P);
|
||||
#else
|
||||
// here we will need a buffer
|
||||
uint8_t buffer[128] __attribute__((aligned(4)));
|
||||
size_t written = 0;
|
||||
while (written < length) {
|
||||
const size_t remain = length - written;
|
||||
const size_t chunk_size = sizeof(buffer) < remain ? sizeof(buffer) : remain;
|
||||
memcpy_P(buffer, data, chunk_size);
|
||||
const size_t write_size = print.write(buffer, chunk_size);
|
||||
written += write_size;
|
||||
data += write_size;
|
||||
if (!write_size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos += written;
|
||||
return written;
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t OutgoingPacket::write_u8(uint8_t c) {
|
||||
TRACE_FUNCTION
|
||||
return write(&c, 1);
|
||||
}
|
||||
|
||||
size_t OutgoingPacket::write_u16(uint16_t value) {
|
||||
TRACE_FUNCTION
|
||||
return write_u8(value >> 8) + write_u8(value & 0xff);
|
||||
}
|
||||
|
||||
size_t OutgoingPacket::write_string(const char * string, uint16_t size) {
|
||||
TRACE_FUNCTION
|
||||
return write_u16(size) + write((const uint8_t *) string, size);
|
||||
}
|
||||
|
||||
size_t OutgoingPacket::write_packet_length(size_t length) {
|
||||
TRACE_FUNCTION
|
||||
size_t ret = 0;
|
||||
do {
|
||||
const uint8_t digit = length & 127; // digit := length % 128
|
||||
length >>= 7; // length := length / 128
|
||||
ret += write_u8(digit | (length ? 0x80 : 0));
|
||||
} while (length);
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t OutgoingPacket::write_header() {
|
||||
TRACE_FUNCTION
|
||||
const size_t ret = write_u8(head) + write_packet_length(size);
|
||||
// we've just written the header, payload starts now
|
||||
pos = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void OutgoingPacket::flush() {
|
||||
TRACE_FUNCTION
|
||||
#ifndef PICOMQTT_UNBUFFERED
|
||||
print.write(buffer, buffer_position);
|
||||
buffer_position = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool OutgoingPacket::send() {
|
||||
TRACE_FUNCTION
|
||||
const size_t remaining_size = get_remaining_size();
|
||||
if (remaining_size) {
|
||||
#ifdef PICOMQTT_DEBUG
|
||||
Serial.printf("OutgoingPacket sent called on incomplete payload (%u / %u), filling with zeros.\n", pos, size);
|
||||
#endif
|
||||
write_zero(remaining_size);
|
||||
}
|
||||
flush();
|
||||
switch (state) {
|
||||
case State::ok:
|
||||
// print.flush();
|
||||
state = State::sent;
|
||||
case State::sent:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
64
lib/PicoMQTT/src/PicoMQTT/outgoing_packet.h
Normal file
64
lib/PicoMQTT/src/PicoMQTT/outgoing_packet.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
// #define MQTT_OUTGOING_PACKET_DEBUG
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "packet.h"
|
||||
|
||||
class Print;
|
||||
class Client;
|
||||
|
||||
#if PICOMQTT_OUTGOING_BUFFER_SIZE == 0
|
||||
#define PICOMQTT_UNBUFFERED
|
||||
#endif
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
class OutgoingPacket: public Packet, public Print {
|
||||
public:
|
||||
OutgoingPacket(Print & print, Type type, uint8_t flags, size_t payload_size);
|
||||
virtual ~OutgoingPacket();
|
||||
|
||||
const OutgoingPacket & operator=(const OutgoingPacket &) = delete;
|
||||
OutgoingPacket(OutgoingPacket && other);
|
||||
|
||||
virtual size_t write(const uint8_t * data, size_t length) override;
|
||||
virtual size_t write(uint8_t value) override final { return write(&value, 1); }
|
||||
|
||||
size_t write_P(PGM_P data, size_t length);
|
||||
size_t write_u8(uint8_t value);
|
||||
size_t write_u16(uint16_t value);
|
||||
size_t write_string(const char * string, uint16_t size);
|
||||
size_t write_header();
|
||||
|
||||
size_t write_from_client(::Client & c, size_t length);
|
||||
size_t write_zero(size_t count);
|
||||
|
||||
virtual void flush() override;
|
||||
virtual bool send();
|
||||
|
||||
protected:
|
||||
OutgoingPacket(const OutgoingPacket &) = default;
|
||||
|
||||
size_t write(const void * data, size_t length, void * (*memcpy_fn)(void *, const void *, size_t n));
|
||||
size_t write_packet_length(size_t length);
|
||||
|
||||
Print & print;
|
||||
|
||||
#ifndef PICOMQTT_UNBUFFERED
|
||||
uint8_t buffer[PICOMQTT_OUTGOING_BUFFER_SIZE] __attribute__((aligned(4)));
|
||||
|
||||
size_t buffer_position;
|
||||
#endif
|
||||
|
||||
enum class State {
|
||||
ok,
|
||||
sent,
|
||||
error,
|
||||
dead,
|
||||
} state;
|
||||
};
|
||||
|
||||
}
|
||||
49
lib/PicoMQTT/src/PicoMQTT/packet.h
Normal file
49
lib/PicoMQTT/src/PicoMQTT/packet.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
class Packet {
|
||||
public:
|
||||
enum Type : uint8_t {
|
||||
ERROR = 0,
|
||||
CONNECT = 1 << 4, // Client request to connect to Server
|
||||
CONNACK = 2 << 4, // Connect Acknowledgment
|
||||
PUBLISH = 3 << 4, // Publish message
|
||||
PUBACK = 4 << 4, // Publish Acknowledgment
|
||||
PUBREC = 5 << 4, // Publish Received (assured delivery part 1)
|
||||
PUBREL = 6 << 4, // Publish Release (assured delivery part 2)
|
||||
PUBCOMP = 7 << 4, // Publish Complete (assured delivery part 3)
|
||||
SUBSCRIBE = 8 << 4, // Client Subscribe request
|
||||
SUBACK = 9 << 4, // Subscribe Acknowledgment
|
||||
UNSUBSCRIBE = 10 << 4, // Client Unsubscribe request
|
||||
UNSUBACK = 11 << 4, // Unsubscribe Acknowledgment
|
||||
PINGREQ = 12 << 4, // PING Request
|
||||
PINGRESP = 13 << 4, // PING Response
|
||||
DISCONNECT = 14 << 4, // Client is Disconnecting
|
||||
};
|
||||
|
||||
Packet(uint8_t head, size_t size)
|
||||
: head(head), size(size), pos(0) {}
|
||||
|
||||
Packet(Type type = ERROR, const uint8_t flags = 0, size_t size = 0)
|
||||
: Packet((uint8_t) type | (flags & 0xf), size) {
|
||||
}
|
||||
|
||||
virtual ~Packet() {}
|
||||
|
||||
Type get_type() const { return Type(head & 0xf0); }
|
||||
uint8_t get_flags() const { return head & 0x0f; }
|
||||
|
||||
bool is_valid() { return get_type() != ERROR; }
|
||||
size_t get_remaining_size() const { return pos < size ? size - pos : 0; }
|
||||
|
||||
const uint8_t head;
|
||||
const size_t size;
|
||||
|
||||
protected:
|
||||
size_t pos;
|
||||
};
|
||||
|
||||
}
|
||||
13
lib/PicoMQTT/src/PicoMQTT/pico_interface.h
Normal file
13
lib/PicoMQTT/src/PicoMQTT/pico_interface.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
class PicoMQTTInterface {
|
||||
public:
|
||||
virtual ~PicoMQTTInterface() {}
|
||||
virtual void begin() {}
|
||||
virtual void stop() {}
|
||||
virtual void loop() {}
|
||||
};
|
||||
|
||||
}
|
||||
29
lib/PicoMQTT/src/PicoMQTT/print_mux.cpp
Normal file
29
lib/PicoMQTT/src/PicoMQTT/print_mux.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "print_mux.h"
|
||||
#include "debug.h"
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
size_t PrintMux::write(uint8_t value) {
|
||||
TRACE_FUNCTION
|
||||
for (auto print_ptr : prints) {
|
||||
print_ptr->write(value);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t PrintMux::write(const uint8_t * buffer, size_t size) {
|
||||
TRACE_FUNCTION
|
||||
for (auto print_ptr : prints) {
|
||||
print_ptr->write(buffer, size);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
void PrintMux::flush() {
|
||||
TRACE_FUNCTION
|
||||
for (auto print_ptr : prints) {
|
||||
print_ptr->flush();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
29
lib/PicoMQTT/src/PicoMQTT/print_mux.h
Normal file
29
lib/PicoMQTT/src/PicoMQTT/print_mux.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
class PrintMux: public ::Print {
|
||||
public:
|
||||
PrintMux() {}
|
||||
|
||||
PrintMux(Print & print) : prints({&print}) {}
|
||||
|
||||
void add(Print & print) {
|
||||
prints.push_back(&print);
|
||||
}
|
||||
|
||||
virtual size_t write(uint8_t) override;
|
||||
virtual size_t write(const uint8_t * buffer, size_t size) override;
|
||||
virtual void flush();
|
||||
|
||||
size_t size() const { return prints.size(); }
|
||||
|
||||
protected:
|
||||
std::vector<Print *> prints;
|
||||
};
|
||||
|
||||
}
|
||||
56
lib/PicoMQTT/src/PicoMQTT/publisher.cpp
Normal file
56
lib/PicoMQTT/src/PicoMQTT/publisher.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "publisher.h"
|
||||
#include "debug.h"
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
Publisher::Publish::Publish(Publisher & publisher, const PrintMux & print,
|
||||
uint8_t flags, size_t total_size,
|
||||
const char * topic, size_t topic_size,
|
||||
uint16_t message_id)
|
||||
:
|
||||
OutgoingPacket(this->print, Packet::PUBLISH, flags, total_size),
|
||||
qos((flags >> 1) & 0b11),
|
||||
message_id(message_id),
|
||||
print(print),
|
||||
publisher(publisher) {
|
||||
TRACE_FUNCTION
|
||||
|
||||
OutgoingPacket::write_header();
|
||||
write_string(topic, topic_size);
|
||||
if (qos) {
|
||||
write_u16(message_id);
|
||||
}
|
||||
}
|
||||
|
||||
Publisher::Publish::Publish(Publisher & publisher, const PrintMux & print,
|
||||
const char * topic, size_t topic_size, size_t payload_size,
|
||||
uint8_t qos, bool retain, bool dup, uint16_t message_id)
|
||||
: Publish(
|
||||
publisher, print,
|
||||
(dup ? 0b1000 : 0) | ((qos & 0b11) << 1) | (retain ? 1 : 0), // flags
|
||||
2 + topic_size + (qos ? 2 : 0) + payload_size, // total size
|
||||
topic, topic_size, // topic
|
||||
message_id) {
|
||||
TRACE_FUNCTION
|
||||
}
|
||||
|
||||
Publisher::Publish::Publish(Publisher & publisher, const PrintMux & print,
|
||||
const char * topic, size_t payload_size,
|
||||
uint8_t qos, bool retain, bool dup, uint16_t message_id)
|
||||
: Publish(
|
||||
publisher, print,
|
||||
topic, strlen(topic), payload_size,
|
||||
qos, retain, dup, message_id) {
|
||||
TRACE_FUNCTION
|
||||
}
|
||||
|
||||
Publisher::Publish::~Publish() {
|
||||
TRACE_FUNCTION
|
||||
}
|
||||
|
||||
bool Publisher::Publish::send() {
|
||||
TRACE_FUNCTION
|
||||
return OutgoingPacket::send() && publisher.on_publish_complete(*this);
|
||||
}
|
||||
|
||||
}
|
||||
90
lib/PicoMQTT/src/PicoMQTT/publisher.h
Normal file
90
lib/PicoMQTT/src/PicoMQTT/publisher.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "debug.h"
|
||||
#include "outgoing_packet.h"
|
||||
#include "print_mux.h"
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
class Publisher {
|
||||
public:
|
||||
class Publish: public OutgoingPacket {
|
||||
private:
|
||||
Publish(Publisher & publisher, const PrintMux & print,
|
||||
uint8_t flags, size_t total_size,
|
||||
const char * topic, size_t topic_size,
|
||||
uint16_t message_id);
|
||||
|
||||
public:
|
||||
Publish(Publisher & publisher, const PrintMux & print,
|
||||
const char * topic, size_t topic_size, size_t payload_size,
|
||||
uint8_t qos = 0, bool retain = false, bool dup = false, uint16_t message_id = 0);
|
||||
|
||||
Publish(Publisher & publisher, const PrintMux & print,
|
||||
const char * topic, size_t payload_size,
|
||||
uint8_t qos = 0, bool retain = false, bool dup = false, uint16_t message_id = 0);
|
||||
|
||||
~Publish();
|
||||
|
||||
virtual bool send() override;
|
||||
|
||||
const uint8_t qos;
|
||||
const uint16_t message_id;
|
||||
PrintMux print;
|
||||
Publisher & publisher;
|
||||
};
|
||||
|
||||
virtual Publish begin_publish(const char * topic, const size_t payload_size,
|
||||
uint8_t qos = 0, bool retain = false, uint16_t message_id = 0) = 0;
|
||||
|
||||
Publish begin_publish(const String & topic, const size_t payload_size,
|
||||
uint8_t qos = 0, bool retain = false, uint16_t message_id = 0) {
|
||||
return begin_publish(topic.c_str(), payload_size, qos, retain, message_id);
|
||||
}
|
||||
|
||||
template <typename TopicStringType>
|
||||
bool publish(TopicStringType topic, const void * payload, const size_t payload_size,
|
||||
uint8_t qos = 0, bool retain = false, uint16_t message_id = 0) {
|
||||
TRACE_FUNCTION
|
||||
auto packet = begin_publish(get_c_str(topic), payload_size, qos, retain, message_id);
|
||||
packet.write((const uint8_t *) payload, payload_size);
|
||||
return packet.send();
|
||||
}
|
||||
|
||||
template <typename TopicStringType>
|
||||
bool publish_P(TopicStringType topic, PGM_P payload, const size_t payload_size,
|
||||
uint8_t qos = 0, bool retain = false, uint16_t message_id = 0) {
|
||||
TRACE_FUNCTION;
|
||||
auto packet = begin_publish(get_c_str(topic), payload_size, qos, retain, message_id);
|
||||
packet.write_P(payload, payload_size);
|
||||
return packet.send();
|
||||
}
|
||||
|
||||
template <typename TopicStringType, typename PayloadStringType>
|
||||
bool publish(TopicStringType topic, PayloadStringType payload,
|
||||
uint8_t qos = 0, bool retain = false, uint16_t message_id = 0) {
|
||||
return publish(topic, (const void *) get_c_str(payload), get_c_str_len(payload),
|
||||
qos, retain, message_id);
|
||||
}
|
||||
|
||||
template <typename TopicStringType>
|
||||
bool publish_P(TopicStringType topic, PGM_P payload,
|
||||
uint8_t qos = 0, bool retain = false, uint16_t message_id = 0) {
|
||||
return publish_P(topic, payload, strlen_P(payload),
|
||||
qos, retain, message_id);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool on_publish_complete(const Publish & publish) { return true; }
|
||||
|
||||
static const char * get_c_str(const char * string) { return string; }
|
||||
static const char * get_c_str(const String & string) { return string.c_str(); }
|
||||
static size_t get_c_str_len(const char * string) { return strlen(string); }
|
||||
static size_t get_c_str_len(const String & string) { return string.length(); }
|
||||
};
|
||||
|
||||
}
|
||||
365
lib/PicoMQTT/src/PicoMQTT/server.cpp
Normal file
365
lib/PicoMQTT/src/PicoMQTT/server.cpp
Normal file
@@ -0,0 +1,365 @@
|
||||
#include "config.h"
|
||||
#include "debug.h"
|
||||
#include "server.h"
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
BasicServer::Client::Client(const BasicServer::Client & other)
|
||||
: Connection(other.client, 0),
|
||||
server(other.server),
|
||||
client_id(other.client_id) {
|
||||
TRACE_FUNCTION
|
||||
}
|
||||
|
||||
BasicServer::Client::Client(BasicServer & server, const WiFiClient & client)
|
||||
: Connection(client, 0, server.socket_timeout_seconds), server(server), client_id("<unknown>") {
|
||||
TRACE_FUNCTION
|
||||
wait_for_reply(Packet::CONNECT, [this](IncomingPacket & packet) {
|
||||
TRACE_FUNCTION
|
||||
|
||||
auto connack = [this](ConnectReturnCode crc) {
|
||||
TRACE_FUNCTION
|
||||
auto connack = build_packet(Packet::CONNACK, 0, 2);
|
||||
connack.write_u8(0); /* session present always set to zero */
|
||||
connack.write_u8(crc);
|
||||
connack.send();
|
||||
if (crc != CRC_ACCEPTED) {
|
||||
this->client.stop();
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
// MQTT protocol identifier
|
||||
char buf[4];
|
||||
|
||||
if (packet.read_u16() != 4) {
|
||||
on_protocol_violation();
|
||||
return;
|
||||
}
|
||||
|
||||
packet.read((uint8_t *) buf, 4);
|
||||
|
||||
if (memcmp(buf, "MQTT", 4) != 0) {
|
||||
on_protocol_violation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t protocol_level = packet.read_u8();
|
||||
if (protocol_level != 4) {
|
||||
on_protocol_violation();
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t connect_flags = packet.read_u8();
|
||||
const bool has_user = connect_flags & (1 << 7);
|
||||
const bool has_pass = connect_flags & (1 << 6);
|
||||
const bool will_retain = connect_flags & (1 << 5);
|
||||
const uint8_t will_qos = (connect_flags >> 3) & 0b11;
|
||||
const bool has_will = connect_flags & (1 << 2);
|
||||
/* const bool clean_session = connect_flags & (1 << 1); */
|
||||
|
||||
if ((has_pass && !has_user)
|
||||
|| (will_qos > 2)
|
||||
|| (!has_will && ((will_qos > 0) || will_retain))) {
|
||||
on_protocol_violation();
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t keep_alive_seconds = packet.read_u16();
|
||||
keep_alive_millis = keep_alive_seconds ? (keep_alive_seconds + this->server.keep_alive_tolerance_seconds) * 1000 : 0;
|
||||
|
||||
{
|
||||
const size_t client_id_size = packet.read_u16();
|
||||
if (client_id_size > PICOMQTT_MAX_CLIENT_ID_SIZE) {
|
||||
connack(CRC_IDENTIFIER_REJECTED);
|
||||
return;
|
||||
}
|
||||
|
||||
char client_id_buffer[client_id_size + 1];
|
||||
packet.read_string(client_id_buffer, client_id_size);
|
||||
client_id = client_id_buffer;
|
||||
}
|
||||
|
||||
if (client_id.isEmpty()) {
|
||||
client_id = String((unsigned int)(this), HEX);
|
||||
}
|
||||
|
||||
if (has_will) {
|
||||
packet.ignore(packet.read_u16()); // will topic
|
||||
packet.ignore(packet.read_u16()); // will payload
|
||||
}
|
||||
|
||||
// read username
|
||||
const size_t user_size = has_user ? packet.read_u16() : 0;
|
||||
if (user_size > PICOMQTT_MAX_USERPASS_SIZE) {
|
||||
connack(CRC_BAD_USERNAME_OR_PASSWORD);
|
||||
return;
|
||||
}
|
||||
char user[user_size + 1];
|
||||
if (user_size && !packet.read_string(user, user_size)) {
|
||||
on_timeout();
|
||||
return;
|
||||
}
|
||||
|
||||
// read password
|
||||
const size_t pass_size = has_pass ? packet.read_u16() : 0;
|
||||
if (pass_size > PICOMQTT_MAX_USERPASS_SIZE) {
|
||||
connack(CRC_BAD_USERNAME_OR_PASSWORD);
|
||||
return;
|
||||
}
|
||||
char pass[pass_size + 1];
|
||||
if (pass_size && !packet.read_string(pass, pass_size)) {
|
||||
on_timeout();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto connect_return_code = this->server.auth(
|
||||
client_id.c_str(),
|
||||
has_user ? user : nullptr, has_pass ? pass : nullptr);
|
||||
|
||||
connack(connect_return_code);
|
||||
});
|
||||
}
|
||||
|
||||
void BasicServer::Client::on_message(const char * topic, IncomingPacket & packet) {
|
||||
TRACE_FUNCTION
|
||||
|
||||
const size_t payload_size = packet.get_remaining_size();
|
||||
auto publish = server.begin_publish(topic, payload_size);
|
||||
|
||||
// Always notify the server about the message
|
||||
{
|
||||
IncomingPublish incoming_publish(packet, publish);
|
||||
server.on_message(topic, incoming_publish);
|
||||
}
|
||||
|
||||
publish.send();
|
||||
}
|
||||
|
||||
void BasicServer::Client::on_subscribe(IncomingPacket & subscribe) {
|
||||
TRACE_FUNCTION
|
||||
const uint16_t message_id = subscribe.read_u16();
|
||||
|
||||
if ((subscribe.get_flags() != 0b0010) || !message_id) {
|
||||
on_protocol_violation();
|
||||
return;
|
||||
}
|
||||
|
||||
std::list<uint8_t> suback_codes;
|
||||
|
||||
while (subscribe.get_remaining_size()) {
|
||||
const size_t topic_size = subscribe.read_u16();
|
||||
if (topic_size > PICOMQTT_MAX_TOPIC_SIZE) {
|
||||
subscribe.ignore(topic_size);
|
||||
subscribe.read_u8();
|
||||
suback_codes.push_back(0x80);
|
||||
} else {
|
||||
char topic[topic_size + 1];
|
||||
if (!subscribe.read_string(topic, topic_size)) {
|
||||
// connection error
|
||||
return;
|
||||
}
|
||||
uint8_t qos = subscribe.read_u8();
|
||||
if (qos > 2) {
|
||||
on_protocol_violation();
|
||||
return;
|
||||
}
|
||||
this->subscribe(topic);
|
||||
server.on_subscribe(client_id.c_str(), topic);
|
||||
suback_codes.push_back(0);
|
||||
}
|
||||
}
|
||||
|
||||
auto suback = build_packet(Packet::SUBACK, 0, 2 + suback_codes.size());
|
||||
suback.write_u16(message_id);
|
||||
for (uint8_t code : suback_codes) {
|
||||
suback.write_u8(code);
|
||||
}
|
||||
suback.send();
|
||||
}
|
||||
|
||||
void BasicServer::Client::on_unsubscribe(IncomingPacket & unsubscribe) {
|
||||
TRACE_FUNCTION
|
||||
const uint16_t message_id = unsubscribe.read_u16();
|
||||
|
||||
if ((unsubscribe.get_flags() != 0b0010) || !message_id) {
|
||||
on_protocol_violation();
|
||||
return;
|
||||
}
|
||||
|
||||
while (unsubscribe.get_remaining_size()) {
|
||||
const size_t topic_size = unsubscribe.read_u16();
|
||||
if (topic_size > PICOMQTT_MAX_TOPIC_SIZE) {
|
||||
unsubscribe.ignore(topic_size);
|
||||
} else {
|
||||
char topic[topic_size + 1];
|
||||
if (!unsubscribe.read_string(topic, topic_size)) {
|
||||
// connection error
|
||||
return;
|
||||
}
|
||||
server.on_unsubscribe(client_id.c_str(), topic);
|
||||
this->unsubscribe(topic);
|
||||
}
|
||||
}
|
||||
|
||||
auto unsuback = build_packet(Packet::UNSUBACK, 0, 2);
|
||||
unsuback.write_u16(message_id);
|
||||
unsuback.send();
|
||||
}
|
||||
|
||||
const char * BasicServer::Client::get_subscription_pattern(BasicServer::Client::SubscriptionId id) const {
|
||||
for (const auto & pattern : subscriptions)
|
||||
if (pattern.id == id) {
|
||||
return pattern.c_str();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Server::SubscriptionId BasicServer::Client::get_subscription(const char * topic) const {
|
||||
TRACE_FUNCTION
|
||||
for (const auto & pattern : subscriptions)
|
||||
if (topic_matches(pattern.c_str(), topic)) {
|
||||
return pattern.id;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
BasicServer::Client::SubscriptionId BasicServer::Client::subscribe(const String & topic_filter) {
|
||||
TRACE_FUNCTION
|
||||
const Subscription subscription(topic_filter.c_str());
|
||||
subscriptions.insert(subscription);
|
||||
return subscription.id;
|
||||
}
|
||||
|
||||
void BasicServer::Client::unsubscribe(const String & topic_filter) {
|
||||
TRACE_FUNCTION
|
||||
subscriptions.erase(topic_filter.c_str());
|
||||
}
|
||||
|
||||
void BasicServer::Client::handle_packet(IncomingPacket & packet) {
|
||||
TRACE_FUNCTION
|
||||
|
||||
switch (packet.get_type()) {
|
||||
case Packet::PINGREQ:
|
||||
build_packet(Packet::PINGRESP).send();
|
||||
return;
|
||||
|
||||
case Packet::SUBSCRIBE:
|
||||
on_subscribe(packet);
|
||||
return;
|
||||
|
||||
case Packet::UNSUBSCRIBE:
|
||||
on_unsubscribe(packet);
|
||||
return;
|
||||
|
||||
default:
|
||||
Connection::handle_packet(packet);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void BasicServer::Client::loop() {
|
||||
TRACE_FUNCTION
|
||||
if (keep_alive_millis && (get_millis_since_last_read() > keep_alive_millis)) {
|
||||
// ping timeout
|
||||
on_timeout();
|
||||
return;
|
||||
}
|
||||
|
||||
Connection::loop();
|
||||
}
|
||||
|
||||
BasicServer::IncomingPublish::IncomingPublish(IncomingPacket & packet, Publish & publish)
|
||||
: IncomingPacket(std::move(packet)), publish(publish) {
|
||||
TRACE_FUNCTION
|
||||
}
|
||||
|
||||
BasicServer::IncomingPublish::~IncomingPublish() {
|
||||
TRACE_FUNCTION
|
||||
pos += publish.write_from_client(client, get_remaining_size());
|
||||
}
|
||||
|
||||
int BasicServer::IncomingPublish::read(uint8_t * buf, size_t size) {
|
||||
TRACE_FUNCTION
|
||||
const int ret = IncomingPacket::read(buf, size);
|
||||
if (ret > 0) {
|
||||
publish.write(buf, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int BasicServer::IncomingPublish::read() {
|
||||
TRACE_FUNCTION
|
||||
const int ret = IncomingPacket::read();
|
||||
if (ret >= 0) {
|
||||
publish.write(ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
BasicServer::BasicServer(uint16_t port, unsigned long keep_alive_tolerance_seconds,
|
||||
unsigned long socket_timeout_seconds)
|
||||
: server(port), keep_alive_tolerance_seconds(keep_alive_tolerance_seconds),
|
||||
socket_timeout_seconds(socket_timeout_seconds) {
|
||||
TRACE_FUNCTION
|
||||
}
|
||||
|
||||
void BasicServer::begin() {
|
||||
TRACE_FUNCTION
|
||||
server.begin();
|
||||
}
|
||||
|
||||
void BasicServer::stop() {
|
||||
TRACE_FUNCTION
|
||||
server.stop();
|
||||
clients.clear();
|
||||
}
|
||||
|
||||
void BasicServer::loop() {
|
||||
TRACE_FUNCTION
|
||||
|
||||
while (server.hasClient()) {
|
||||
auto client = Client(*this, server.accept());
|
||||
clients.push_back(client);
|
||||
on_connected(client.get_client_id());
|
||||
}
|
||||
|
||||
for (auto it = clients.begin(); it != clients.end();) {
|
||||
it->loop();
|
||||
|
||||
if (!it->connected()) {
|
||||
on_disconnected(it->get_client_id());
|
||||
clients.erase(it++);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PrintMux BasicServer::get_subscribed(const char * topic) {
|
||||
TRACE_FUNCTION
|
||||
PrintMux ret;
|
||||
for (auto & client : clients) {
|
||||
if (client.get_subscription(topic)) {
|
||||
ret.add(client.get_print());
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Publisher::Publish BasicServer::begin_publish(const char * topic, const size_t payload_size,
|
||||
uint8_t, bool, uint16_t) {
|
||||
TRACE_FUNCTION
|
||||
return Publish(*this, get_subscribed(topic), topic, payload_size);
|
||||
}
|
||||
|
||||
void BasicServer::on_message(const char * topic, IncomingPacket & packet) {
|
||||
}
|
||||
|
||||
void Server::on_message(const char * topic, IncomingPacket & packet) {
|
||||
TRACE_FUNCTION
|
||||
fire_message_callbacks(topic, packet);
|
||||
}
|
||||
|
||||
}
|
||||
105
lib/PicoMQTT/src/PicoMQTT/server.h
Normal file
105
lib/PicoMQTT/src/PicoMQTT/server.h
Normal file
@@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <set>
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <WiFi.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#else
|
||||
#error "This board is not supported."
|
||||
#endif
|
||||
|
||||
#include "debug.h"
|
||||
#include "incoming_packet.h"
|
||||
#include "connection.h"
|
||||
#include "publisher.h"
|
||||
#include "subscriber.h"
|
||||
#include "pico_interface.h"
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
class BasicServer: public PicoMQTTInterface, public Publisher {
|
||||
public:
|
||||
class Client: public Connection, public Subscriber {
|
||||
public:
|
||||
Client(BasicServer & server, const WiFiClient & client);
|
||||
Client(const Client &);
|
||||
|
||||
void on_message(const char * topic, IncomingPacket & packet) override;
|
||||
|
||||
Print & get_print() { return client; }
|
||||
const char * get_client_id() const { return client_id.c_str(); }
|
||||
|
||||
virtual void loop() override;
|
||||
|
||||
virtual const char * get_subscription_pattern(SubscriptionId id) const override;
|
||||
virtual SubscriptionId get_subscription(const char * topic) const override;
|
||||
virtual SubscriptionId subscribe(const String & topic_filter) override;
|
||||
virtual void unsubscribe(const String & topic_filter) override;
|
||||
|
||||
protected:
|
||||
BasicServer & server;
|
||||
String client_id;
|
||||
std::set<Subscription> subscriptions;
|
||||
|
||||
virtual void on_subscribe(IncomingPacket & packet);
|
||||
virtual void on_unsubscribe(IncomingPacket & packet);
|
||||
|
||||
virtual void handle_packet(IncomingPacket & packet) override;
|
||||
};
|
||||
|
||||
class IncomingPublish: public IncomingPacket {
|
||||
public:
|
||||
IncomingPublish(IncomingPacket & packet, Publish & publish);
|
||||
IncomingPublish(const IncomingPublish &) = delete;
|
||||
~IncomingPublish();
|
||||
|
||||
virtual int read(uint8_t * buf, size_t size) override;
|
||||
virtual int read() override;
|
||||
|
||||
protected:
|
||||
Publish & publish;
|
||||
};
|
||||
|
||||
BasicServer(uint16_t port = 1883, unsigned long keep_alive_tolerance_seconds = 10,
|
||||
unsigned long socket_timeout_seconds = 5);
|
||||
|
||||
void begin() override;
|
||||
void stop() override;
|
||||
void loop() override;
|
||||
|
||||
using Publisher::begin_publish;
|
||||
virtual Publish begin_publish(const char * topic, const size_t payload_size,
|
||||
uint8_t qos = 0, bool retain = false, uint16_t message_id = 0) override;
|
||||
|
||||
protected:
|
||||
virtual void on_message(const char * topic, IncomingPacket & packet);
|
||||
virtual ConnectReturnCode auth(const char * client_id, const char * username, const char * password) { return CRC_ACCEPTED; }
|
||||
|
||||
virtual void on_connected(const char * client_id) {}
|
||||
virtual void on_disconnected(const char * client_id) {}
|
||||
|
||||
virtual void on_subscribe(const char * client_id, const char * topic) {}
|
||||
virtual void on_unsubscribe(const char * client_id, const char * topic) {}
|
||||
|
||||
virtual PrintMux get_subscribed(const char * topic);
|
||||
|
||||
WiFiServer server;
|
||||
std::list<Client> clients;
|
||||
|
||||
const unsigned long keep_alive_tolerance_seconds;
|
||||
const unsigned long socket_timeout_seconds;
|
||||
|
||||
};
|
||||
|
||||
class Server: public BasicServer, public SubscribedMessageListener {
|
||||
public:
|
||||
using BasicServer::BasicServer;
|
||||
virtual void on_message(const char * topic, IncomingPacket & packet) override;
|
||||
};
|
||||
|
||||
}
|
||||
174
lib/PicoMQTT/src/PicoMQTT/subscriber.cpp
Normal file
174
lib/PicoMQTT/src/PicoMQTT/subscriber.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
#include "subscriber.h"
|
||||
#include "incoming_packet.h"
|
||||
#include "debug.h"
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
String Subscriber::get_topic_element(const char * topic, size_t index) {
|
||||
|
||||
while (index && topic[0]) {
|
||||
if (topic++[0] == '/') {
|
||||
--index;
|
||||
}
|
||||
}
|
||||
|
||||
if (!topic[0]) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const char * end = topic;
|
||||
while (*end && *end != '/') {
|
||||
++end;
|
||||
}
|
||||
|
||||
String ret;
|
||||
ret.concat(topic, end - topic);
|
||||
return ret;
|
||||
}
|
||||
|
||||
String Subscriber::get_topic_element(const String & topic, size_t index) {
|
||||
TRACE_FUNCTION
|
||||
return get_topic_element(topic.c_str(), index);
|
||||
}
|
||||
|
||||
bool Subscriber::topic_matches(const char * p, const char * t) {
|
||||
TRACE_FUNCTION
|
||||
// TODO: Special handling of the $ prefix
|
||||
while (true) {
|
||||
switch (*p) {
|
||||
case '\0':
|
||||
// end of pattern reached
|
||||
// TODO: check for '/#' suffix
|
||||
return (*t == '\0');
|
||||
|
||||
case '#':
|
||||
// multilevel wildcard
|
||||
if (*t == '\0') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
case '+':
|
||||
// single level wildcard
|
||||
while (*t && *t != '/') {
|
||||
++t;
|
||||
}
|
||||
++p;
|
||||
break;
|
||||
|
||||
default:
|
||||
// regular match
|
||||
if (*p != *t) {
|
||||
if (*t == '\0')
|
||||
{
|
||||
if (*p == '/')
|
||||
{
|
||||
++p;
|
||||
if (*p == '#')
|
||||
{
|
||||
++p;
|
||||
if (*p == '\0')
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
++p;
|
||||
++t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char * SubscribedMessageListener::get_subscription_pattern(SubscriptionId id) const {
|
||||
TRACE_FUNCTION
|
||||
for (const auto & kv : subscriptions) {
|
||||
if (kv.first.id == id) {
|
||||
return kv.first.c_str();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Subscriber::SubscriptionId SubscribedMessageListener::get_subscription(const char * topic) const {
|
||||
TRACE_FUNCTION
|
||||
for (const auto & kv : subscriptions) {
|
||||
if (topic_matches(kv.first.c_str(), topic)) {
|
||||
return kv.first.id;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Subscriber::SubscriptionId SubscribedMessageListener::subscribe(const String & topic_filter) {
|
||||
TRACE_FUNCTION
|
||||
return subscribe(topic_filter, [this](const char * topic, IncomingPacket & packet) { on_extra_message(topic, packet); });
|
||||
}
|
||||
|
||||
Subscriber::SubscriptionId SubscribedMessageListener::subscribe(const String & topic_filter, MessageCallback callback) {
|
||||
TRACE_FUNCTION
|
||||
unsubscribe(topic_filter);
|
||||
auto pair = subscriptions.emplace(std::make_pair(Subscription(topic_filter), callback));
|
||||
return pair.first->first.id;
|
||||
}
|
||||
|
||||
void SubscribedMessageListener::unsubscribe(const String & topic_filter) {
|
||||
TRACE_FUNCTION
|
||||
subscriptions.erase(topic_filter);
|
||||
}
|
||||
|
||||
void SubscribedMessageListener::fire_message_callbacks(const char * topic, IncomingPacket & packet) {
|
||||
TRACE_FUNCTION
|
||||
for (const auto & kv : subscriptions) {
|
||||
if (topic_matches(kv.first.c_str(), topic)) {
|
||||
kv.second((char *) topic, packet);
|
||||
return;
|
||||
}
|
||||
}
|
||||
on_extra_message(topic, packet);
|
||||
}
|
||||
|
||||
Subscriber::SubscriptionId SubscribedMessageListener::subscribe(const String & topic_filter,
|
||||
std::function<void(char *, void *, size_t)> callback, size_t max_size) {
|
||||
TRACE_FUNCTION
|
||||
return subscribe(topic_filter, [this, callback, max_size](char * topic, IncomingPacket & packet) {
|
||||
const size_t payload_size = packet.get_remaining_size();
|
||||
if (payload_size >= max_size) {
|
||||
on_message_too_big(topic, packet);
|
||||
return;
|
||||
}
|
||||
char payload[payload_size + 1];
|
||||
if (packet.read((uint8_t *) payload, payload_size) != (int) payload_size) {
|
||||
// connection error, ignore
|
||||
return;
|
||||
}
|
||||
payload[payload_size] = '\0';
|
||||
callback(topic, payload, payload_size);
|
||||
});
|
||||
}
|
||||
|
||||
Subscriber::SubscriptionId SubscribedMessageListener::subscribe(const String & topic_filter,
|
||||
std::function<void(char *, char *)> callback, size_t max_size) {
|
||||
TRACE_FUNCTION
|
||||
return subscribe(topic_filter, [callback](char * topic, void * payload, size_t payload_size) {
|
||||
callback(topic, (char *) payload);
|
||||
});
|
||||
}
|
||||
|
||||
Subscriber::SubscriptionId SubscribedMessageListener::subscribe(const String & topic_filter,
|
||||
std::function<void(char *)> callback, size_t max_size) {
|
||||
TRACE_FUNCTION
|
||||
return subscribe(topic_filter, [callback](char * topic, void * payload, size_t payload_size) {
|
||||
callback((char *) payload);
|
||||
});
|
||||
}
|
||||
|
||||
Subscriber::SubscriptionId SubscribedMessageListener::subscribe(const String & topic_filter,
|
||||
std::function<void(void *, size_t)> callback, size_t max_size) {
|
||||
TRACE_FUNCTION
|
||||
return subscribe(topic_filter, [callback](char * topic, void * payload, size_t payload_size) {
|
||||
callback(payload, payload_size);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
73
lib/PicoMQTT/src/PicoMQTT/subscriber.h
Normal file
73
lib/PicoMQTT/src/PicoMQTT/subscriber.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "autoid.h"
|
||||
#include "config.h"
|
||||
|
||||
namespace PicoMQTT {
|
||||
|
||||
class IncomingPacket;
|
||||
|
||||
class Subscriber {
|
||||
public:
|
||||
typedef AutoId::Id SubscriptionId;
|
||||
|
||||
static bool topic_matches(const char * topic_filter, const char * topic);
|
||||
static String get_topic_element(const char * topic, size_t index);
|
||||
static String get_topic_element(const String & topic, size_t index);
|
||||
|
||||
virtual const char * get_subscription_pattern(SubscriptionId id) const = 0;
|
||||
virtual SubscriptionId get_subscription(const char * topic) const = 0;
|
||||
|
||||
virtual SubscriptionId subscribe(const String & topic_filter) = 0;
|
||||
|
||||
virtual void unsubscribe(const String & topic_filter) = 0;
|
||||
void unsubscribe(SubscriptionId id) { unsubscribe(get_subscription_pattern(id)); }
|
||||
|
||||
protected:
|
||||
class Subscription: public String, public AutoId {
|
||||
public:
|
||||
using String::String;
|
||||
Subscription(const String & str): Subscription(str.c_str()) {}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
class SubscribedMessageListener: public Subscriber {
|
||||
public:
|
||||
// NOTE: None of the callback functions use const arguments for wider compatibility. It's still OK (and
|
||||
// recommended) to use callbacks which take const arguments. Similarly with Strings.
|
||||
typedef std::function<void(char * topic, IncomingPacket & packet)> MessageCallback;
|
||||
|
||||
virtual const char * get_subscription_pattern(SubscriptionId id) const override;
|
||||
virtual SubscriptionId get_subscription(const char * topic) const override;
|
||||
|
||||
virtual SubscriptionId subscribe(const String & topic_filter) override;
|
||||
virtual SubscriptionId subscribe(const String & topic_filter, MessageCallback callback);
|
||||
|
||||
SubscriptionId subscribe(const String & topic_filter, std::function<void(char *, void *, size_t)> callback,
|
||||
size_t max_size = PICOMQTT_MAX_MESSAGE_SIZE);
|
||||
|
||||
SubscriptionId subscribe(const String & topic_filter, std::function<void(char *, char *)> callback,
|
||||
size_t max_size = PICOMQTT_MAX_MESSAGE_SIZE);
|
||||
SubscriptionId subscribe(const String & topic_filter, std::function<void(void *, size_t)> callback,
|
||||
size_t max_size = PICOMQTT_MAX_MESSAGE_SIZE);
|
||||
SubscriptionId subscribe(const String & topic_filter, std::function<void(char *)> callback,
|
||||
size_t max_size = PICOMQTT_MAX_MESSAGE_SIZE);
|
||||
|
||||
virtual void unsubscribe(const String & topic_filter) override;
|
||||
|
||||
virtual void on_extra_message(const char * topic, IncomingPacket & packet) {}
|
||||
virtual void on_message_too_big(const char * topic, IncomingPacket & packet) {}
|
||||
|
||||
protected:
|
||||
void fire_message_callbacks(const char * topic, IncomingPacket & packet);
|
||||
|
||||
std::map<Subscription, MessageCallback> subscriptions;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -128,6 +128,10 @@
|
||||
},
|
||||
"modules": {
|
||||
"virtual_elments": [
|
||||
{
|
||||
"path": "src/modules/virtual/Benchmark",
|
||||
"active": false
|
||||
},
|
||||
{
|
||||
"path": "src/modules/virtual/Cron",
|
||||
"active": true
|
||||
@@ -152,6 +156,10 @@
|
||||
"path": "src/modules/virtual/owmWeather",
|
||||
"active": true
|
||||
},
|
||||
{
|
||||
"path": "src/modules/virtual/Ping",
|
||||
"active": true
|
||||
},
|
||||
{
|
||||
"path": "src/modules/virtual/Timer",
|
||||
"active": true
|
||||
@@ -234,6 +242,10 @@
|
||||
"path": "src/modules/sensors/Emon",
|
||||
"active": false
|
||||
},
|
||||
{
|
||||
"path": "src/modules/sensors/EnergyMon485",
|
||||
"active": true
|
||||
},
|
||||
{
|
||||
"path": "src/modules/sensors/ExampleModule",
|
||||
"active": false
|
||||
@@ -352,6 +364,10 @@
|
||||
"path": "src/modules/exec/AnalogBtn",
|
||||
"active": true
|
||||
},
|
||||
{
|
||||
"path": "src/modules/exec/BrokerMQTT",
|
||||
"active": false
|
||||
},
|
||||
{
|
||||
"path": "src/modules/exec/ButtonIn",
|
||||
"active": true
|
||||
@@ -420,6 +436,14 @@
|
||||
"path": "src/modules/exec/SDcard",
|
||||
"active": false
|
||||
},
|
||||
{
|
||||
"path": "src/modules/exec/SIM800",
|
||||
"active": false
|
||||
},
|
||||
{
|
||||
"path": "src/modules/exec/SmartBoiler",
|
||||
"active": false
|
||||
},
|
||||
{
|
||||
"path": "src/modules/exec/SysExt",
|
||||
"active": false
|
||||
|
||||
@@ -180,6 +180,7 @@ build_src_filter =
|
||||
${env:esp8266_16mb_fromitems.build_src_filter}
|
||||
|
||||
[env:esp32_4mb]
|
||||
extra_scripts = pre:tools/patch32_ws.py
|
||||
lib_deps =
|
||||
${common_env_data.lib_deps_external}
|
||||
${env:esp32_4mb_fromitems.lib_deps}
|
||||
@@ -200,6 +201,7 @@ build_src_filter =
|
||||
${env:esp32_4mb_fromitems.build_src_filter}
|
||||
|
||||
[env:esp32_4mb3f]
|
||||
extra_scripts = pre:tools/patch32_ws.py
|
||||
lib_deps =
|
||||
${common_env_data.lib_deps_external}
|
||||
${env:esp32_4mb3f_fromitems.lib_deps}
|
||||
@@ -221,6 +223,7 @@ build_src_filter =
|
||||
${env:esp32_4mb3f_fromitems.build_src_filter}
|
||||
|
||||
[env:esp32cam_4mb]
|
||||
extra_scripts = pre:tools/patch32_ws.py
|
||||
lib_deps =
|
||||
${common_env_data.lib_deps_external}
|
||||
${env:esp32cam_4mb_fromitems.lib_deps}
|
||||
@@ -244,6 +247,7 @@ build_src_filter =
|
||||
${env:esp32cam_4mb_fromitems.build_src_filter}
|
||||
|
||||
[env:esp32s2_4mb]
|
||||
extra_scripts = pre:tools/patch32_ws.py
|
||||
lib_deps =
|
||||
${common_env_data.lib_deps_external}
|
||||
${env:esp32s2_4mb_fromitems.lib_deps}
|
||||
@@ -267,6 +271,7 @@ build_src_filter =
|
||||
${env:esp32s2_4mb_fromitems.build_src_filter}
|
||||
|
||||
[env:esp32c3m_4mb]
|
||||
extra_scripts = pre:tools/patch32_ws.py
|
||||
lib_deps =
|
||||
${common_env_data.lib_deps_external}
|
||||
${env:esp32c3m_4mb_fromitems.lib_deps}
|
||||
@@ -289,6 +294,7 @@ build_src_filter =
|
||||
${env:esp32c3m_4mb_fromitems.build_src_filter}
|
||||
|
||||
[env:esp32s3_16mb]
|
||||
extra_scripts = pre:tools/patch32_ws.py
|
||||
lib_deps =
|
||||
${common_env_data.lib_deps_external}
|
||||
${env:esp32s3_16mb_fromitems.lib_deps}
|
||||
@@ -313,6 +319,7 @@ build_src_filter =
|
||||
${env:esp32s3_16mb_fromitems.build_src_filter}
|
||||
|
||||
[env:esp32_16mb]
|
||||
extra_scripts = pre:tools/patch32_ws.py
|
||||
lib_deps =
|
||||
${common_env_data.lib_deps_external}
|
||||
${env:esp32_16mb_fromitems.lib_deps}
|
||||
|
||||
@@ -33,6 +33,9 @@ void configure(String path) {
|
||||
if (driver = myIoTItem->getRtcDriver()) rtcItem = (IoTItem*)driver;
|
||||
// пробуем спросить драйвер CAM
|
||||
//if (driver = myIoTItem->getCAMDriver()) camItem = (IoTItem*)driver;
|
||||
// пробуем спросить драйвер Benchmark
|
||||
if (driver = myIoTItem->getBenchmarkTask()) benchTaskItem = ((IoTBench*)driver);
|
||||
if (driver = myIoTItem->getBenchmarkLoad()) benchLoadItem = ((IoTBench*)driver);
|
||||
// пробуем спросить драйвер Telegram_v2
|
||||
if (driver = myIoTItem->getTlgrmDriver()) tlgrmItem = (IoTItem*)driver;
|
||||
IoTItems.push_back(myIoTItem);
|
||||
@@ -48,7 +51,7 @@ void clearConfigure() {
|
||||
Serial.printf("Start clearing config\n");
|
||||
rtcItem = nullptr;
|
||||
//camItem = nullptr;
|
||||
tlgrmItem = nullptr;
|
||||
tlgrmItem = nullptr;
|
||||
IoTgpio.clearDrivers();
|
||||
|
||||
for (std::list<IoTItem*>::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) {
|
||||
@@ -58,4 +61,6 @@ void clearConfigure() {
|
||||
IoTItems.clear();
|
||||
|
||||
valuesFlashJson.clear();
|
||||
benchTaskItem = nullptr;
|
||||
benchLoadItem = nullptr;
|
||||
}
|
||||
@@ -33,6 +33,8 @@ IoTGpio IoTgpio(0);
|
||||
IoTItem* rtcItem = nullptr;
|
||||
//IoTItem* camItem = nullptr;
|
||||
IoTItem* tlgrmItem = nullptr;
|
||||
IoTBench* benchTaskItem = nullptr;
|
||||
IoTBench* benchLoadItem = nullptr;
|
||||
String settingsFlashJson = "{}"; // переменная в которой хранятся все настройки, находится в оперативной памяти и синхронизированна с flash памятью
|
||||
String valuesFlashJson = "{}"; // переменная в которой хранятся все значения элементов, которые необходимо сохранить на flash. Находится в оперативной памяти и синхронизированна с flash памятью
|
||||
String errorsHeapJson = "{}"; // переменная в которой хранятся все ошибки, находится в оперативной памяти только
|
||||
|
||||
25
src/Main.cpp
25
src/Main.cpp
@@ -2,6 +2,7 @@
|
||||
#include <time.h>
|
||||
#include "classes/IoTDB.h"
|
||||
#include "utils/Statistic.h"
|
||||
#include "classes/IoTBench.h"
|
||||
#include <Wire.h>
|
||||
#if defined(esp32s2_4mb) || defined(esp32s3_16mb)
|
||||
#include <USB.h>
|
||||
@@ -16,8 +17,9 @@ String volStrForSave = "";
|
||||
void elementsLoop() {
|
||||
// передаем управление каждому элементу конфигурации для выполнения своих функций
|
||||
for (std::list<IoTItem *>::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) {
|
||||
if (benchTaskItem) benchTaskItem->preTaskFunction((*it)->getID());
|
||||
(*it)->loop();
|
||||
|
||||
if (benchTaskItem) benchTaskItem->postTaskFunction((*it)->getID());
|
||||
// if ((*it)->iAmDead) {
|
||||
if (!((*it)->iAmLocal) && (*it)->getIntFromNet() == -1) {
|
||||
delete *it;
|
||||
@@ -150,13 +152,13 @@ void setup() {
|
||||
iotScen.loadScenario("/scenario.txt");
|
||||
// создаем событие завершения инициализации основных моментов для возможности выполнения блока кода при загрузке
|
||||
createItemFromNet("onInit", "1", 1);
|
||||
elementsLoop();
|
||||
// elementsLoop(); //Для работы MQTT Брокера перенес ниже, иначе брокер падает если вызван до routerConnect();
|
||||
|
||||
stopErrorMarker(SETUPSCEN_ERRORMARKER);
|
||||
|
||||
initErrorMarker(SETUPINET_ERRORMARKER);
|
||||
|
||||
// подключаемся к роутеру
|
||||
// подключаемся к роутеру
|
||||
routerConnect();
|
||||
|
||||
// инициализация асинхронного веб сервера и веб сокетов
|
||||
@@ -179,6 +181,7 @@ void setup() {
|
||||
|
||||
initErrorMarker(SETUPLAST_ERRORMARKER);
|
||||
|
||||
elementsLoop();
|
||||
// NTP
|
||||
ntpInit();
|
||||
|
||||
@@ -224,31 +227,35 @@ void loop() {
|
||||
#ifdef LOOP_DEBUG
|
||||
unsigned long st = millis();
|
||||
#endif
|
||||
|
||||
if (benchLoadItem) benchLoadItem->preLoadFunction();
|
||||
if (benchTaskItem) benchTaskItem->preTaskFunction("TickerScheduler");
|
||||
initErrorMarker(TICKER_ERRORMARKER);
|
||||
ts.update();
|
||||
stopErrorMarker(TICKER_ERRORMARKER);
|
||||
|
||||
if (benchTaskItem) benchTaskItem->postTaskFunction("TickerScheduler");
|
||||
if (benchTaskItem) benchTaskItem->preTaskFunction("webServer");
|
||||
#ifdef STANDARD_WEB_SERVER
|
||||
initErrorMarker(HTTP_ERRORMARKER);
|
||||
HTTP.handleClient();
|
||||
stopErrorMarker(HTTP_ERRORMARKER);
|
||||
#endif
|
||||
|
||||
if (benchTaskItem) benchTaskItem->postTaskFunction("webServer");
|
||||
if (benchTaskItem) benchTaskItem->preTaskFunction("webSocket");
|
||||
#ifdef STANDARD_WEB_SOCKETS
|
||||
initErrorMarker(SOCKETS_ERRORMARKER);
|
||||
standWebSocket.loop();
|
||||
stopErrorMarker(SOCKETS_ERRORMARKER);
|
||||
#endif
|
||||
|
||||
if (benchTaskItem) benchTaskItem->postTaskFunction("webSocket");
|
||||
if (benchTaskItem) benchTaskItem->preTaskFunction("mqtt");
|
||||
initErrorMarker(MQTT_ERRORMARKER);
|
||||
mqttLoop();
|
||||
stopErrorMarker(MQTT_ERRORMARKER);
|
||||
|
||||
if (benchTaskItem) benchTaskItem->postTaskFunction("mqtt");
|
||||
initErrorMarker(MODULES_ERRORMARKER);
|
||||
elementsLoop();
|
||||
stopErrorMarker(MODULES_ERRORMARKER);
|
||||
|
||||
if (benchLoadItem) benchLoadItem->postLoadFunction();
|
||||
// #ifdef LOOP_DEBUG
|
||||
// loopPeriod = millis() - st;
|
||||
// if (loopPeriod > 2) Serial.println(loopPeriod);
|
||||
|
||||
@@ -167,6 +167,15 @@ unsigned long strDateToUnix(String date) {
|
||||
int numberOfLeepYears = 12;
|
||||
int totalNormalYears = year - 1970 - numberOfLeepYears;
|
||||
unsigned int daysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
if (year % 4 == 0) {
|
||||
if (year % 100 != 0 || year % 400 == 0) {
|
||||
daysInMonth[1] = 29;
|
||||
} else {
|
||||
daysInMonth[1] = 28;
|
||||
}
|
||||
} else {
|
||||
daysInMonth[1] = 28;
|
||||
}
|
||||
int numberOfDaysInPastMonths = 0;
|
||||
for (int i = 0; i <= 11; i++) {
|
||||
if (i <= month - 2) {
|
||||
|
||||
21
src/classes/IoTBench.cpp
Normal file
21
src/classes/IoTBench.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "Global.h"
|
||||
#include "classes/IoTBench.h"
|
||||
|
||||
IoTBench::IoTBench(const String ¶meters) : IoTItem(parameters)
|
||||
{
|
||||
/* int _tx, _rx, _speed, _line;
|
||||
jsonRead(parameters, "rx", _rx);
|
||||
jsonRead(parameters, "tx", _tx);
|
||||
jsonRead(parameters, "speed", _speed);
|
||||
jsonRead(parameters, "line", _line);
|
||||
*/
|
||||
}
|
||||
|
||||
void IoTBench::preLoadFunction() {}
|
||||
void IoTBench::postLoadFunction() {}
|
||||
void IoTBench::preTaskFunction(const String &id) {}
|
||||
void IoTBench::postTaskFunction(const String &id) {}
|
||||
|
||||
// void IoTBench::uartHandle() {}
|
||||
|
||||
IoTBench::~IoTBench() {}
|
||||
@@ -250,7 +250,16 @@ IoTItem* IoTItem::getTlgrmDriver() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
unsigned long IoTItem::getRtcUnixTime() {
|
||||
IoTBench *IoTItem::getBenchmarkTask()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
IoTBench *IoTItem::getBenchmarkLoad()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
unsigned long IoTItem::getRtcUnixTime()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ IoTUart::IoTUart(const String& parameters) : IoTItem(parameters) {
|
||||
|
||||
void IoTUart::loop() {
|
||||
uartHandle();
|
||||
IoTItem::loop();
|
||||
}
|
||||
|
||||
void IoTUart::uartHandle() {}
|
||||
@@ -93,7 +94,7 @@ IoTValue IoTUart::execute(String command, std::vector<IoTValue> ¶m) {
|
||||
if (param.size() == 1) {
|
||||
//if (param[0].isDecimal) uartPrint((String)param[0].valD);
|
||||
//else uartPrint(param[0].valS);
|
||||
uartPrintln(param[0].valS);
|
||||
uartPrint(param[0].valS);
|
||||
}
|
||||
} else if (command == "printHex") {
|
||||
if (param.size() == 1) {
|
||||
|
||||
225
src/modules/exec/BrokerMQTT/BrokerMQTT.cpp
Normal file
225
src/modules/exec/BrokerMQTT/BrokerMQTT.cpp
Normal file
@@ -0,0 +1,225 @@
|
||||
#include "Global.h"
|
||||
#include "classes/IoTItem.h"
|
||||
#include <Arduino.h>
|
||||
#include <PicoMQTT.h>
|
||||
|
||||
namespace _Broker
|
||||
{
|
||||
#define DEF_PORT 1883
|
||||
|
||||
// MqttBroker broker(1883);
|
||||
|
||||
class myPicoMQTT : public PicoMQTT::Server
|
||||
{
|
||||
private:
|
||||
bool _debug;
|
||||
String _user;
|
||||
String _pass;
|
||||
|
||||
public:
|
||||
myPicoMQTT(int port) : PicoMQTT::Server(port)
|
||||
{
|
||||
}
|
||||
|
||||
void setAuth(String user, String pass)
|
||||
{
|
||||
_user = user;
|
||||
_pass = pass;
|
||||
}
|
||||
|
||||
void setDebug(bool debug)
|
||||
{
|
||||
_debug = debug;
|
||||
}
|
||||
|
||||
protected:
|
||||
void on_connected(const char *client_id)
|
||||
{
|
||||
if (_debug)
|
||||
{
|
||||
Serial.print("[BrokerMQTT], Client connected: ");
|
||||
Serial.println(client_id);
|
||||
}
|
||||
}
|
||||
void on_disconnected(const char *client_id)
|
||||
{
|
||||
if (_debug)
|
||||
|
||||
{
|
||||
// SerialPrint("i", "BrokerMQTT", "Client disconnected: " + client_id);
|
||||
Serial.print("[BrokerMQTT], Client disconnected: ");
|
||||
Serial.println(client_id);
|
||||
}
|
||||
}
|
||||
void on_subscribe(const char *client_id, const char *topic)
|
||||
{
|
||||
if (_debug)
|
||||
|
||||
{
|
||||
// SerialPrint("i", "BrokerMQTT", "Client " + client_id + ", subscribe: " + topic);
|
||||
Serial.print("[BrokerMQTT], Client: ");
|
||||
Serial.print(client_id);
|
||||
Serial.print(", subscribe: ");
|
||||
Serial.println(topic);
|
||||
}
|
||||
}
|
||||
void on_unsubscribe(const char *client_id, const char *topic)
|
||||
{
|
||||
if (_debug)
|
||||
|
||||
{
|
||||
// SerialPrint("i", "BrokerMQTT", "Client " + client_id + ", unsubscribe: " + topic);
|
||||
Serial.print("[BrokerMQTT], Client: ");
|
||||
Serial.print(client_id);
|
||||
Serial.print(", unsubscribe: ");
|
||||
Serial.println(topic);
|
||||
}
|
||||
}
|
||||
PicoMQTT::ConnectReturnCode auth(const char *client_id, const char *username, const char *password)
|
||||
{
|
||||
if (String(client_id).length() < 3)
|
||||
{
|
||||
return PicoMQTT::CRC_IDENTIFIER_REJECTED;
|
||||
}
|
||||
if (!username && !password)
|
||||
{
|
||||
return PicoMQTT::CRC_NOT_AUTHORIZED;
|
||||
}
|
||||
if (String(username) == _user && String(password) == _pass)
|
||||
{
|
||||
return PicoMQTT::CRC_ACCEPTED;
|
||||
}
|
||||
Serial.print("[BrokerMQTT], Client: ");
|
||||
Serial.print(client_id);
|
||||
Serial.print(", NOT Authorized: ");
|
||||
Serial.print(username);
|
||||
Serial.print(" != ");
|
||||
Serial.print(_user);
|
||||
Serial.print(" ,pass: ");
|
||||
Serial.print(password);
|
||||
Serial.print(" != ");
|
||||
Serial.println(_pass);
|
||||
return PicoMQTT::CRC_BAD_USERNAME_OR_PASSWORD;
|
||||
}
|
||||
};
|
||||
|
||||
myPicoMQTT *picoMqtt = nullptr;
|
||||
PicoMQTT::Client *clientMqtt = nullptr;
|
||||
|
||||
TaskHandle_t brokerTask;
|
||||
// void Task2code( void * pvParameters );
|
||||
|
||||
void tBrokerMQTT(void *pvParameters)
|
||||
{
|
||||
TickType_t xLastWakeTime = xTaskGetTickCount();
|
||||
Serial.print("Task PicoMQTT running on core ");
|
||||
Serial.println(xPortGetCoreID());
|
||||
for (;;)
|
||||
{
|
||||
if (clientMqtt)
|
||||
clientMqtt->loop();
|
||||
if (picoMqtt)
|
||||
picoMqtt->loop();
|
||||
// picoMqtt.loop();
|
||||
// vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(5));
|
||||
}
|
||||
}
|
||||
|
||||
class BrokerMQTT : public IoTItem
|
||||
{
|
||||
private:
|
||||
unsigned long ts = 0;
|
||||
int _port = 0;
|
||||
String _user;
|
||||
String _pass;
|
||||
bool _debug;
|
||||
bool _brige;
|
||||
String _server;
|
||||
String _srvUser;
|
||||
String _srvPass;
|
||||
int _srvPort;
|
||||
|
||||
public:
|
||||
BrokerMQTT(String parameters) : IoTItem(parameters)
|
||||
{
|
||||
SerialPrint("i", F("BrokerMQTT"), " START... ");
|
||||
jsonRead(parameters, "port", _port);
|
||||
jsonRead(parameters, "user", _user);
|
||||
jsonRead(parameters, "pass", _pass);
|
||||
jsonRead(parameters, "debug", _debug);
|
||||
jsonRead(parameters, "brige", _brige);
|
||||
jsonRead(parameters, "server", _server);
|
||||
jsonRead(parameters, "srvUser", _srvUser);
|
||||
jsonRead(parameters, "srvPass", _srvPass);
|
||||
jsonRead(parameters, "srvPort", _srvPort);
|
||||
|
||||
if (_brige)
|
||||
{
|
||||
clientMqtt = new PicoMQTT::Client(_server.c_str(), _srvPort, nullptr, _srvUser.c_str(), _srvPass.c_str());
|
||||
if (_debug)
|
||||
{
|
||||
SerialPrint("i", F("BrigeMQTT"), "Bridge mode : ON");
|
||||
SerialPrint("i", F("BrigeMQTT"), "Bridge server: " + _server);
|
||||
SerialPrint("i", F("BrigeMQTT"), "Bridge port: " + String(_srvPort));
|
||||
SerialPrint("i", F("BrigeMQTT"), "Bridge user: " + _srvUser);
|
||||
SerialPrint("i", F("BrigeMQTT"), "Bridge pass: " + _srvPass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void doByInterval()
|
||||
{
|
||||
static bool flagOne = false;
|
||||
if (!flagOne)
|
||||
{
|
||||
if (!_port)
|
||||
_port = DEF_PORT;
|
||||
picoMqtt = new myPicoMQTT(_port);
|
||||
picoMqtt->begin();
|
||||
picoMqtt->setDebug(_debug);
|
||||
picoMqtt->setAuth(_user, _pass);
|
||||
if (_brige && picoMqtt && clientMqtt)
|
||||
{
|
||||
picoMqtt->subscribe("#", [](const char *topic, const char *message)
|
||||
{ clientMqtt->publish(topic, message);
|
||||
SerialPrint("i", F("BrigeMQTT"), "client publish, topic: " + String(topic) + " msg: " + String(message) ); });
|
||||
}
|
||||
// picoMqtt.begin();
|
||||
xTaskCreatePinnedToCore(
|
||||
tBrokerMQTT, // Функция задачи.
|
||||
"BrokerMQTT", // Имя задачи.
|
||||
10000, // Размер стека
|
||||
NULL, // Параметры задачи
|
||||
0, // Приоритет
|
||||
&brokerTask, // Дескриптор задачи для отслеживания
|
||||
0);
|
||||
flagOne = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Основной цикл программы
|
||||
void loop()
|
||||
{
|
||||
IoTItem::loop();
|
||||
}
|
||||
|
||||
~BrokerMQTT()
|
||||
{
|
||||
vTaskDelete(brokerTask);
|
||||
delete picoMqtt;
|
||||
delete clientMqtt;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void *getAPI_BrokerMQTT(String subtype, String param)
|
||||
{
|
||||
if (subtype == F("BrokerMQTT"))
|
||||
{
|
||||
return new _Broker::BrokerMQTT(param);
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
52
src/modules/exec/BrokerMQTT/modinfo.json
Normal file
52
src/modules/exec/BrokerMQTT/modinfo.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"menuSection": "executive_devices",
|
||||
"configItem": [
|
||||
{
|
||||
"global": 0,
|
||||
"name": "BrokerMQTT",
|
||||
"type": "Reading",
|
||||
"subtype": "BrokerMQTT",
|
||||
"id": "broker",
|
||||
"widget": "",
|
||||
"page": "",
|
||||
"descr": "",
|
||||
"int": 10,
|
||||
"value": "",
|
||||
"port": 1883,
|
||||
"user": "root",
|
||||
"pass": "4321",
|
||||
"brige":1,
|
||||
"server":"http://iotmanager.org",
|
||||
"srvUser": "rise",
|
||||
"srvPass": "3hostel3",
|
||||
"srvPort": 1883,
|
||||
"debug": 1
|
||||
}
|
||||
],
|
||||
"about": {
|
||||
"authorName": "Mikhail Bubnov",
|
||||
"authorContact": "https://t.me/Mit4bmw",
|
||||
"authorGit": "https://github.com/Mit4el",
|
||||
"specialThanks": "Андрей Душин",
|
||||
"moduleName": "BrokerMQTT",
|
||||
"moduleVersion": "2.0",
|
||||
"usedRam": {
|
||||
"esp32_4mb": 15,
|
||||
"esp8266_4mb": 15
|
||||
},
|
||||
"title": "BrokerMQTT",
|
||||
"moduleDesc": "MQTT Брокер на основе Pico Mqtt",
|
||||
"propInfo": {
|
||||
"port":"Порт, по умолчанию 1883",
|
||||
"brige":"1 - Использовать режим моста, Брокер будет дублировать все топики в указанные сервер",
|
||||
"server":"Адрес внешнего MQTT брокера/сервера для режима моста",
|
||||
"srvUser": "Пользователь внешнего MQTT брокера",
|
||||
"srvPass": "Пароль внешнего MQTT брокера"
|
||||
}
|
||||
},
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32_4mb3f": [],
|
||||
"esp32*": []
|
||||
}
|
||||
}
|
||||
191
src/modules/exec/SIM800/SIM800.cpp
Normal file
191
src/modules/exec/SIM800/SIM800.cpp
Normal file
@@ -0,0 +1,191 @@
|
||||
|
||||
#include "Global.h"
|
||||
#include "classes/IoTUart.h"
|
||||
|
||||
class Sim800 : public IoTUart
|
||||
{
|
||||
private:
|
||||
bool _debug;
|
||||
String _number;
|
||||
char _inc;
|
||||
String _inStr = ""; // буфер приема строк
|
||||
|
||||
public:
|
||||
Sim800(String parameters) : IoTUart(parameters)
|
||||
{
|
||||
_number = jsonReadStr(parameters, "number");
|
||||
jsonRead(parameters, "debug", _debug);
|
||||
}
|
||||
|
||||
void _printUart(bool ln, String str)
|
||||
{
|
||||
if (!_myUART)
|
||||
return;
|
||||
if (!ln)
|
||||
{
|
||||
_myUART->print(str);
|
||||
if (_debug)
|
||||
SerialPrint("I", F("SIM800"), "<- print(" + str + ")");
|
||||
}
|
||||
else
|
||||
{
|
||||
_myUART->println(str);
|
||||
if (_debug)
|
||||
SerialPrint("I", F("SIM800"), "<- println(" + str + ")");
|
||||
}
|
||||
}
|
||||
|
||||
void sendSms(String sms, String num)
|
||||
{
|
||||
_printUart(1, "AT+CMGF=1"); // переводим в текстовый режим
|
||||
delay(2);
|
||||
_printUart(1, "AT+CMGS=\"" + num + "\"");
|
||||
delay(2);
|
||||
//_printUart(1, sms + "\r\n" + String((char)26));
|
||||
_myUART->println(sms + "\r\n" + String((char)26));
|
||||
if (_debug)
|
||||
SerialPrint("I", F("SIM800"), "<- println(" + sms + ")");
|
||||
// _myUART->print((char)26); // код ctrl+c что является командой передачи сообщения
|
||||
}
|
||||
|
||||
void doByInterval()
|
||||
{
|
||||
if (_myUART)
|
||||
{
|
||||
// _myUART->println("AT"); // должен ответить OK
|
||||
// _myUART->println("AT+CSQ"); // уровень сигнала в dB
|
||||
// _myUART->println("AT+CCID"); // если есть сим карта, то вернет её номер
|
||||
//_printUart(1, "AT+COPS?"); //+COPS: 0,0,"MTS-RUS" - оператор
|
||||
_printUart(1, "AT+CPAS"); //+CPAS: 0 - готов к работе
|
||||
//_printUart(1, "AT+CREG?"); // проверка регистрации в сети, второй паратетр: 1-регистрация в сети, 5-роуминг
|
||||
// _myUART->println("ATI"); // имя модуля и его номер
|
||||
}
|
||||
}
|
||||
|
||||
void uartHandle()
|
||||
{
|
||||
if (!_myUART)
|
||||
return;
|
||||
|
||||
if (_myUART->available())
|
||||
{
|
||||
_inc = _myUART->read();
|
||||
|
||||
if (_inc == '\r')
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_inc == '\n')
|
||||
{
|
||||
_inStr += "";//_inc;
|
||||
if (_debug && _inStr != "")
|
||||
SerialPrint("I", F("SIM800"), "-> " + _inStr);
|
||||
|
||||
if (_inStr.indexOf("CPAS") != -1)
|
||||
{
|
||||
if (_inStr.indexOf("+CPAS: 0") != -1)
|
||||
setValue("OK");
|
||||
else
|
||||
setValue("NO");
|
||||
}
|
||||
_inStr = "";
|
||||
return;
|
||||
}
|
||||
else
|
||||
_inStr += _inc;
|
||||
}
|
||||
}
|
||||
|
||||
IoTValue execute(String command, std::vector<IoTValue> ¶m)
|
||||
{
|
||||
if (!_myUART)
|
||||
return {};
|
||||
if (command == "sms")
|
||||
{
|
||||
if (param.size() == 1)
|
||||
{
|
||||
sendSms(param[0].valS, _number);
|
||||
}
|
||||
else if (param.size() == 2)
|
||||
{
|
||||
sendSms(param[0].valS, param[1].valS);
|
||||
}
|
||||
}
|
||||
// отправка кирилических символов на Nextion (русские буквы)
|
||||
else if (command == "print")
|
||||
{
|
||||
if (param.size() == 2)
|
||||
{
|
||||
String strToUart = "";
|
||||
strToUart = param[0].valS;
|
||||
|
||||
if (param[1].valD)
|
||||
_printUart(0, "\"" + strToUart + "\"");
|
||||
else
|
||||
_printUart(0, strToUart);
|
||||
}
|
||||
if (param.size() == 3)
|
||||
{
|
||||
String strToUart = "";
|
||||
strToUart = param[0].valS;
|
||||
|
||||
if (param[2].valD)
|
||||
_printUart(0, strToUart + "\"" + param[1].valS + "\"");
|
||||
else
|
||||
_printUart(0, strToUart + param[1].valS);
|
||||
}
|
||||
}
|
||||
else if (command == "println")
|
||||
{
|
||||
if (param.size() == 2)
|
||||
{
|
||||
String strToUart = "";
|
||||
strToUart = param[0].valS;
|
||||
|
||||
if (param[1].valD)
|
||||
_printUart(1, "\"" + strToUart + "\"");
|
||||
else
|
||||
_printUart(1, strToUart);
|
||||
}
|
||||
if (param.size() == 3)
|
||||
{
|
||||
String strToUart = "";
|
||||
strToUart = param[0].valS;
|
||||
|
||||
if (param[2].valD)
|
||||
_printUart(1, strToUart + "\"" + param[1].valS + "\"");
|
||||
else
|
||||
_printUart(1, strToUart + param[1].valS);
|
||||
}
|
||||
}
|
||||
else if (command == "printHex")
|
||||
{
|
||||
unsigned char Hi, Lo;
|
||||
uint8_t byteTx;
|
||||
const char *strPtr = param[0].valS.c_str();
|
||||
while ((Hi = *strPtr++) && (Lo = *strPtr++))
|
||||
{
|
||||
byteTx = (ChartoHex(Hi) << 4) | ChartoHex(Lo);
|
||||
_myUART->write(byteTx);
|
||||
}
|
||||
if (_debug)
|
||||
SerialPrint("I", F("SIM800"), "<- printHex(" + String(byteTx, HEX) + ")");
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
~Sim800(){};
|
||||
};
|
||||
|
||||
void *getAPI_SIM800(String subtype, String param)
|
||||
{
|
||||
if (subtype == F("sim800"))
|
||||
{
|
||||
return new Sim800(param);
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
86
src/modules/exec/SIM800/modinfo.json
Normal file
86
src/modules/exec/SIM800/modinfo.json
Normal file
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"menuSection": "executive_devices",
|
||||
"configItem": [
|
||||
{
|
||||
"global": 0,
|
||||
"name": "SIM800",
|
||||
"type": "Reading",
|
||||
"subtype": "sim800",
|
||||
"id": "sim",
|
||||
"widget": "anydataDef",
|
||||
"page": "Состояние",
|
||||
"descr": "Sim в сети",
|
||||
"int": 5,
|
||||
"tx": 17,
|
||||
"rx": 16,
|
||||
"line": 2,
|
||||
"speed": 115200,
|
||||
"number": "+71234567890",
|
||||
"debug": 0
|
||||
}
|
||||
],
|
||||
"about": {
|
||||
"authorName": "Bubnov Mikhail",
|
||||
"authorContact": "https://t.me/Mit4bmw",
|
||||
"authorGit": "https://github.com/Mit4el",
|
||||
"specialThanks": "",
|
||||
"moduleName": "SIM800",
|
||||
"moduleVersion": "1.0",
|
||||
"usedRam": {
|
||||
"esp32_4mb": 15,
|
||||
"esp8266_4mb": 15
|
||||
},
|
||||
"title": "SIM800",
|
||||
"moduleDesc": "Оправка AT-команд в модуль SIM800L. Отправка sms сообщений через модуль",
|
||||
"propInfo": {
|
||||
"int": "Период опроса состояния модуля SIM800",
|
||||
"tx": "TX пин",
|
||||
"rx": "RX пин",
|
||||
"speed": "Скорость UART",
|
||||
"line": "Актуально только для ESP32: номер линии hardUART. =2 rx=16 tx=17, для SoftwarwSerial в ESP32 line = -1",
|
||||
"number": "Номер телефона для отправки sms сообщения"
|
||||
},
|
||||
"funcInfo": [
|
||||
{
|
||||
"name": "sms",
|
||||
"descr": "Отправить sms сообщение. Например sim.sms(\"сообщение\") или sim.sms(\"сообщение\", \"+7999999\")",
|
||||
"params": [
|
||||
"Строка текста",
|
||||
"Номер телефона, если не указывать будет отправлено на номер number из конфигурации"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "print",
|
||||
"descr": "Отправить в UART строку текста (AT-команду). Например sim.print(\"AT\",0)",
|
||||
"params": [
|
||||
"Строка текста",
|
||||
"ID Виджета или любое значение, не обязательно",
|
||||
"1 - обернуть строку в кавычки, 0 - отправить без кавычек (При наличии второго параметра оборачивает только его)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "println",
|
||||
"descr": "Отправить в UART строку текста (AT-команду) и признак завершения строки (перевод строки). Например sim.println(\"AT+CMGS=\", \"+799999\", 1);",
|
||||
"params": [
|
||||
"Строка текста",
|
||||
"ID Виджета или любое значение, не обязательно",
|
||||
"1 - обернуть строку в кавычки, 0 - отправить без кавычек (Пр наличии второго параметра оборачивает только его)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "printHex",
|
||||
"descr": "Отправить в UART HEX-строку.",
|
||||
"params": [
|
||||
"HEX-строка."
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32_4mb": [],
|
||||
"esp32_4mb3f": [],
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
}
|
||||
}
|
||||
82
src/modules/exec/SmartBoiler/BoilerHeader.h
Normal file
82
src/modules/exec/SmartBoiler/BoilerHeader.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#define SLAVE true
|
||||
#define TIMEOUT_TRESHOLD 5
|
||||
|
||||
namespace _Boiler_v2
|
||||
{
|
||||
// команды/установки от термостата
|
||||
struct SetpointBoiler
|
||||
{
|
||||
bool cmd_chEnable = 0;
|
||||
bool cmd_dhwEnable = 0;
|
||||
float TSetCH = 0;
|
||||
float TSetDhw = 0;
|
||||
} set;
|
||||
|
||||
struct failCode
|
||||
{
|
||||
bool service_required = 0;
|
||||
bool lockout_reset = 0;
|
||||
bool low_water_pressure = 0;
|
||||
bool gas_fault = 0;
|
||||
bool air_fault = 0;
|
||||
bool water_overtemp = 0;
|
||||
int fault_code = 0;
|
||||
};
|
||||
|
||||
// текущее реальное состояние котла
|
||||
struct StateBoiler
|
||||
{
|
||||
bool antiFreezOn = false;
|
||||
bool stateCH = 0;
|
||||
bool stateDHW = 0;
|
||||
bool fl_flame = 0;
|
||||
int currentRele = 0;
|
||||
bool fl_fail = 0;
|
||||
failCode fCode;
|
||||
float RelModLevel = 0;
|
||||
float Tboiler = -40;
|
||||
float Tret = 0;
|
||||
float Tdhw = 0;
|
||||
float Toutside = 0;
|
||||
// bool r[3] = {0, 0, 0};
|
||||
int numStepOn;
|
||||
} state;
|
||||
|
||||
// конфигурация котла
|
||||
struct ConfigBoiler
|
||||
{
|
||||
bool autoPower = true; // если false то управление только из сценария или веба
|
||||
int antiFreez;
|
||||
// bool pump = false; // 1- наличие реле насоса СО, 0 - мы не управляем насосом СО (в протоколе ОТ нет)
|
||||
bool changeRele = false;
|
||||
bool dhw = false; // 1- есть поддержка ГВС, по наличию реле(трехходовой)
|
||||
bool ctrlType = false; // 0 - модуляция, 1- вкл/выкл
|
||||
bool confDhw = false; // 1 - бак, 0 - проточная //TODO ПОКА НЕ ЗНАЮ ЧТО ДЕЛАТЬ
|
||||
bool pumpControlMaster = false; // в протоколе ОТ: мастер управляет насосом ????????????????????? //TODO Команды кправления насосом от мастера не помню
|
||||
|
||||
int minDhw;
|
||||
int maxDhw;
|
||||
int minCH;
|
||||
int maxCH;
|
||||
|
||||
int gistDhw;
|
||||
int gistCH;
|
||||
|
||||
int countRele = 0;
|
||||
// int relePwr[3]={0,0,0};
|
||||
int prcOnekWt = 0; // процент одного киловата из общей мощности всех тэнев, расчитывается для модуляции
|
||||
// int rele2Pwr = 0;
|
||||
// int rele3Pwr = 0;
|
||||
int numStepDhw;
|
||||
float maxKW;
|
||||
} conf;
|
||||
|
||||
unsigned long timeout_count = 0;
|
||||
|
||||
bool _debug = 0;
|
||||
bool _telegram = false;
|
||||
unsigned long ot_response = 0;
|
||||
int SlaveMemberIDcode = 0;
|
||||
}
|
||||
812
src/modules/exec/SmartBoiler/OpenTherm.cpp
Normal file
812
src/modules/exec/SmartBoiler/OpenTherm.cpp
Normal file
@@ -0,0 +1,812 @@
|
||||
/*
|
||||
OpenTherm.cpp - OpenTherm Communication Library For Arduino, ESP8266
|
||||
Copyright 2018, Ihor Melnyk
|
||||
*/
|
||||
|
||||
#include "OpenTherm.h"
|
||||
|
||||
OpenTherm::OpenTherm(int inPin, int outPin, bool isSlave) : status(OpenThermStatus::NOT_INITIALIZED),
|
||||
inPin(inPin),
|
||||
outPin(outPin),
|
||||
isSlave(isSlave),
|
||||
response(0),
|
||||
responseStatus(OpenThermResponseStatus::NONE),
|
||||
responseTimestamp(0),
|
||||
handleInterruptCallback(NULL),
|
||||
processResponseCallback(NULL)
|
||||
{
|
||||
imitFlag = false;
|
||||
}
|
||||
|
||||
void OpenTherm::begin(void (*handleInterruptCallback)(void), void (*processResponseCallback)(unsigned long, OpenThermResponseStatus))
|
||||
{
|
||||
pinMode(inPin, INPUT);
|
||||
pinMode(outPin, OUTPUT);
|
||||
if (handleInterruptCallback != NULL)
|
||||
{
|
||||
this->handleInterruptCallback = handleInterruptCallback;
|
||||
attachInterrupt(digitalPinToInterrupt(inPin), handleInterruptCallback, CHANGE);
|
||||
}
|
||||
activateBoiler();
|
||||
status = OpenThermStatus::READY;
|
||||
this->processResponseCallback = processResponseCallback;
|
||||
}
|
||||
|
||||
void OpenTherm::begin(void (*handleInterruptCallback)(void))
|
||||
{
|
||||
begin(handleInterruptCallback, NULL);
|
||||
}
|
||||
|
||||
bool IRAM_ATTR OpenTherm::isReady()
|
||||
{
|
||||
return status == OpenThermStatus::READY;
|
||||
}
|
||||
|
||||
int IRAM_ATTR OpenTherm::readState()
|
||||
{
|
||||
return digitalRead(inPin);
|
||||
}
|
||||
|
||||
void OpenTherm::setActiveState()
|
||||
{
|
||||
digitalWrite(outPin, LOW);
|
||||
}
|
||||
|
||||
void OpenTherm::setIdleState()
|
||||
{
|
||||
digitalWrite(outPin, HIGH);
|
||||
}
|
||||
|
||||
void OpenTherm::activateBoiler()
|
||||
{
|
||||
setIdleState();
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
void OpenTherm::sendBit(bool high)
|
||||
{
|
||||
if (high)
|
||||
setActiveState();
|
||||
else
|
||||
setIdleState();
|
||||
delayMicroseconds(500);
|
||||
if (high)
|
||||
setIdleState();
|
||||
else
|
||||
setActiveState();
|
||||
delayMicroseconds(500);
|
||||
}
|
||||
|
||||
bool OpenTherm::sendRequestAync(unsigned long request)
|
||||
{
|
||||
// Serial.println("Request: " + String(request, HEX));
|
||||
noInterrupts();
|
||||
const bool ready = isReady();
|
||||
interrupts();
|
||||
|
||||
if (!ready)
|
||||
return false;
|
||||
|
||||
status = OpenThermStatus::REQUEST_SENDING;
|
||||
response = 0;
|
||||
responseStatus = OpenThermResponseStatus::NONE;
|
||||
// Prevent switching to other tasks as there is a delay within sendBit
|
||||
#ifdef ESP32
|
||||
// vTaskSuspendAll();
|
||||
#endif
|
||||
sendBit(HIGH); // start bit
|
||||
for (int i = 31; i >= 0; i--)
|
||||
{
|
||||
sendBit(bitRead(request, i));
|
||||
}
|
||||
sendBit(HIGH); // stop bit
|
||||
setIdleState();
|
||||
#ifdef ESP32
|
||||
// xTaskResumeAll();
|
||||
#endif
|
||||
status = OpenThermStatus::RESPONSE_WAITING;
|
||||
responseTimestamp = micros();
|
||||
if (imitFlag)
|
||||
ImitationResponse(request);
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned long OpenTherm::sendRequest(unsigned long request)
|
||||
{
|
||||
if (!sendRequestAync(request))
|
||||
return 0;
|
||||
while (!isReady())
|
||||
{
|
||||
process();
|
||||
yield();
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
bool OpenTherm::sendResponse(unsigned long request)
|
||||
{
|
||||
status = OpenThermStatus::REQUEST_SENDING;
|
||||
response = 0;
|
||||
responseStatus = OpenThermResponseStatus::NONE;
|
||||
// Prevent switching to other tasks as there is a delay within sendBit
|
||||
#ifdef ESP32
|
||||
// vTaskSuspendAll();
|
||||
#endif
|
||||
sendBit(HIGH); // start bit
|
||||
for (int i = 31; i >= 0; i--)
|
||||
{
|
||||
sendBit(bitRead(request, i));
|
||||
}
|
||||
sendBit(HIGH); // stop bit
|
||||
setIdleState();
|
||||
#ifdef ESP32
|
||||
// xTaskResumeAll();
|
||||
#endif
|
||||
status = OpenThermStatus::READY;
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned long OpenTherm::getLastResponse()
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
OpenThermResponseStatus OpenTherm::getLastResponseStatus()
|
||||
{
|
||||
return responseStatus;
|
||||
}
|
||||
|
||||
void IRAM_ATTR OpenTherm::handleInterrupt()
|
||||
{
|
||||
if (isReady())
|
||||
{
|
||||
if (isSlave && readState() == HIGH)
|
||||
{
|
||||
status = OpenThermStatus::RESPONSE_WAITING;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long newTs = micros();
|
||||
if (status == OpenThermStatus::RESPONSE_WAITING)
|
||||
{
|
||||
if (readState() == HIGH)
|
||||
{
|
||||
status = OpenThermStatus::RESPONSE_START_BIT;
|
||||
responseTimestamp = newTs;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Error start bit / Ошибка стартового бита
|
||||
status = OpenThermStatus::RESPONSE_INVALID;
|
||||
responseTimestamp = newTs;
|
||||
}
|
||||
}
|
||||
else if (status == OpenThermStatus::RESPONSE_START_BIT)
|
||||
{
|
||||
if ((newTs - responseTimestamp < 750) && readState() == LOW)
|
||||
{
|
||||
status = OpenThermStatus::RESPONSE_RECEIVING;
|
||||
responseTimestamp = newTs;
|
||||
responseBitIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Error Start_bit LOW 750mks / Ошибка стартового бита по тылу (нет LOW через 750мкс)
|
||||
status = OpenThermStatus::RESPONSE_INVALID;
|
||||
responseTimestamp = newTs;
|
||||
}
|
||||
}
|
||||
else if (status == OpenThermStatus::RESPONSE_RECEIVING)
|
||||
{
|
||||
// unsigned long bitDuration = newTs - responseTimestamp;
|
||||
// В новой спецификации стоповый бит не обязателен. Если не дождались, всё равно попробуем разобрать
|
||||
if ((newTs - responseTimestamp) > 750 && (newTs - responseTimestamp) < 1300)
|
||||
{
|
||||
if (responseBitIndex < 32)
|
||||
{
|
||||
response = (response << 1) | !readState();
|
||||
responseTimestamp = newTs;
|
||||
responseBitIndex++;
|
||||
}
|
||||
else
|
||||
{ // stop bit
|
||||
status = OpenThermStatus::RESPONSE_READY;
|
||||
responseTimestamp = newTs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OpenTherm::process()
|
||||
{
|
||||
noInterrupts();
|
||||
OpenThermStatus st = status;
|
||||
unsigned long ts = responseTimestamp;
|
||||
interrupts();
|
||||
|
||||
if (st == OpenThermStatus::READY)
|
||||
return;
|
||||
unsigned long newTs = micros();
|
||||
if (st != OpenThermStatus::NOT_INITIALIZED && st != OpenThermStatus::DELAY && (newTs - ts) > 1000000)
|
||||
{
|
||||
status = OpenThermStatus::READY;
|
||||
responseStatus = OpenThermResponseStatus::TIMEOUT;
|
||||
if (processResponseCallback != NULL)
|
||||
{
|
||||
processResponseCallback(response, responseStatus);
|
||||
}
|
||||
}
|
||||
else if (st == OpenThermStatus::RESPONSE_INVALID)
|
||||
{
|
||||
status = OpenThermStatus::DELAY;
|
||||
responseStatus = OpenThermResponseStatus::INVALID;
|
||||
if (processResponseCallback != NULL)
|
||||
{
|
||||
processResponseCallback(response, responseStatus);
|
||||
}
|
||||
}
|
||||
else if (st == OpenThermStatus::RESPONSE_READY)
|
||||
{
|
||||
status = OpenThermStatus::DELAY;
|
||||
responseStatus = (isSlave ? isValidRequest(response) : isValidResponse(response)) ? OpenThermResponseStatus::SUCCESS : OpenThermResponseStatus::INVALID;
|
||||
// Error msgType (READ_ACK | WRITE_ACK) is Header
|
||||
if (processResponseCallback != NULL)
|
||||
{
|
||||
processResponseCallback(response, responseStatus);
|
||||
}
|
||||
}
|
||||
else if (st == OpenThermStatus::DELAY)
|
||||
{
|
||||
if ((newTs - ts) > 100000)
|
||||
{
|
||||
status = OpenThermStatus::READY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenTherm::parity(unsigned long frame) // odd parity
|
||||
{
|
||||
byte p = 0;
|
||||
while (frame > 0)
|
||||
{
|
||||
if (frame & 1)
|
||||
p++;
|
||||
frame = frame >> 1;
|
||||
}
|
||||
return (p & 1);
|
||||
}
|
||||
|
||||
OpenThermMessageType OpenTherm::getMessageType(unsigned long message)
|
||||
{
|
||||
OpenThermMessageType msg_type = static_cast<OpenThermMessageType>((message >> 28) & 7);
|
||||
return msg_type;
|
||||
}
|
||||
|
||||
OpenThermMessageID OpenTherm::getDataID(unsigned long frame)
|
||||
{
|
||||
return (OpenThermMessageID)((frame >> 16) & 0xFF);
|
||||
}
|
||||
|
||||
unsigned long OpenTherm::buildRequest(OpenThermMessageType type, OpenThermMessageID id, unsigned int data)
|
||||
{
|
||||
unsigned long request = data;
|
||||
if (type == OpenThermMessageType::WRITE_DATA)
|
||||
{
|
||||
request |= 1ul << 28;
|
||||
}
|
||||
request |= ((unsigned long)id) << 16;
|
||||
if (parity(request))
|
||||
request |= (1ul << 31);
|
||||
return request;
|
||||
}
|
||||
unsigned long OpenTherm::buildRequestID(OpenThermMessageType type, unsigned int id, unsigned int data)
|
||||
{
|
||||
unsigned long request = data;
|
||||
if (type == OpenThermMessageType::WRITE_DATA)
|
||||
{
|
||||
request |= 1ul << 28;
|
||||
}
|
||||
request |= ((unsigned long)id) << 16;
|
||||
if (parity(request))
|
||||
request |= (1ul << 31);
|
||||
return request;
|
||||
}
|
||||
|
||||
unsigned long OpenTherm::buildResponse(OpenThermMessageType type, OpenThermMessageID id, unsigned int data)
|
||||
{
|
||||
unsigned long response = data;
|
||||
response |= ((unsigned long)type) << 28;
|
||||
response |= ((unsigned long)id) << 16;
|
||||
if (parity(response))
|
||||
response |= (1ul << 31);
|
||||
return response;
|
||||
}
|
||||
|
||||
bool OpenTherm::isValidResponse(unsigned long response)
|
||||
{
|
||||
if (parity(response))
|
||||
return false;
|
||||
byte msgType = (response << 1) >> 29;
|
||||
return msgType == READ_ACK || msgType == WRITE_ACK;
|
||||
}
|
||||
|
||||
bool OpenTherm::isValidRequest(unsigned long request)
|
||||
{
|
||||
if (parity(request))
|
||||
return false;
|
||||
byte msgType = (request << 1) >> 29;
|
||||
return msgType == READ_DATA || msgType == WRITE_DATA;
|
||||
}
|
||||
|
||||
void OpenTherm::end()
|
||||
{
|
||||
if (this->handleInterruptCallback != NULL)
|
||||
{
|
||||
detachInterrupt(digitalPinToInterrupt(inPin));
|
||||
}
|
||||
}
|
||||
|
||||
const char *OpenTherm::statusToString(OpenThermResponseStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case NONE:
|
||||
return "NONE";
|
||||
case SUCCESS:
|
||||
return "SUCCESS";
|
||||
case INVALID:
|
||||
return "INVALID";
|
||||
case TIMEOUT:
|
||||
return "TIMEOUT";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
const char *OpenTherm::messageTypeToString(OpenThermMessageType message_type)
|
||||
{
|
||||
switch (message_type)
|
||||
{
|
||||
case READ_DATA:
|
||||
return "READ_DATA";
|
||||
case WRITE_DATA:
|
||||
return "WRITE_DATA";
|
||||
case INVALID_DATA:
|
||||
return "INVALID_DATA";
|
||||
case RESERVED:
|
||||
return "RESERVED";
|
||||
case READ_ACK:
|
||||
return "READ_ACK";
|
||||
case WRITE_ACK:
|
||||
return "WRITE_ACK";
|
||||
case DATA_INVALID:
|
||||
return "DATA_INVALID";
|
||||
case UNKNOWN_DATA_ID:
|
||||
return "UNKNOWN_DATA_ID";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
// building requests
|
||||
|
||||
unsigned long OpenTherm::buildSetBoilerStatusRequest(bool enableCentralHeating, bool enableHotWater, bool enableCooling, bool enableOutsideTemperatureCompensation, bool enableCentralHeating2, bool enableSummerMode, bool dhwBlock)
|
||||
{
|
||||
unsigned int data = enableCentralHeating | (enableHotWater << 1) | (enableCooling << 2) | (enableOutsideTemperatureCompensation << 3) | (enableCentralHeating2 << 4) | (enableSummerMode << 5) | (dhwBlock << 6);
|
||||
data <<= 8;
|
||||
return buildRequest(OpenThermMessageType::READ_DATA, OpenThermMessageID::Status, data);
|
||||
}
|
||||
|
||||
unsigned long OpenTherm::buildSetBoilerTemperatureRequest(float temperature)
|
||||
{
|
||||
unsigned int data = temperatureToData(temperature);
|
||||
return buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::TSet, data);
|
||||
}
|
||||
|
||||
unsigned long OpenTherm::buildGetBoilerTemperatureRequest()
|
||||
{
|
||||
return buildRequest(OpenThermMessageType::READ_DATA, OpenThermMessageID::Tboiler, 0);
|
||||
}
|
||||
|
||||
// parsing responses
|
||||
bool OpenTherm::isFault(unsigned long response)
|
||||
{
|
||||
return response & 0x1;
|
||||
}
|
||||
|
||||
bool OpenTherm::isCentralHeatingActive(unsigned long response)
|
||||
{
|
||||
return response & 0x2;
|
||||
}
|
||||
|
||||
bool OpenTherm::isHotWaterActive(unsigned long response)
|
||||
{
|
||||
return response & 0x4;
|
||||
}
|
||||
|
||||
bool OpenTherm::isFlameOn(unsigned long response)
|
||||
{
|
||||
return response & 0x8;
|
||||
}
|
||||
|
||||
bool OpenTherm::isCoolingActive(unsigned long response)
|
||||
{
|
||||
return response & 0x10;
|
||||
}
|
||||
|
||||
bool OpenTherm::isDiagnostic(unsigned long response)
|
||||
{
|
||||
return response & 0x40;
|
||||
}
|
||||
|
||||
uint16_t OpenTherm::getUInt(const unsigned long response) const
|
||||
{
|
||||
const uint16_t u88 = response & 0xffff;
|
||||
return u88;
|
||||
}
|
||||
|
||||
float OpenTherm::getFloat(const unsigned long response) const
|
||||
{
|
||||
const uint16_t u88 = getUInt(response);
|
||||
const float f = (u88 & 0x8000) ? -(0x10000L - u88) / 256.0f : u88 / 256.0f;
|
||||
return f;
|
||||
}
|
||||
|
||||
unsigned int OpenTherm::temperatureToData(float temperature)
|
||||
{
|
||||
if (temperature < 0)
|
||||
temperature = 0;
|
||||
if (temperature > 100)
|
||||
temperature = 100;
|
||||
unsigned int data = (unsigned int)(temperature * 256);
|
||||
return data;
|
||||
}
|
||||
|
||||
// basic requests
|
||||
|
||||
unsigned long OpenTherm::setBoilerStatus(bool enableCentralHeating, bool enableHotWater, bool enableCooling, bool enableOutsideTemperatureCompensation, bool enableCentralHeating2, bool enableSummerMode, bool dhwBlock)
|
||||
{
|
||||
return sendRequest(buildSetBoilerStatusRequest(enableCentralHeating, enableHotWater, enableCooling, enableOutsideTemperatureCompensation, enableCentralHeating2, enableSummerMode, dhwBlock));
|
||||
}
|
||||
|
||||
bool OpenTherm::setBoilerTemperature(float temperature)
|
||||
{
|
||||
unsigned long response = sendRequest(buildSetBoilerTemperatureRequest(temperature));
|
||||
return isValidResponse(response);
|
||||
}
|
||||
|
||||
float OpenTherm::getBoilerTemperature()
|
||||
{
|
||||
unsigned long response = sendRequest(buildGetBoilerTemperatureRequest());
|
||||
return isValidResponse(response) ? getFloat(response) : 0;
|
||||
}
|
||||
|
||||
float OpenTherm::getReturnTemperature()
|
||||
{
|
||||
unsigned long response = sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Tret, 0));
|
||||
return isValidResponse(response) ? getFloat(response) : 0;
|
||||
}
|
||||
|
||||
bool OpenTherm::setDHWSetpoint(float temperature)
|
||||
{
|
||||
unsigned int data = temperatureToData(temperature);
|
||||
unsigned long response = sendRequest(buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::TdhwSet, data));
|
||||
return isValidResponse(response);
|
||||
}
|
||||
|
||||
float OpenTherm::getDHWTemperature()
|
||||
{
|
||||
unsigned long response = sendRequest(buildRequest(OpenThermMessageType::READ_DATA, OpenThermMessageID::Tdhw, 0));
|
||||
return isValidResponse(response) ? getFloat(response) : 0;
|
||||
}
|
||||
|
||||
float OpenTherm::getModulation()
|
||||
{
|
||||
unsigned long response = sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::RelModLevel, 0));
|
||||
return isValidResponse(response) ? getFloat(response) : 0;
|
||||
}
|
||||
|
||||
float OpenTherm::getPressure()
|
||||
{
|
||||
unsigned long response = sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::CHPressure, 0));
|
||||
return isValidResponse(response) ? getFloat(response) : 0;
|
||||
}
|
||||
|
||||
unsigned char OpenTherm::getFault()
|
||||
{
|
||||
return ((sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::ASFflags, 0)) >> 8) & 0xff);
|
||||
}
|
||||
int8_t flame_timer = 0;
|
||||
void OpenTherm::ImitationResponse(unsigned long request)
|
||||
{
|
||||
|
||||
// unsigned long response;
|
||||
unsigned int data = getUInt(request);
|
||||
OpenThermMessageType msgType;
|
||||
byte ID;
|
||||
OpenThermMessageID id = getDataID(request);
|
||||
uint8_t flags;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case OpenThermMessageID::Status:
|
||||
// Статус котла получен
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
static int8_t flame = 0;
|
||||
flame_timer++;
|
||||
if (flame_timer > 10)
|
||||
flame = 1;
|
||||
if (flame_timer > 20)
|
||||
{
|
||||
flame_timer = 0;
|
||||
flame = 0;
|
||||
}
|
||||
static int8_t fault = 0;
|
||||
// fault = 1 - fault;
|
||||
data = (bool)fault | (true << 1) | (true << 2) | ((bool)flame << 3) | (false << 4);
|
||||
break;
|
||||
case OpenThermMessageID::SConfigSMemberIDcode:
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::SlaveVersion:
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::MasterVersion:
|
||||
msgType = OpenThermMessageType::WRITE_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::RelModLevel:
|
||||
static float RelModLevel = 10;
|
||||
// RelModLevel = RelModLevel > 100 ? 10 : RelModLevel + 1;
|
||||
if (flame_timer < 11)
|
||||
{
|
||||
RelModLevel = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
RelModLevel = RelModLevel == 0 ? 10 : RelModLevel + 1;
|
||||
}
|
||||
// data = RelModLevel;
|
||||
data = temperatureToData(RelModLevel);
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::Tboiler:
|
||||
// Получили температуру котла
|
||||
static float Tboiler = 40;
|
||||
Tboiler = Tboiler > 60 ? 40 : Tboiler + 1;
|
||||
data = temperatureToData(Tboiler);
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::Tdhw:
|
||||
// Получили температуру ГВС
|
||||
static float Tdhw = 60;
|
||||
Tdhw = Tdhw > 80 ? 60 : Tdhw + 1;
|
||||
data = temperatureToData(Tdhw);
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::Toutside:
|
||||
// Получили внешнюю температуру
|
||||
static float Toutside = -10;
|
||||
Toutside = Toutside > 10 ? -10 : Toutside + 1;
|
||||
data = temperatureToData(Toutside);
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::ASFflags:
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
|
||||
case OpenThermMessageID::TdhwSetUBTdhwSetLB:
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::MaxTSetUBMaxTSetLB:
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
|
||||
case OpenThermMessageID::OEMDiagnosticCode:
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
|
||||
case OpenThermMessageID::OpenThermVersionSlave:
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
|
||||
case OpenThermMessageID::CHPressure:
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
|
||||
break;
|
||||
case OpenThermMessageID::DHWFlowRate:
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::DayTime:
|
||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
else
|
||||
msgType = OpenThermMessageType::WRITE_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::Date:
|
||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
else
|
||||
msgType = OpenThermMessageType::WRITE_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::Year:
|
||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
else
|
||||
msgType = OpenThermMessageType::WRITE_ACK;
|
||||
break;
|
||||
|
||||
case OpenThermMessageID::Tret:
|
||||
//
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::Tstorage:
|
||||
//
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::Tcollector:
|
||||
//
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::TflowCH2:
|
||||
//
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
|
||||
break;
|
||||
case OpenThermMessageID::Tdhw2:
|
||||
//
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
|
||||
break;
|
||||
case OpenThermMessageID::Texhaust:
|
||||
//
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::TheatExchanger:
|
||||
//
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::BoilerFanSpeed:
|
||||
//
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::ElectricBurnerFlame:
|
||||
//
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::BurnerStarts:
|
||||
//
|
||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
else
|
||||
msgType = OpenThermMessageType::WRITE_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::CHPumpStarts:
|
||||
//
|
||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
else
|
||||
msgType = OpenThermMessageType::WRITE_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::DHWPumpValveStarts:
|
||||
//
|
||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
else
|
||||
msgType = OpenThermMessageType::WRITE_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::DHWBurnerStarts:
|
||||
//
|
||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
else
|
||||
msgType = OpenThermMessageType::WRITE_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::BurnerOperationHours:
|
||||
//
|
||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
else
|
||||
msgType = OpenThermMessageType::WRITE_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::CHPumpOperationHours:
|
||||
//
|
||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
else
|
||||
msgType = OpenThermMessageType::WRITE_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::DHWPumpValveOperationHours:
|
||||
//
|
||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
else
|
||||
msgType = OpenThermMessageType::WRITE_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::DHWBurnerOperationHours:
|
||||
//
|
||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
else
|
||||
msgType = OpenThermMessageType::WRITE_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::RBPflags:
|
||||
//
|
||||
// Pre-Defined Remote Boiler Parameters
|
||||
//
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::TdhwSet:
|
||||
//
|
||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
else
|
||||
msgType = OpenThermMessageType::WRITE_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::TSet:
|
||||
//
|
||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
else
|
||||
msgType = OpenThermMessageType::WRITE_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::MaxTSet:
|
||||
//
|
||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
else
|
||||
msgType = OpenThermMessageType::WRITE_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::Hcratio:
|
||||
//
|
||||
if (getMessageType(request) == OpenThermMessageType::READ_DATA)
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
else
|
||||
msgType = OpenThermMessageType::WRITE_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::TSP:
|
||||
//
|
||||
// Transparent Slave Parameters
|
||||
//
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::FHBsize:
|
||||
//
|
||||
// Fault History Data
|
||||
//
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::MaxCapacityMinModLevel:
|
||||
//
|
||||
// Boiler Sequencer Control
|
||||
//
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::TrOverride:
|
||||
//
|
||||
// Remote override room setpoint
|
||||
//
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
case OpenThermMessageID::RemoteOverrideFunction:
|
||||
msgType = OpenThermMessageType::READ_ACK;
|
||||
break;
|
||||
|
||||
default:
|
||||
msgType = OpenThermMessageType::UNKNOWN_DATA_ID;
|
||||
break;
|
||||
}
|
||||
response = buildResponse(msgType, id, data);
|
||||
status = OpenThermStatus::RESPONSE_READY;
|
||||
responseStatus = OpenThermResponseStatus::SUCCESS;
|
||||
/*
|
||||
if (processResponseCallback != NULL)
|
||||
{
|
||||
processResponseCallback(response, OpenThermResponseStatus::SUCCESS);
|
||||
}
|
||||
*/
|
||||
}
|
||||
208
src/modules/exec/SmartBoiler/OpenTherm.h
Normal file
208
src/modules/exec/SmartBoiler/OpenTherm.h
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
OpenTherm.h - OpenTherm Library for the ESP8266/Arduino platform
|
||||
https://github.com/ihormelnyk/OpenTherm
|
||||
http://ihormelnyk.com/pages/OpenTherm
|
||||
Licensed under MIT license
|
||||
Copyright 2018, Ihor Melnyk
|
||||
|
||||
Frame Structure:
|
||||
P MGS-TYPE SPARE DATA-ID DATA-VALUE
|
||||
0 000 0000 00000000 00000000 00000000
|
||||
*/
|
||||
|
||||
#ifndef OpenTherm_h
|
||||
#define OpenTherm_h
|
||||
|
||||
#include <stdint.h>
|
||||
#include <Arduino.h>
|
||||
|
||||
enum OpenThermResponseStatus : uint8_t
|
||||
{
|
||||
NONE,
|
||||
SUCCESS,
|
||||
INVALID,
|
||||
TIMEOUT
|
||||
};
|
||||
|
||||
enum OpenThermMessageType : uint8_t
|
||||
{
|
||||
/* Master to Slave */
|
||||
READ_DATA = B000,
|
||||
READ = READ_DATA, // for backwared compatibility
|
||||
WRITE_DATA = B001,
|
||||
WRITE = WRITE_DATA, // for backwared compatibility
|
||||
INVALID_DATA = B010,
|
||||
RESERVED = B011,
|
||||
/* Slave to Master */
|
||||
READ_ACK = B100,
|
||||
WRITE_ACK = B101,
|
||||
DATA_INVALID = B110,
|
||||
UNKNOWN_DATA_ID = B111
|
||||
};
|
||||
|
||||
typedef OpenThermMessageType OpenThermRequestType; // for backwared compatibility
|
||||
|
||||
enum OpenThermMessageID : uint8_t
|
||||
{
|
||||
Status, // flag8 / flag8 Master and Slave Status flags.
|
||||
TSet, // f8.8 Control setpoint ie CH water temperature setpoint (°C)
|
||||
MConfigMMemberIDcode, // flag8 / u8 Master Configuration Flags / Master MemberID Code
|
||||
SConfigSMemberIDcode, // flag8 / u8 Slave Configuration Flags / Slave MemberID Code
|
||||
Command, // u8 / u8 Remote Command
|
||||
ASFflags, // / OEM-fault-code flag8 / u8 Application-specific fault flags and OEM fault code
|
||||
RBPflags, // flag8 / flag8 Remote boiler parameter transfer-enable & read/write flags
|
||||
CoolingControl, // f8.8 Cooling control signal (%)
|
||||
TsetCH2, // f8.8 Control setpoint for 2e CH circuit (°C)
|
||||
TrOverride, // f8.8 Remote override room setpoint
|
||||
TSP, // u8 / u8 Number of Transparent-Slave-Parameters supported by slave
|
||||
TSPindexTSPvalue, // u8 / u8 Index number / Value of referred-to transparent slave parameter.
|
||||
FHBsize, // u8 / u8 Size of Fault-History-Buffer supported by slave
|
||||
FHBindexFHBvalue, // u8 / u8 Index number / Value of referred-to fault-history buffer entry.
|
||||
MaxRelModLevelSetting, // f8.8 Maximum relative modulation level setting (%)
|
||||
MaxCapacityMinModLevel, // u8 / u8 Maximum boiler capacity (kW) / Minimum boiler modulation level(%)
|
||||
TrSet, // f8.8 Room Setpoint (°C)
|
||||
RelModLevel, // f8.8 Relative Modulation Level (%)
|
||||
CHPressure, // f8.8 Water pressure in CH circuit (bar)
|
||||
DHWFlowRate, // f8.8 Water flow rate in DHW circuit. (litres/minute)
|
||||
DayTime, // special / u8 Day of Week and Time of Day
|
||||
Date, // u8 / u8 Calendar date
|
||||
Year, // u16 Calendar year
|
||||
TrSetCH2, // f8.8 Room Setpoint for 2nd CH circuit (°C)
|
||||
Tr, // f8.8 Room temperature (°C)
|
||||
Tboiler, // f8.8 Boiler flow water temperature (°C)
|
||||
Tdhw, // f8.8 DHW temperature (°C)
|
||||
Toutside, // f8.8 Outside temperature (°C)
|
||||
Tret, // f8.8 Return water temperature (°C)
|
||||
Tstorage, // f8.8 Solar storage temperature (°C)
|
||||
Tcollector, // f8.8 Solar collector temperature (°C)
|
||||
TflowCH2, // f8.8 Flow water temperature CH2 circuit (°C)
|
||||
Tdhw2, // f8.8 Domestic hot water temperature 2 (°C)
|
||||
Texhaust, // s16 Boiler exhaust temperature (°C)
|
||||
TheatExchanger, // f8.8 Boiler heat exchanger temperature (°C)
|
||||
BoilerFanSpeed, // u16 Boiler fan speed Setpiont and actual value
|
||||
ElectricBurnerFlame, // f8.8?? Electric current through burner flame (mюA)
|
||||
TdhwSetUBTdhwSetLB = 48, // s8 / s8 DHW setpoint upper & lower bounds for adjustment (°C)
|
||||
MaxTSetUBMaxTSetLB, // s8 / s8 Max CH water setpoint upper & lower bounds for adjustment (°C)
|
||||
HcratioUBHcratioLB, // s8 / s8 OTC heat curve ratio upper & lower bounds for adjustment
|
||||
TdhwSet = 56, // f8.8 DHW setpoint (°C) (Remote parameter 1)
|
||||
MaxTSet, // f8.8 Max CH water setpoint (°C) (Remote parameters 2)
|
||||
Hcratio, // f8.8 OTC heat curve ratio (°C) (Remote parameter 3)
|
||||
RemoteOverrideFunction = 100, // flag8 / - Function of manual and program changes in master and remote room setpoint.
|
||||
OEMDiagnosticCode = 115, // u16 OEM-specific diagnostic/service code
|
||||
BurnerStarts, // u16 Number of starts burner
|
||||
CHPumpStarts, // u16 Number of starts CH pump
|
||||
DHWPumpValveStarts, // u16 Number of starts DHW pump/valve
|
||||
DHWBurnerStarts, // u16 Number of starts burner during DHW mode
|
||||
BurnerOperationHours, // u16 Number of hours that burner is in operation (i.e. flame on)
|
||||
CHPumpOperationHours, // u16 Number of hours that CH pump has been running
|
||||
DHWPumpValveOperationHours, // u16 Number of hours that DHW pump has been running or DHW valve has been opened
|
||||
DHWBurnerOperationHours, // u16 Number of hours that burner is in operation during DHW mode
|
||||
OpenThermVersionMaster, // f8.8 The implemented version of the OpenTherm Protocol Specification in the master.
|
||||
OpenThermVersionSlave, // f8.8 The implemented version of the OpenTherm Protocol Specification in the slave.
|
||||
MasterVersion, // u8 / u8 Master product version number and type
|
||||
SlaveVersion, // u8 / u8 Slave product version number and type
|
||||
};
|
||||
|
||||
enum OpenThermStatus : uint8_t
|
||||
{
|
||||
NOT_INITIALIZED,
|
||||
READY,
|
||||
DELAY,
|
||||
REQUEST_SENDING,
|
||||
RESPONSE_WAITING,
|
||||
RESPONSE_START_BIT,
|
||||
RESPONSE_RECEIVING,
|
||||
RESPONSE_READY,
|
||||
RESPONSE_INVALID
|
||||
};
|
||||
|
||||
class OpenTherm
|
||||
{
|
||||
public:
|
||||
OpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false);
|
||||
volatile OpenThermStatus status;
|
||||
void begin(void (*handleInterruptCallback)(void));
|
||||
void begin(void (*handleInterruptCallback)(void), void (*processResponseCallback)(unsigned long, OpenThermResponseStatus));
|
||||
bool isReady();
|
||||
unsigned long sendRequest(unsigned long request);
|
||||
bool sendResponse(unsigned long request);
|
||||
bool sendRequestAync(unsigned long request);
|
||||
unsigned long buildRequest(OpenThermMessageType type, OpenThermMessageID id, unsigned int data);
|
||||
unsigned long buildRequestID(OpenThermMessageType type, unsigned int id, unsigned int data);
|
||||
unsigned long buildResponse(OpenThermMessageType type, OpenThermMessageID id, unsigned int data);
|
||||
unsigned long getLastResponse();
|
||||
OpenThermResponseStatus getLastResponseStatus();
|
||||
const char *statusToString(OpenThermResponseStatus status);
|
||||
void handleInterrupt();
|
||||
void process();
|
||||
void end();
|
||||
|
||||
bool parity(unsigned long frame);
|
||||
OpenThermMessageType getMessageType(unsigned long message);
|
||||
OpenThermMessageID getDataID(unsigned long frame);
|
||||
const char *messageTypeToString(OpenThermMessageType message_type);
|
||||
bool isValidRequest(unsigned long request);
|
||||
bool isValidResponse(unsigned long response);
|
||||
|
||||
// requests
|
||||
unsigned long buildSetBoilerStatusRequest(bool enableCentralHeating, bool enableHotWater = false, bool enableCooling = false, bool enableOutsideTemperatureCompensation = false, bool enableCentralHeating2 = false, bool enableSummerMode = false, bool dhwBlock = false);
|
||||
unsigned long buildSetBoilerTemperatureRequest(float temperature);
|
||||
unsigned long buildGetBoilerTemperatureRequest();
|
||||
|
||||
// responses
|
||||
bool isFault(unsigned long response);
|
||||
bool isCentralHeatingActive(unsigned long response);
|
||||
bool isHotWaterActive(unsigned long response);
|
||||
bool isFlameOn(unsigned long response);
|
||||
bool isCoolingActive(unsigned long response);
|
||||
bool isDiagnostic(unsigned long response);
|
||||
uint16_t getUInt(const unsigned long response) const;
|
||||
float getFloat(const unsigned long response) const;
|
||||
unsigned int temperatureToData(float temperature);
|
||||
|
||||
// basic requests
|
||||
unsigned long setBoilerStatus(bool enableCentralHeating, bool enableHotWater = false, bool enableCooling = false, bool enableOutsideTemperatureCompensation = false, bool enableCentralHeating2 = false, bool enableSummerMode = false, bool dhwBlock = false);
|
||||
bool setBoilerTemperature(float temperature);
|
||||
float getBoilerTemperature();
|
||||
float getReturnTemperature();
|
||||
bool setDHWSetpoint(float temperature);
|
||||
float getDHWTemperature();
|
||||
float getModulation();
|
||||
float getPressure();
|
||||
unsigned char getFault();
|
||||
|
||||
//Имитация ответов от котла, TRUE - идет имитация ответов котла, в котел так же шлется (лучше его отключить), FALSE - штатная работа
|
||||
void imitation(bool fl) {imitFlag = fl;}
|
||||
|
||||
private:
|
||||
bool imitFlag;
|
||||
void ImitationResponse(unsigned long request);
|
||||
|
||||
const int inPin;
|
||||
const int outPin;
|
||||
const bool isSlave;
|
||||
|
||||
volatile unsigned long response;
|
||||
volatile OpenThermResponseStatus responseStatus;
|
||||
volatile unsigned long responseTimestamp;
|
||||
volatile byte responseBitIndex;
|
||||
|
||||
int readState();
|
||||
void setActiveState();
|
||||
void setIdleState();
|
||||
void activateBoiler();
|
||||
|
||||
void sendBit(bool high);
|
||||
void (*handleInterruptCallback)();
|
||||
void (*processResponseCallback)(unsigned long, OpenThermResponseStatus);
|
||||
};
|
||||
|
||||
#ifndef ICACHE_RAM_ATTR
|
||||
#define ICACHE_RAM_ATTR
|
||||
#endif
|
||||
|
||||
#ifndef IRAM_ATTR
|
||||
#define IRAM_ATTR ICACHE_RAM_ATTR
|
||||
#endif
|
||||
|
||||
#endif // OpenTherm_h
|
||||
1336
src/modules/exec/SmartBoiler/SmartBoiler.cpp
Normal file
1336
src/modules/exec/SmartBoiler/SmartBoiler.cpp
Normal file
File diff suppressed because it is too large
Load Diff
171
src/modules/exec/SmartBoiler/modinfo.json
Normal file
171
src/modules/exec/SmartBoiler/modinfo.json
Normal file
@@ -0,0 +1,171 @@
|
||||
{
|
||||
"menuSection": "executive_devices",
|
||||
"configItem": [
|
||||
{
|
||||
"global": 0,
|
||||
"name": "BoilerControl",
|
||||
"type": "Reading",
|
||||
"subtype": "BoilerControl",
|
||||
"id": "boiler",
|
||||
"widget": "anydataWt",
|
||||
"page": "Boiler",
|
||||
"descr": "Котёл",
|
||||
"int": 1,
|
||||
"value": "...",
|
||||
"debug": 0,
|
||||
"telegram": 1,
|
||||
"idPID":"PID",
|
||||
"idTboiler": "Tboiler",
|
||||
"idTret": "Tret",
|
||||
"idToutside": "Toutside",
|
||||
"idStateCH":"StateCH",
|
||||
"idStateFlame":"StateFlame",
|
||||
"idModLevel":"ModLevel",
|
||||
"idCmdCH":"CmdCH",
|
||||
"idCmdDHW":"CmdDHW",
|
||||
"idSetCH":"SetCH",
|
||||
"idCtrlType":"CtrlType",
|
||||
"changeRele":0,
|
||||
"idRelePump": "relePump",
|
||||
"minCH": 35,
|
||||
"maxCH": 85,
|
||||
"gistCH": 5,
|
||||
"antiFreez":10,
|
||||
"maxKW": 24
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"name": "DHWControl",
|
||||
"type": "Reading",
|
||||
"subtype": "DHWControl",
|
||||
"id": "dhw",
|
||||
"widget": "",
|
||||
"page": "Boiler",
|
||||
"descr": "Котёл",
|
||||
"int": 1,
|
||||
"value": "...",
|
||||
"idTdhw": "TDhw",
|
||||
"idReleDhw": "ReleDhw",
|
||||
"idCmdDHW":"CmdDHW",
|
||||
"idStateDHW":"StateDHW",
|
||||
"idSetDHW":"SetDHW",
|
||||
"minDhw": 20,
|
||||
"maxDhw": 60,
|
||||
"gistDhw": 2,
|
||||
"numStepDhw":1
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"name": "OpenThermSlave",
|
||||
"type": "Reading",
|
||||
"subtype": "OpenThermSlave",
|
||||
"id": "otslave",
|
||||
"widget": "",
|
||||
"page": "Boiler",
|
||||
"descr": "Котёл",
|
||||
"int": 1,
|
||||
"value": "...",
|
||||
"RX_pin": 13,
|
||||
"TX_pin": 15,
|
||||
"MemberID": 0
|
||||
}
|
||||
],
|
||||
"about": {
|
||||
"authorName": "Mikhail Bubnov",
|
||||
"authorContact": "https://t.me/Mit4bmw",
|
||||
"authorGit": "https://github.com/Mit4el",
|
||||
"specialThanks": "",
|
||||
"moduleName": "SmartBoiler",
|
||||
"moduleVersion": "2.0",
|
||||
"usedRam": {
|
||||
"esp32_4mb": 15,
|
||||
"esp8266_4mb": 15
|
||||
},
|
||||
"subTypes": [
|
||||
"BoilerControl",
|
||||
"OpenThermSlave",
|
||||
"DHWControl"
|
||||
],
|
||||
"title": "SmartBoiler",
|
||||
"moduleDesc": "Модуль для автоматизации электрического котла. Мозги котла с внешним протоколом opentherm. Модуль OpenThermSlave_v2 id модулй использует теже, что указаны в BoilerControl_v2. Но так же может работать автономно, если нет модуля BoilerControl_v2, он ищет модули по ID по умолчаию",
|
||||
"propInfo": {
|
||||
"int": "Интервал обработки логики и опроса внешних модулей",
|
||||
"telegram": "1- Будет отправлять в телеграмм оповещения при ошибках котла и пропаже сигнала от котла, остальные необходимо реализовывать через сценарий",
|
||||
"MemberID": "SlaveMemberIDcode - код производителя котла, кем притворится котёл;) Менять в большинстве случаев не надо",
|
||||
"idPID":"ID модуля ПИД регулятора, для расчета модуляции и включения тэнов в зависимости от температуры теплоносителя, в модуле TCHSet будет уставка СО, создать TCHSet и указать его в модуле ПИД",
|
||||
"idTboiler": "ID датчика температуры подачи котла",
|
||||
"idTret": "ID датчика температуры обратки котла, только для передачи по opentherm",
|
||||
"idToutside": "ID датчика уличной температуры, только для передачи по opentherm",
|
||||
"Pupm": "1-есть реле насоса (ID реле должно называться relePump), 0-нет реле насоса, насос управляется котлом без нас",
|
||||
"minCH": "Граница установки температуры СО",
|
||||
"maxCH": "Граница установки температуры СО",
|
||||
"gistCH": "Гистерезис СО - нагрев СО включится если температура теплоносителя ниже уставки на указанные градусы (CHSet = 45гр, gistCH = 5гр, нагрев включится когда idTboiler = 40гр)",
|
||||
"idTdhw": "ID датчика температуры ГВС, например в датчик в БКН",
|
||||
"idReleDhw":"ID реле трехходового крана ГВС",
|
||||
"gistDhw": "Гистерезис ГВС - нагрев ГВС включится если температура воды ниже уставки на указанные градусы",
|
||||
"minDhw": "Граница установки температуры ГВС",
|
||||
"maxDhw": "Граница установки температуры ГВС",
|
||||
"changeRele":"Будет менять каждый раз при включении тэн 1->2->3->1...",
|
||||
"antiFreez":"Режим анти-замерзания, Указывается температура, если опустится ниже указанной, то включится нарев один тэн и нагреет на +5гр от указанной",
|
||||
"maxKW": "Максимальная мощность котла при включении на поcледнем Шаге Мощности",
|
||||
"numStepDhw":"На каком Шаге Мощности включать ГВС"
|
||||
},
|
||||
"funcInfo": [
|
||||
{
|
||||
"name": "CHSet",
|
||||
"descr": "Установить целевую температуру СО",
|
||||
"params": [
|
||||
"тепмература СО (подачи) - bolier.CHSet(60)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "CHEnable",
|
||||
"descr": "включить / выключить отопление",
|
||||
"params": [
|
||||
"bolier.CHEnable(1) - вкл, bolier.CHEnable(0) - выкл, "
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SetDHW",
|
||||
"descr": "Установить целевую температуру ГВС",
|
||||
"params": [
|
||||
"тепмература ГВС - dhw.SetDHW(40)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "DHWEnable",
|
||||
"descr": "включить / выключить ГВС",
|
||||
"params": [
|
||||
"dhw.DHWEnable(1) - вкл, dhw.DHWEnable(0) - выкл "
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "addStepPower",
|
||||
"descr": "Добавить Шаг Нагрева: мощность Шага кВт, ID реле на данном шаге",
|
||||
"params": [
|
||||
"bolier.addStepPower(1, 3, rele1) - шаг №1 в 3kW на первом реле, bolier.addStepPower(4, 24, rele1, rele3, rele4) - шаг 4 в 24Квт на 1+3+4 реле "
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "onStepPower",
|
||||
"descr": "включить определенный шаг нагрева, указывается номер шага, Включит Ручной Режим! ",
|
||||
"params": [
|
||||
"bolier.onStepPower(2) "
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "autoPower",
|
||||
"descr": "включить автоматический режим работы котла (по умолчанию включен) ",
|
||||
"params": [
|
||||
"bolier.autoPower()"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32_4mb3f": [],
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
}
|
||||
}
|
||||
71
src/modules/exec/SmartBoiler/readme.txt
Normal file
71
src/modules/exec/SmartBoiler/readme.txt
Normal file
@@ -0,0 +1,71 @@
|
||||
Модуль для автоматизации электрического котла.
|
||||
(Описание модуля SmartBoiler/modinfo.json)
|
||||
|
||||
0 TODO Сделать чтобы при связи моих модулей промежуточные итемы можно было не создавать,
|
||||
но если их создать там отобразятся актуальные значения
|
||||
|
||||
1 Управления котлом с 1-3 тэнами
|
||||
1.1 указывается количество тэнов от 1 до 3х
|
||||
1.2 задается мощность каждого тэна, должна быть по возрастающей
|
||||
1.3 задание гистерезиса включения тэнов (ограничиваем в том числе и с ПИД)
|
||||
1.4 задание минимальной и максимальной температуры теплоносителя (минимальную автоматически не поддерживает пока, просто проверяет вхождение в диапазон)
|
||||
1.5 задание минимальной и максимальной температуры ГВС (минимальную автоматически не поддерживает пока, просто проверяет вхождение в диапазон)
|
||||
1.6 реализация тремя отдельными элементами:
|
||||
BoilerControl - Основная логика котла, по сути раздутый термостат
|
||||
DHWControl - Логика управления нагрева ГВС
|
||||
OpenThermSlave - Обеспечения протоокла взаимодействи OpenTherm
|
||||
1.7 Смена тэна периодически (по флагу из конфигурации)
|
||||
1.8 Если отвалились датчика, котел не включится
|
||||
1.9 TODO режим антизамерзания
|
||||
1.998 TODO поддержание минимальной температуры СО и ГВС
|
||||
|
||||
|
||||
2 Поддержание температуры теплоносителя
|
||||
2.1 Теплоноситель нагреется до заданной целевой и поддерживает её по гистерезису (если упала на гист. то включит нагрев)
|
||||
2.2 ПИД расчитывает только количество тэнов для включения (0-30% = первый тэн, 30-60% = 1 и 2 тэны, 60-100% все тэны)
|
||||
2.3 в зависимости от включенных тэнов и их мощности показывается процент модуляции.
|
||||
2.4 Если не добавлен модуль ПИД, то включает все тэны
|
||||
2.5 Если указан всего один тэн, то модуль ПИД не нужно создавать
|
||||
2.998 TODO возможно использовать ПИД для периодического включения одного тэна
|
||||
2.999 TODO сделать больше ступеней включения с различными комбинациями тэнов
|
||||
|
||||
3 Управление из IoTManager
|
||||
3.1 Возможность управления по комнатному термостату в том числе с другой ESP
|
||||
3.2 Управление модулем из сценария
|
||||
3.3 есть проверка ошибок датчиков (если отвалились датчика, котел не включится)
|
||||
3.4 Отправка состояния в телеграмм
|
||||
3.998 TODO Автоматическая отправка состояния в модули для отображения (имена модулей в логах "new")
|
||||
3.999 другой функционал IoTManager ...
|
||||
|
||||
4 Возможность управления циркуляционным насосом
|
||||
4.1 насос включается всегда, если включено хотя бы один из тэнов
|
||||
4.2 если включено по ГВС, насос отключается сразу как ГВС нагрелся до целевой
|
||||
4.3 если включено по СО, насос отключается когда теплоноситель остынет до минимальной температуры СО
|
||||
4.999 TODO Сделать управление выбегом насоса
|
||||
|
||||
5 Возможность управления ГВС при наличии 3-х ходового крана с БКН (бойлер косвенного нагрева)
|
||||
5.1 ГВС работает только при указании реле 3-х ходового крана (а иначе как?)
|
||||
5.2 ГВС имеет приоритет над СО
|
||||
5.3 при нагреве ГВС котел включается на полную мощность (все тэны)
|
||||
5.4 ГВС нагреет до заданной целевой и поддерживает её по гистерезису (если упала на гист. то включит ГВС)
|
||||
5.5 Температура подачи котла при этом не должны превысить минимальную температуру СО (что бы не перегревал тенплоноситель, пока греется БКН)
|
||||
|
||||
6 Возможность управления по OpenTherm
|
||||
6.1 в схему платы управления необходимо добавить часть OpenThermSlave и 24В
|
||||
6.2 возможность управления любым OpenTherm адаптером/термостатом
|
||||
6.3 задание целевой температуры теплоносителя
|
||||
6.4 задание целевой температуры ГВС
|
||||
6.5 команды включения СО и ГВС
|
||||
6.6 отправка статуса Управляющему устройству OpenTherm
|
||||
6.999 TODO Явного приоритета OpenTherm над другим управлением нет, а надо сделать. Сделать настройку "OpenTherm главне сценария" (сейчас команды выполняются ото всех по мере поступления)
|
||||
|
||||
7. Название модулей в которые автоматически отправится информации при их наличии (простто добавить в конфигурацию с указанным именем)
|
||||
controlType - Тип управления тэнами: 0 - модуляция, 1- вкл/выкл
|
||||
CHEnable - Состояние включения СО (не нагрев, а включение режима) 0 - выкл, 1- вкл
|
||||
isFlame - Состояние нагрева/горелки (включенных тэнов) 0 - выкл, 1- вкл
|
||||
RelModLevel - Уровень модуляции, в процентах в зависимости мощности включенных тэнов от их общего количества
|
||||
TDHWSet - Установленная в котле целевая температура ГВС (из Сценария или OpenTherm)
|
||||
TCHSet - Установленная в котле целевая температура СО (из Сценария или OpenTherm)
|
||||
DHWEnable Состояние включения ГВС (не нагрев, а включение режима) 0 - выкл, 1- вкл
|
||||
boilerslave - Состояние подключения к Управляющему устройству OpenTherm, значком ❌ ✅
|
||||
status - Состояние подключения к Управляющему устройству OpenTherm, строкой: "не подключен" / "подключен"
|
||||
237
src/modules/exec/SmartBoiler/smartBoiler.json
Normal file
237
src/modules/exec/SmartBoiler/smartBoiler.json
Normal file
@@ -0,0 +1,237 @@
|
||||
{
|
||||
"mark": "iotm",
|
||||
"config": [
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "Variable",
|
||||
"id": "Tboiler",
|
||||
"needSave": 0,
|
||||
"widget": "inputDgt",
|
||||
"page": "Котёл",
|
||||
"descr": "Датчик котла",
|
||||
"int": "0",
|
||||
"val": "20",
|
||||
"map": "1024,1024,1,100",
|
||||
"plus": 0,
|
||||
"multiply": 1,
|
||||
"round": 0
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "Variable",
|
||||
"id": "SetCH",
|
||||
"needSave": 0,
|
||||
"widget": "inputDgt",
|
||||
"page": "Котёл",
|
||||
"descr": "Уставка СО",
|
||||
"int": "0",
|
||||
"val": "60",
|
||||
"map": "1024,1024,1,100",
|
||||
"plus": 0,
|
||||
"multiply": 1,
|
||||
"round": 0
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "Variable",
|
||||
"id": "CtrlType",
|
||||
"needSave": 0,
|
||||
"widget": "anydataDef",
|
||||
"page": "Состояние",
|
||||
"descr": " Тип управления",
|
||||
"int": "0",
|
||||
"val": "0.0",
|
||||
"map": "1024,1024,1,100",
|
||||
"plus": 0,
|
||||
"multiply": 1,
|
||||
"round": 0
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "VButton",
|
||||
"id": "CmdCH",
|
||||
"needSave": 0,
|
||||
"widget": "toggle",
|
||||
"page": "Котёл",
|
||||
"descr": " ВКЛ СО",
|
||||
"int": "0",
|
||||
"val": "0"
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "Variable",
|
||||
"id": "StateCH",
|
||||
"needSave": 0,
|
||||
"widget": "anydataDef",
|
||||
"page": "Состояние",
|
||||
"descr": "Состояние СО",
|
||||
"int": "0",
|
||||
"val": "0.0",
|
||||
"map": "1024,1024,1,100",
|
||||
"plus": 0,
|
||||
"multiply": 1,
|
||||
"round": 0
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "Variable",
|
||||
"id": "StateFlame",
|
||||
"needSave": 0,
|
||||
"widget": "anydataDef",
|
||||
"page": "Состояние",
|
||||
"descr": "Состояние Нагрева",
|
||||
"int": "0",
|
||||
"val": "0.0",
|
||||
"map": "1024,1024,1,100",
|
||||
"plus": 0,
|
||||
"multiply": 1,
|
||||
"round": 0
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "VButton",
|
||||
"id": "rele1",
|
||||
"needSave": 0,
|
||||
"widget": "toggle",
|
||||
"page": "Котёл",
|
||||
"descr": "rele1",
|
||||
"int": "0",
|
||||
"val": "0"
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "VButton",
|
||||
"id": "rele2",
|
||||
"needSave": 0,
|
||||
"widget": "toggle",
|
||||
"page": "Котёл",
|
||||
"descr": "rele2",
|
||||
"int": "0",
|
||||
"val": "0"
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "VButton",
|
||||
"id": "rele3",
|
||||
"needSave": 0,
|
||||
"widget": "toggle",
|
||||
"page": "Котёл",
|
||||
"descr": "rele3",
|
||||
"int": "0",
|
||||
"val": "0"
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "Variable",
|
||||
"id": "ModLevel",
|
||||
"needSave": 0,
|
||||
"widget": "anydataHum",
|
||||
"page": "Состояние",
|
||||
"descr": "Модуляция",
|
||||
"int": "0",
|
||||
"val": "0.0",
|
||||
"map": "1024,1024,1,100",
|
||||
"plus": 0,
|
||||
"multiply": 1,
|
||||
"round": 0
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "VButton",
|
||||
"id": "relePump",
|
||||
"needSave": 0,
|
||||
"widget": "toggle",
|
||||
"page": "Котёл",
|
||||
"descr": "Реле цирк.насоса",
|
||||
"int": "0",
|
||||
"val": "0"
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "Variable",
|
||||
"id": "status",
|
||||
"needSave": 0,
|
||||
"widget": "anydataDef",
|
||||
"page": "Состояние",
|
||||
"descr": " Состояние",
|
||||
"int": "0",
|
||||
"val": "0.0",
|
||||
"map": "1024,1024,1,100",
|
||||
"plus": 0,
|
||||
"multiply": 1,
|
||||
"round": 0
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"needSave": 0,
|
||||
"type": "Writing",
|
||||
"subtype": "ThermostatPID",
|
||||
"id": "PID",
|
||||
"widget": "anydataHum",
|
||||
"page": "Котёл",
|
||||
"descr": "термостат",
|
||||
"int": 60,
|
||||
"round": 1,
|
||||
"map": "1,100,1,100",
|
||||
"set_id": "SetCH",
|
||||
"term_id": "Tboiler",
|
||||
"term_rezerv_id": "",
|
||||
"rele": "",
|
||||
"KP": 5,
|
||||
"KI": 50,
|
||||
"KD": 1
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "BoilerControl",
|
||||
"id": "boiler",
|
||||
"widget": "anydataWt",
|
||||
"page": "Котёл",
|
||||
"descr": "Котёл",
|
||||
"int": 1,
|
||||
"value": "...",
|
||||
"debug": "0",
|
||||
"telegram": 1,
|
||||
"idPID": "PID",
|
||||
"idTboiler": "Tboiler",
|
||||
"idTret": "Tret",
|
||||
"idToutside": "Toutside",
|
||||
"idStateCH": "StateCH",
|
||||
"idStateFlame": "StateFlame",
|
||||
"idModLevel": "ModLevel",
|
||||
"idCmdCH": "CmdCH",
|
||||
"idCmdDHW": "CmdDHW",
|
||||
"idSetCH": "SetCH",
|
||||
"idCtrlType": "CtrlType",
|
||||
"changeRele": 0,
|
||||
"idRelePump": "relePump",
|
||||
"minCH": 35,
|
||||
"maxCH": 85,
|
||||
"gistCH": 5,
|
||||
"antiFreez": 10,
|
||||
"maxKW": "8"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
scenario=>if onStart then
|
||||
{
|
||||
boiler.addStepPower(1, 3, "rele1");
|
||||
boiler.addStepPower(2, 5, "rele2", "rele3");
|
||||
boiler.addStepPower(3, 8, "rele1", "rele2", "rele3");
|
||||
#boiler.onStepPower(2);
|
||||
#boiler.autoPower();
|
||||
}
|
||||
@@ -110,8 +110,13 @@ class Telegram : public IoTItem {
|
||||
}
|
||||
}
|
||||
|
||||
~Telegram() {
|
||||
IoTItem *getTlgrmDriver()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
~Telegram() {
|
||||
tlgrmItem = nullptr;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -58,7 +58,14 @@ class TelegramLT : public IoTItem {
|
||||
return {};
|
||||
}
|
||||
|
||||
~TelegramLT(){};
|
||||
IoTItem *getTlgrmDriver()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
~TelegramLT() {
|
||||
tlgrmItem = nullptr;
|
||||
};
|
||||
};
|
||||
|
||||
void *getAPI_TelegramLT(String subtype, String param) {
|
||||
|
||||
269
src/modules/sensors/BL0937/BL0937.cpp
Normal file
269
src/modules/sensors/BL0937/BL0937.cpp
Normal file
@@ -0,0 +1,269 @@
|
||||
|
||||
#include "Global.h"
|
||||
#include "classes/IoTItem.h"
|
||||
|
||||
#include "BL0937lib.h"
|
||||
// #include "classes/IoTUart.h"
|
||||
// #include <map>
|
||||
BL0937 *bl0937 = nullptr;
|
||||
|
||||
class BL0937v : public IoTItem
|
||||
{
|
||||
private:
|
||||
public:
|
||||
BL0937v(String parameters) : IoTItem(parameters)
|
||||
{
|
||||
}
|
||||
|
||||
void doByInterval()
|
||||
{
|
||||
if (bl0937)
|
||||
regEvent(bl0937->getVoltage(), "BL0937 V");
|
||||
else
|
||||
{
|
||||
regEvent(NAN, "BL0937v");
|
||||
SerialPrint("E", "BL0937cmd", "initialization error", _id);
|
||||
}
|
||||
}
|
||||
|
||||
~BL0937v(){};
|
||||
};
|
||||
|
||||
class BL0937a : public IoTItem
|
||||
{
|
||||
private:
|
||||
public:
|
||||
BL0937a(String parameters) : IoTItem(parameters)
|
||||
{
|
||||
}
|
||||
|
||||
void doByInterval()
|
||||
{
|
||||
if (bl0937)
|
||||
regEvent(bl0937->getCurrent(), "BL0937 A");
|
||||
else
|
||||
{
|
||||
regEvent(NAN, "BL0937a");
|
||||
SerialPrint("E", "BL0937cmd", "initialization error", _id);
|
||||
}
|
||||
}
|
||||
|
||||
~BL0937a(){};
|
||||
};
|
||||
|
||||
class BL0937w : public IoTItem
|
||||
{
|
||||
private:
|
||||
public:
|
||||
BL0937w(String parameters) : IoTItem(parameters)
|
||||
{
|
||||
}
|
||||
|
||||
void doByInterval()
|
||||
{
|
||||
if (bl0937)
|
||||
regEvent(bl0937->getApparentPower(), "BL0937 W");
|
||||
else
|
||||
{
|
||||
regEvent(NAN, "BL0937w");
|
||||
SerialPrint("E", "BL0937cmd", "initialization error", _id);
|
||||
}
|
||||
}
|
||||
|
||||
~BL0937w(){};
|
||||
};
|
||||
|
||||
class BL0937reactw : public IoTItem
|
||||
{
|
||||
private:
|
||||
public:
|
||||
BL0937reactw(String parameters) : IoTItem(parameters)
|
||||
{
|
||||
}
|
||||
|
||||
void doByInterval()
|
||||
{
|
||||
if (bl0937)
|
||||
regEvent(bl0937->getReactivePower(), "BL0937 reactW");
|
||||
else
|
||||
{
|
||||
regEvent(NAN, "BL0937reactw");
|
||||
SerialPrint("E", "BL0937cmd", "initialization error", _id);
|
||||
}
|
||||
}
|
||||
|
||||
~BL0937reactw(){};
|
||||
};
|
||||
|
||||
class BL0937actw : public IoTItem
|
||||
{
|
||||
private:
|
||||
public:
|
||||
BL0937actw(String parameters) : IoTItem(parameters)
|
||||
{
|
||||
}
|
||||
|
||||
void doByInterval()
|
||||
{
|
||||
if (bl0937)
|
||||
regEvent(bl0937->getActivePower(), "BL0937 actW");
|
||||
else
|
||||
{
|
||||
regEvent(NAN, "BL0937actw");
|
||||
SerialPrint("E", "BL0937cmd", "initialization error", _id);
|
||||
}
|
||||
}
|
||||
|
||||
~BL0937actw(){};
|
||||
};
|
||||
|
||||
class BL0937wh : public IoTItem
|
||||
{
|
||||
private:
|
||||
public:
|
||||
BL0937wh(String parameters) : IoTItem(parameters)
|
||||
{
|
||||
}
|
||||
|
||||
void doByInterval()
|
||||
{
|
||||
if (bl0937)
|
||||
regEvent(bl0937->getEnergy() / 3600.0 / 1000.0, "BL0937 Wh");
|
||||
else
|
||||
{
|
||||
regEvent(NAN, "BL0937wh");
|
||||
SerialPrint("E", "BL0937cmd", "initialization error", _id);
|
||||
}
|
||||
}
|
||||
|
||||
~BL0937wh(){};
|
||||
};
|
||||
|
||||
void ICACHE_RAM_ATTR bl0937_cf1_interrupt()
|
||||
{
|
||||
bl0937->cf1_interrupt();
|
||||
}
|
||||
void ICACHE_RAM_ATTR bl0937_cf_interrupt()
|
||||
{
|
||||
bl0937->cf_interrupt();
|
||||
}
|
||||
|
||||
class BL0937cmd : public IoTItem
|
||||
{
|
||||
private:
|
||||
float CURRENT_RESISTOR = 0.001; // Нужна возможность задавать из веб, это по умолчанию
|
||||
int VOLTAGE_RESISTOR_UPSTREAM = 1000000; // Нужна возможность задавать из веб, это по умолчанию
|
||||
int VOLTAGE_RESISTOR_DOWNSTREAM = 1000; // Нужна возможность задавать из веб, это по умолчанию
|
||||
int BL0937_CF_GPIO = 4; // 8266 12 //Нужна возможность задавать пин из веб, это по умолчанию
|
||||
int BL0937_CF1_GPIO = 5; // 8266 13 //Нужна возможность задавать пин из веб, это по умолчанию
|
||||
int BL0937_SEL_GPIO_INV = 12; // 8266 15 // inverted //Нужна возможность задавать пин из веб, это по умолчанию
|
||||
float _expV = 0;
|
||||
float _expA = 0;
|
||||
float _expW = 0;
|
||||
|
||||
public:
|
||||
BL0937cmd(String parameters) : IoTItem(parameters)
|
||||
{
|
||||
jsonRead(parameters, "R_current", CURRENT_RESISTOR);
|
||||
jsonRead(parameters, "R_upstream", VOLTAGE_RESISTOR_UPSTREAM);
|
||||
jsonRead(parameters, "R_downstream", VOLTAGE_RESISTOR_DOWNSTREAM);
|
||||
jsonRead(parameters, "CF_GPIO", BL0937_CF_GPIO);
|
||||
jsonRead(parameters, "CF1_GPIO", BL0937_CF1_GPIO);
|
||||
jsonRead(parameters, "SEL_GPIO", BL0937_SEL_GPIO_INV);
|
||||
jsonRead(parameters, "expV", _expV);
|
||||
jsonRead(parameters, "expA", _expA);
|
||||
jsonRead(parameters, "expW", _expW);
|
||||
bl0937 = new BL0937;
|
||||
bl0937->begin(BL0937_CF_GPIO, BL0937_CF1_GPIO, BL0937_SEL_GPIO_INV, LOW, true);
|
||||
bl0937->setResistors(CURRENT_RESISTOR, VOLTAGE_RESISTOR_UPSTREAM, VOLTAGE_RESISTOR_DOWNSTREAM);
|
||||
attachInterrupt(BL0937_CF1_GPIO, bl0937_cf1_interrupt, FALLING);
|
||||
attachInterrupt(BL0937_CF_GPIO, bl0937_cf_interrupt, FALLING);
|
||||
if (_expV)
|
||||
bl0937->expectedVoltage(_expV); // для калибровки вольтаж нужно вводить из веб интерфейса
|
||||
if (_expV)
|
||||
bl0937->expectedCurrent(_expA); // для калибровки можно так, а лучше ток вводить из веб интерфейса
|
||||
if (_expV)
|
||||
bl0937->expectedActivePower(_expW); // для калибровки потребляемую мощность нужно вводить из веб интерфейса
|
||||
}
|
||||
|
||||
void doByInterval()
|
||||
{
|
||||
}
|
||||
|
||||
void onModuleOrder(String &key, String &value)
|
||||
{
|
||||
if (bl0937)
|
||||
{
|
||||
if (key == "reset")
|
||||
{
|
||||
bl0937->resetEnergy();
|
||||
SerialPrint("i", "BL0937", "reset energy done");
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
IoTValue execute(String command, std::vector<IoTValue> ¶m)
|
||||
{
|
||||
if (!bl0937)
|
||||
return {};
|
||||
if (command == "calibration")
|
||||
{
|
||||
if (param.size() == 3)
|
||||
{
|
||||
float v = param[0].valD;
|
||||
float a = param[1].valD;
|
||||
float p = param[2].valD;
|
||||
bl0937->expectedVoltage(v); // для калибровки вольтаж нужно вводить из веб интерфейса
|
||||
bl0937->expectedCurrent(a); // для калибровки можно так, а лучше ток вводить из веб интерфейса
|
||||
bl0937->expectedActivePower(p); // для калибровки потребляемую мощность нужно вводить из веб интерфейса
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
*/
|
||||
~BL0937cmd()
|
||||
{
|
||||
if (bl0937)
|
||||
{
|
||||
delete bl0937;
|
||||
bl0937 = nullptr;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
void *getAPI_BL0937(String subtype, String param)
|
||||
{
|
||||
if (subtype == F("BL0937v"))
|
||||
{
|
||||
return new BL0937v(param);
|
||||
}
|
||||
else if (subtype == F("BL0937a"))
|
||||
{
|
||||
return new BL0937a(param);
|
||||
}
|
||||
else if (subtype == F("BL0937w"))
|
||||
{
|
||||
return new BL0937w(param);
|
||||
}
|
||||
else if (subtype == F("BL0937wh"))
|
||||
{
|
||||
return new BL0937wh(param);
|
||||
}
|
||||
else if (subtype == F("BL0937reactw"))
|
||||
{
|
||||
return new BL0937reactw(param);
|
||||
}
|
||||
else if (subtype == F("BL0937actw"))
|
||||
{
|
||||
return new BL0937actw(param);
|
||||
}
|
||||
else if (subtype == F("BL0937cmd"))
|
||||
{
|
||||
return new BL0937cmd(param);
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
238
src/modules/sensors/BL0937/BL0937lib.cpp
Normal file
238
src/modules/sensors/BL0937/BL0937lib.cpp
Normal file
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
|
||||
BL0937
|
||||
|
||||
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
|
||||
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/>.
|
||||
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "BL0937lib.h"
|
||||
|
||||
void BL0937::begin(
|
||||
unsigned char cf_pin,
|
||||
unsigned char cf1_pin,
|
||||
unsigned char sel_pin,
|
||||
unsigned char currentWhen,
|
||||
bool use_interrupts,
|
||||
unsigned long pulse_timeout
|
||||
) {
|
||||
|
||||
_cf_pin = cf_pin;
|
||||
_cf1_pin = cf1_pin;
|
||||
_sel_pin = sel_pin;
|
||||
_current_mode = currentWhen;
|
||||
_use_interrupts = use_interrupts;
|
||||
_pulse_timeout = pulse_timeout;
|
||||
|
||||
pinMode(_cf_pin, INPUT_PULLUP);
|
||||
pinMode(_cf1_pin, INPUT_PULLUP);
|
||||
pinMode(_sel_pin, OUTPUT);
|
||||
|
||||
_calculateDefaultMultipliers();
|
||||
|
||||
_mode = _current_mode;
|
||||
digitalWrite(_sel_pin, _mode);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void BL0937::setMode(bl0937_mode_t mode) {
|
||||
_mode = (mode == MODE_CURRENT) ? _current_mode : 1 - _current_mode;
|
||||
digitalWrite(_sel_pin, _mode);
|
||||
if (_use_interrupts) {
|
||||
_last_cf1_interrupt = _first_cf1_interrupt = micros();
|
||||
}
|
||||
}
|
||||
|
||||
bl0937_mode_t BL0937::getMode() {
|
||||
return (_mode == _current_mode) ? MODE_CURRENT : MODE_VOLTAGE;
|
||||
}
|
||||
|
||||
bl0937_mode_t BL0937::toggleMode() {
|
||||
bl0937_mode_t new_mode = getMode() == MODE_CURRENT ? MODE_VOLTAGE : MODE_CURRENT;
|
||||
setMode(new_mode);
|
||||
return new_mode;
|
||||
}
|
||||
|
||||
double BL0937::getCurrent() {
|
||||
|
||||
// Power measurements are more sensitive to switch offs,
|
||||
// so we first check if power is 0 to set _current to 0 too
|
||||
if (_power == 0) {
|
||||
_current_pulse_width = 0;
|
||||
|
||||
} else if (_use_interrupts) {
|
||||
_checkCF1Signal();
|
||||
|
||||
} else if (_mode == _current_mode) {
|
||||
_current_pulse_width = pulseIn(_cf1_pin, HIGH, _pulse_timeout);
|
||||
}
|
||||
|
||||
_current = (_current_pulse_width > 0) ? _current_multiplier / _current_pulse_width : 0;
|
||||
return _current;
|
||||
|
||||
}
|
||||
|
||||
unsigned int BL0937::getVoltage() {
|
||||
if (_use_interrupts) {
|
||||
_checkCF1Signal();
|
||||
} else if (_mode != _current_mode) {
|
||||
_voltage_pulse_width = pulseIn(_cf1_pin, HIGH, _pulse_timeout);
|
||||
}
|
||||
_voltage = (_voltage_pulse_width > 0) ? _voltage_multiplier / _voltage_pulse_width : 0;
|
||||
return _voltage;
|
||||
}
|
||||
|
||||
unsigned int BL0937::getActivePower() {
|
||||
if (_use_interrupts) {
|
||||
_checkCFSignal();
|
||||
} else {
|
||||
_power_pulse_width = pulseIn(_cf_pin, HIGH, _pulse_timeout);
|
||||
}
|
||||
_power = (_power_pulse_width > 0) ? _power_multiplier / _power_pulse_width : 0;
|
||||
return _power;
|
||||
}
|
||||
|
||||
unsigned int BL0937::getApparentPower() {
|
||||
double current = getCurrent();
|
||||
unsigned int voltage = getVoltage();
|
||||
return voltage * current;
|
||||
}
|
||||
|
||||
unsigned int BL0937::getReactivePower() {
|
||||
unsigned int active = getActivePower();
|
||||
unsigned int apparent = getApparentPower();
|
||||
if (apparent > active) {
|
||||
return sqrt(apparent * apparent - active * active);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
double BL0937::getPowerFactor() {
|
||||
unsigned int active = getActivePower();
|
||||
unsigned int apparent = getApparentPower();
|
||||
if (active > apparent) return 1;
|
||||
if (apparent == 0) return 0;
|
||||
return (double) active / apparent;
|
||||
}
|
||||
|
||||
unsigned long BL0937::getEnergy() {
|
||||
|
||||
// Counting pulses only works in IRQ mode
|
||||
if (!_use_interrupts) return 0;
|
||||
|
||||
/*
|
||||
Pulse count is directly proportional to energy:
|
||||
P = m*f (m=power multiplier, f = Frequency)
|
||||
f = N/t (N=pulse count, t = time)
|
||||
E = P*t = m*N (E=energy)
|
||||
*/
|
||||
return _pulse_count * _power_multiplier / 1000000.0;
|
||||
|
||||
}
|
||||
|
||||
void BL0937::resetEnergy() {
|
||||
_pulse_count = 0;
|
||||
}
|
||||
|
||||
void BL0937::expectedCurrent(double value) {
|
||||
if (_current == 0) getCurrent();
|
||||
if (_current > 0) _current_multiplier *= (value / _current);
|
||||
}
|
||||
|
||||
void BL0937::expectedVoltage(unsigned int value) {
|
||||
if (_voltage == 0) getVoltage();
|
||||
if (_voltage > 0) _voltage_multiplier *= ((double) value / _voltage);
|
||||
}
|
||||
|
||||
void BL0937::expectedActivePower(unsigned int value) {
|
||||
if (_power == 0) getActivePower();
|
||||
if (_power > 0) _power_multiplier *= ((double) value / _power);
|
||||
}
|
||||
|
||||
void BL0937::resetMultipliers() {
|
||||
_calculateDefaultMultipliers();
|
||||
}
|
||||
|
||||
void BL0937::setResistors(double current, double voltage_upstream, double voltage_downstream) {
|
||||
if (voltage_downstream > 0) {
|
||||
_current_resistor = current;
|
||||
_voltage_resistor = (voltage_upstream + voltage_downstream) / voltage_downstream;
|
||||
_calculateDefaultMultipliers();
|
||||
}
|
||||
}
|
||||
|
||||
void ICACHE_RAM_ATTR BL0937::cf_interrupt() {
|
||||
unsigned long now = micros();
|
||||
_power_pulse_width = now - _last_cf_interrupt;
|
||||
_last_cf_interrupt = now;
|
||||
_pulse_count++;
|
||||
}
|
||||
|
||||
void ICACHE_RAM_ATTR BL0937::cf1_interrupt() {
|
||||
|
||||
unsigned long now = micros();
|
||||
|
||||
if ((now - _first_cf1_interrupt) > _pulse_timeout) {
|
||||
|
||||
unsigned long pulse_width;
|
||||
|
||||
if (_last_cf1_interrupt == _first_cf1_interrupt) {
|
||||
pulse_width = 0;
|
||||
} else {
|
||||
pulse_width = now - _last_cf1_interrupt;
|
||||
}
|
||||
|
||||
if (_mode == _current_mode) {
|
||||
_current_pulse_width = pulse_width;
|
||||
} else {
|
||||
_voltage_pulse_width = pulse_width;
|
||||
}
|
||||
|
||||
_mode = 1 - _mode;
|
||||
digitalWrite(_sel_pin, _mode);
|
||||
_first_cf1_interrupt = now;
|
||||
|
||||
}
|
||||
|
||||
_last_cf1_interrupt = now;
|
||||
|
||||
}
|
||||
|
||||
void BL0937::_checkCFSignal() {
|
||||
if ((micros() - _last_cf_interrupt) > _pulse_timeout) _power_pulse_width = 0;
|
||||
}
|
||||
|
||||
void BL0937::_checkCF1Signal() {
|
||||
if ((micros() - _last_cf1_interrupt) > _pulse_timeout) {
|
||||
if (_mode == _current_mode) {
|
||||
_current_pulse_width = 0;
|
||||
} else {
|
||||
_voltage_pulse_width = 0;
|
||||
}
|
||||
toggleMode();
|
||||
}
|
||||
}
|
||||
|
||||
// These are the multipliers for current, voltage and power as per datasheet
|
||||
// These values divided by output period (in useconds) give the actual value
|
||||
void BL0937::_calculateDefaultMultipliers() {
|
||||
_current_multiplier = ( 1000000.0 * 512 * V_REF / _current_resistor / 24.0 / F_OSC ) * 0.850112464f;
|
||||
_voltage_multiplier = ( 1000000.0 * 512 * V_REF * _voltage_resistor / 2.0 / F_OSC) * 0.863158011f;
|
||||
_power_multiplier = ( 1000000.0 * 128 * V_REF * V_REF * _voltage_resistor / _current_resistor / 48.0 / F_OSC) * 0.713465334f;
|
||||
}
|
||||
146
src/modules/sensors/BL0937/BL0937lib.h
Normal file
146
src/modules/sensors/BL0937/BL0937lib.h
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
|
||||
BL0937
|
||||
|
||||
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
|
||||
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/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef BL0937_h
|
||||
#define BL0937_h
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// Internal voltage reference value
|
||||
#define V_REF 1.218
|
||||
|
||||
// The factor of a 1mOhm resistor
|
||||
// as per recomended circuit in datasheet
|
||||
// A 1mOhm resistor allows a ~30A max measurement
|
||||
#define R_CURRENT 0.001
|
||||
|
||||
// This is the factor of a voltage divider of 1MOhm upstream and 1kOhm downstream
|
||||
// as per recomended circuit in datasheet
|
||||
#define R_VOLTAGE 1000
|
||||
|
||||
// Frequency of the BL0937 internal clock
|
||||
#define F_OSC 2000000
|
||||
|
||||
// Minimum delay between selecting a mode and reading a sample
|
||||
#define READING_INTERVAL 3000
|
||||
|
||||
// Maximum pulse with in microseconds
|
||||
// If longer than this pulse width is reset to 0
|
||||
// This value is purely experimental.
|
||||
// Higher values allow for a better precission but reduce sampling rate
|
||||
// and response speed to change
|
||||
// Lower values increase sampling rate but reduce precission
|
||||
// Values below 0.5s are not recommended since current and voltage output
|
||||
// will have no time to stabilise
|
||||
#define PULSE_TIMEOUT 5000000
|
||||
|
||||
// Define ICACHE_RAM_ATTR for AVR platforms
|
||||
#if defined(ARDUINO_ARCH_AVR)
|
||||
#define ICACHE_RAM_ATTR
|
||||
#endif
|
||||
|
||||
// CF1 mode
|
||||
typedef enum {
|
||||
MODE_CURRENT,
|
||||
MODE_VOLTAGE
|
||||
} bl0937_mode_t;
|
||||
|
||||
class BL0937 {
|
||||
|
||||
public:
|
||||
|
||||
void cf_interrupt();
|
||||
void cf1_interrupt();
|
||||
|
||||
void begin(
|
||||
unsigned char cf_pin,
|
||||
unsigned char cf1_pin,
|
||||
unsigned char sel_pin,
|
||||
unsigned char currentWhen = HIGH,
|
||||
bool use_interrupts = true,
|
||||
unsigned long pulse_timeout = PULSE_TIMEOUT);
|
||||
|
||||
void setMode(bl0937_mode_t mode);
|
||||
bl0937_mode_t getMode();
|
||||
bl0937_mode_t toggleMode();
|
||||
|
||||
double getCurrent();
|
||||
unsigned int getVoltage();
|
||||
unsigned int getActivePower();
|
||||
unsigned int getApparentPower();
|
||||
double getPowerFactor();
|
||||
unsigned int getReactivePower();
|
||||
unsigned long getEnergy(); //in Ws
|
||||
void resetEnergy();
|
||||
|
||||
void setResistors(double current, double voltage_upstream, double voltage_downstream);
|
||||
|
||||
void expectedCurrent(double current);
|
||||
void expectedVoltage(unsigned int current);
|
||||
void expectedActivePower(unsigned int power);
|
||||
|
||||
double getCurrentMultiplier() { return _current_multiplier; };
|
||||
double getVoltageMultiplier() { return _voltage_multiplier; };
|
||||
double getPowerMultiplier() { return _power_multiplier; };
|
||||
|
||||
void setCurrentMultiplier(double current_multiplier) { _current_multiplier = current_multiplier; };
|
||||
void setVoltageMultiplier(double voltage_multiplier) { _voltage_multiplier = voltage_multiplier; };
|
||||
void setPowerMultiplier(double power_multiplier) { _power_multiplier = power_multiplier; };
|
||||
void resetMultipliers();
|
||||
|
||||
private:
|
||||
|
||||
unsigned char _cf_pin;
|
||||
unsigned char _cf1_pin;
|
||||
unsigned char _sel_pin;
|
||||
|
||||
double _current_resistor = R_CURRENT;
|
||||
double _voltage_resistor = R_VOLTAGE;
|
||||
|
||||
double _current_multiplier; // Unit: us/A
|
||||
double _voltage_multiplier; // Unit: us/V
|
||||
double _power_multiplier; // Unit: us/W
|
||||
|
||||
unsigned long _pulse_timeout = PULSE_TIMEOUT; //Unit: us
|
||||
volatile unsigned long _voltage_pulse_width = 0; //Unit: us
|
||||
volatile unsigned long _current_pulse_width = 0; //Unit: us
|
||||
volatile unsigned long _power_pulse_width = 0; //Unit: us
|
||||
volatile unsigned long _pulse_count = 0;
|
||||
|
||||
double _current = 0;
|
||||
unsigned int _voltage = 0;
|
||||
unsigned int _power = 0;
|
||||
|
||||
unsigned char _current_mode = HIGH;
|
||||
volatile unsigned char _mode;
|
||||
|
||||
bool _use_interrupts = true;
|
||||
volatile unsigned long _last_cf_interrupt = 0;
|
||||
volatile unsigned long _last_cf1_interrupt = 0;
|
||||
volatile unsigned long _first_cf1_interrupt = 0;
|
||||
|
||||
void _checkCFSignal();
|
||||
void _checkCF1Signal();
|
||||
void _calculateDefaultMultipliers();
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
129
src/modules/sensors/BL0937/modinfo.json
Normal file
129
src/modules/sensors/BL0937/modinfo.json
Normal file
@@ -0,0 +1,129 @@
|
||||
{
|
||||
"menuSection": "sensors",
|
||||
"configItem": [
|
||||
{
|
||||
"global": 0,
|
||||
"name": "BL0937 Напряжение",
|
||||
"type": "Reading",
|
||||
"subtype": "BL0937v",
|
||||
"id": "bl_v",
|
||||
"widget": "anydataVlt",
|
||||
"page": "BL0937",
|
||||
"descr": "Напряжение",
|
||||
"int": 15,
|
||||
"round": 1
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"name": "BL0937 Сила тока",
|
||||
"type": "Reading",
|
||||
"subtype": "BL0937a",
|
||||
"id": "bl_a",
|
||||
"widget": "anydataAmp",
|
||||
"page": "BL0937",
|
||||
"descr": "Сила тока",
|
||||
"int": 15,
|
||||
"round": 1
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"name": "BL0937 Мощность",
|
||||
"type": "Reading",
|
||||
"subtype": "BL0937w",
|
||||
"id": "bl_w",
|
||||
"widget": "anydataWt",
|
||||
"page": "BL0937",
|
||||
"descr": "Мощность",
|
||||
"int": 15,
|
||||
"round": 1
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"name": "BL0937 Реакт.Мощность",
|
||||
"type": "Reading",
|
||||
"subtype": "BL0937reactw",
|
||||
"id": "bl_w",
|
||||
"widget": "anydataWt",
|
||||
"page": "BL0937",
|
||||
"descr": "Реакт.Мощность",
|
||||
"int": 15,
|
||||
"round": 1
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"name": "BL0937 Активн.Мощность",
|
||||
"type": "Reading",
|
||||
"subtype": "BL0937actw",
|
||||
"id": "bl_actw",
|
||||
"widget": "anydataWt",
|
||||
"page": "BL0937",
|
||||
"descr": "Актив.Мощность",
|
||||
"int": 15,
|
||||
"round": 1
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"name": "BL0937 Энергия",
|
||||
"type": "Reading",
|
||||
"subtype": "BL0937wh",
|
||||
"id": "bl_wh",
|
||||
"widget": "anydataWth",
|
||||
"page": "BL0937",
|
||||
"descr": "Энергия",
|
||||
"int": 15,
|
||||
"round": 1
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"name": "BL0937 настройка",
|
||||
"type": "Reading",
|
||||
"subtype": "BL0937cmd",
|
||||
"id": "bl_set",
|
||||
"widget": "nil",
|
||||
"page": "",
|
||||
"descr": "",
|
||||
"btn-reset": "",
|
||||
"R_current": 0.001,
|
||||
"R_upstream": 1000000,
|
||||
"R_downstream": 1000,
|
||||
"CF_GPIO": 4,
|
||||
"CF1_GPIO": 5,
|
||||
"SEL_GPIO": 12,
|
||||
"expV": 0,
|
||||
"expA": 0,
|
||||
"expW": 0
|
||||
}
|
||||
],
|
||||
"about": {
|
||||
"authorName": "Bubnov Mikhail",
|
||||
"authorContact": "https://t.me/Mit4bmw",
|
||||
"authorGit": "https://github.com/Mit4el",
|
||||
"specialThanks": "",
|
||||
"moduleName": "BL0937",
|
||||
"moduleVersion": "1.0",
|
||||
"usedRam": {
|
||||
"esp32_4mb": 15,
|
||||
"esp8266_4mb": 15
|
||||
},
|
||||
"title": "Счетчик электроэнергии BL0937",
|
||||
"moduleDesc": "Считает потраченную электроэнергию, измеряет напряжение, силу тока и прочие параметры.",
|
||||
"propInfo": {
|
||||
"int": "Количество секунд между опросами датчика.",
|
||||
"btn-reset": "Энергия BL0937 будет сброшена к нулю.",
|
||||
"R_current": "Резистор подключенный последовательно к основной линии",
|
||||
"R_upstream": "это 5 резисторов по 470 Ком в делителе напряжения, который питает вывод V2P",
|
||||
"R_downstream": "это резистор емкостью 1 Ком в делителе напряжения, который питает вывод V2P",
|
||||
"CF_GPIO": "пин CF",
|
||||
"CF1_GPIO": "пин CF1",
|
||||
"SEL_GPIO": "пин SEL",
|
||||
"expV": "реальное напряжение, указать для калибровки",
|
||||
"expA": "реальный ток, указать для калибровки",
|
||||
"expW": "реальная мощность, указать для калибровки"
|
||||
}
|
||||
},
|
||||
"defActive": true,
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
}
|
||||
}
|
||||
48
src/modules/sensors/EnergyMon485/Energy485Header.h
Normal file
48
src/modules/sensors/EnergyMon485/Energy485Header.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
namespace _Electro485
|
||||
{
|
||||
|
||||
unsigned char mass_to_send[16];
|
||||
unsigned char mass_read[16];
|
||||
unsigned short int checkSum;
|
||||
|
||||
unsigned char auchCRCLo[] = {
|
||||
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
|
||||
0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
|
||||
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
|
||||
0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
|
||||
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
|
||||
0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
|
||||
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
|
||||
0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
|
||||
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
|
||||
0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
|
||||
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
|
||||
0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
|
||||
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
|
||||
0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
|
||||
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
|
||||
0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
|
||||
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
|
||||
0x40};
|
||||
unsigned char auchCRCHi[] = {
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
|
||||
0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
||||
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
|
||||
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
|
||||
0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
|
||||
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
|
||||
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
|
||||
0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
||||
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
|
||||
0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
|
||||
0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
||||
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
|
||||
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
|
||||
0x40};
|
||||
}
|
||||
342
src/modules/sensors/EnergyMon485/EnergyMon485.cpp
Normal file
342
src/modules/sensors/EnergyMon485/EnergyMon485.cpp
Normal file
@@ -0,0 +1,342 @@
|
||||
|
||||
#include "Global.h"
|
||||
#include "classes/IoTItem.h"
|
||||
|
||||
#include "classes/IoTUart.h"
|
||||
#include "Energy485Header.h"
|
||||
|
||||
// PZEMContainer _pzemCntr;
|
||||
Stream *_myUART_Gran = nullptr;
|
||||
|
||||
namespace _Electro485
|
||||
{
|
||||
|
||||
unsigned short int sdm120_mbCRC16(unsigned char *puchMsg, unsigned char usDataLen)
|
||||
{
|
||||
unsigned char uchCRCHi = 0xFF; /* инициализация старшего байта контрольной суммы */
|
||||
unsigned char uchCRCLo = 0xFF; /* инициализация младшего байта контрольной суммы */
|
||||
unsigned char uIndex;
|
||||
|
||||
uchCRCHi = 0xFF;
|
||||
uchCRCLo = 0xFF;
|
||||
|
||||
while (usDataLen--)
|
||||
{
|
||||
uIndex = uchCRCHi ^ *puchMsg++;
|
||||
uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex];
|
||||
uchCRCLo = auchCRCLo[uIndex];
|
||||
}
|
||||
|
||||
return (uchCRCLo << 8 | uchCRCHi);
|
||||
}
|
||||
|
||||
unsigned short int gran485_mbCRC16(unsigned char *puchMsg, unsigned char usDataLen)
|
||||
{
|
||||
unsigned char uchCRCHi = 0xFF; /* инициализация старшего байта контрольной суммы */
|
||||
unsigned char uchCRCLo = 0xFF; /* инициализация младшего байта контрольной суммы */
|
||||
unsigned char uIndex;
|
||||
|
||||
uchCRCHi = 0xFF;
|
||||
uchCRCLo = 0xFF;
|
||||
|
||||
while (usDataLen--)
|
||||
{
|
||||
uIndex = (uchCRCHi ^ *puchMsg++) & 0xFF;
|
||||
uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex];
|
||||
uchCRCLo = auchCRCLo[uIndex];
|
||||
}
|
||||
|
||||
return (uchCRCHi << 8 | uchCRCLo);
|
||||
}
|
||||
|
||||
void uart485_send(unsigned char *str, unsigned char bytes_to_send)
|
||||
{
|
||||
// delay(20);
|
||||
if (_myUART_Gran)
|
||||
{
|
||||
for (int i = 0; i < bytes_to_send; i++)
|
||||
{
|
||||
_myUART_Gran->write(str[i]);
|
||||
}
|
||||
}
|
||||
// delay(100);
|
||||
}
|
||||
|
||||
int8_t gran485_read(byte addr, String ¶m, float &res)
|
||||
{
|
||||
int8_t ret;
|
||||
if (!_myUART_Gran)
|
||||
return 3;
|
||||
unsigned short int param_hex;
|
||||
mass_to_send[1] = 0x03;
|
||||
mass_to_send[4] = 0x00;
|
||||
if (param == "v")
|
||||
param_hex = 0x0A00;
|
||||
else if (param == "a")
|
||||
param_hex = 0x0B00;
|
||||
else if (param == "w")
|
||||
param_hex = 0x0800;
|
||||
else if (param == "r")
|
||||
param_hex = 0x0900;
|
||||
else if (param == "f")
|
||||
param_hex = 0x0D00;
|
||||
else if (param == "k")
|
||||
{
|
||||
param_hex = 0x0100;
|
||||
mass_to_send[1] = 0x04;
|
||||
mass_to_send[4] = 0x01;
|
||||
}
|
||||
else if (param == "p")
|
||||
{
|
||||
param_hex = 0x0C00;
|
||||
}
|
||||
SerialPrint("i", "Gran", "param: " + param + ", param_hex: " + String(param_hex, HEX));
|
||||
mass_to_send[0] = addr;
|
||||
// mass_to_send[1] = 0x03;
|
||||
mass_to_send[2] = param_hex >> 8;
|
||||
mass_to_send[3] = param_hex;
|
||||
|
||||
mass_to_send[5] = 0x01;
|
||||
checkSum = gran485_mbCRC16(mass_to_send, 6);
|
||||
mass_to_send[6] = checkSum;
|
||||
mass_to_send[7] = checkSum >> 8;
|
||||
uart485_send(mass_to_send, 8);
|
||||
|
||||
// Считываем первые 3 байта из ответа
|
||||
int s = _myUART_Gran->readBytes(mass_read, 10);
|
||||
// uart_send(mass_read, 3);
|
||||
// Serial.println("Count read byte: " + String(s));
|
||||
|
||||
// Если вернулся правильный адрес и команда
|
||||
if (mass_read[0] == addr && mass_read[1] == mass_to_send[1])
|
||||
{
|
||||
// Проверяем контрольную сумму
|
||||
checkSum = 0;
|
||||
checkSum = gran485_mbCRC16(mass_read, 8);
|
||||
/*
|
||||
Serial.print("HEX: ");
|
||||
Serial.print(String(mass_read[4], HEX));
|
||||
Serial.print(String(mass_read[5], HEX));
|
||||
Serial.print(String(mass_read[6], HEX));
|
||||
Serial.println(String(mass_read[7], HEX));
|
||||
*/
|
||||
uint32_t x = mass_read[7] << 24 | mass_read[6] << 16 | mass_read[5] << 8 | mass_read[4];
|
||||
float y = *(float *)&x;
|
||||
Serial.println(String(y));
|
||||
if ((byte)checkSum == mass_read[8] && (checkSum >> 8) == mass_read[9])
|
||||
{
|
||||
res = y;
|
||||
ret = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Serial.println("ERROR_CRC");
|
||||
ret = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Serial.println("ERROR_NO_RESPONSE");
|
||||
ret = 2;
|
||||
}
|
||||
|
||||
// очистка массива
|
||||
mass_read[0] = 0;
|
||||
mass_read[1] = 0;
|
||||
mass_read[2] = 0;
|
||||
mass_read[3] = 0;
|
||||
mass_read[4] = 0;
|
||||
mass_read[5] = 0;
|
||||
mass_read[6] = 0;
|
||||
// очистка буфера serial
|
||||
while (_myUART_Gran->available())
|
||||
_myUART_Gran->read();
|
||||
return ret;
|
||||
}
|
||||
|
||||
int8_t sdm120_read(byte addr, String ¶m, float &res)
|
||||
{
|
||||
int8_t ret;
|
||||
if (!_myUART_Gran)
|
||||
return 3;
|
||||
unsigned short int param_hex;
|
||||
if (param == "v")
|
||||
param_hex = 0x0000;
|
||||
else if (param == "a")
|
||||
param_hex = 0x0006;
|
||||
else if (param == "w")
|
||||
param_hex = 0x000C;
|
||||
else if (param == "r")
|
||||
param_hex = 0x0018;
|
||||
else if (param == "f")
|
||||
param_hex = 0x0046;
|
||||
else if (param == "k")
|
||||
param_hex = 0x0156;
|
||||
else if (param == "p")
|
||||
param_hex = 0x001E;
|
||||
SerialPrint("i", "SMD120", "param: " + param + ", param_hex: " + String(param_hex, HEX));
|
||||
|
||||
mass_to_send[0] = addr;
|
||||
mass_to_send[1] = 0x04;
|
||||
mass_to_send[2] = param_hex >> 8;
|
||||
mass_to_send[3] = param_hex;
|
||||
mass_to_send[4] = 0x00;
|
||||
mass_to_send[5] = 0x02;
|
||||
checkSum = sdm120_mbCRC16(mass_to_send, 6);
|
||||
mass_to_send[6] = checkSum;
|
||||
mass_to_send[7] = checkSum >> 8;
|
||||
uart485_send(mass_to_send, 8);
|
||||
|
||||
// Считываем первые 3 байта из ответа
|
||||
_myUART_Gran->readBytes(mass_read, 3);
|
||||
// uart_send(mass_read, 3);
|
||||
|
||||
// Если вернулся правильный адрес и команда
|
||||
if (mass_read[0] == addr && mass_read[1] == 0x04)
|
||||
{
|
||||
// то считываем данные (их количество было в тетьем байте) +2 байта на контрольную сумму +3 байта чтобы не затереть начало массива
|
||||
for (int i = 3; i < mass_read[2] + 5; i++)
|
||||
{
|
||||
mass_read[i] = _myUART_Gran->read();
|
||||
}
|
||||
|
||||
// Проверяем контрольную сумму
|
||||
checkSum = 0;
|
||||
checkSum = sdm120_mbCRC16(mass_read, mass_read[2] + 3);
|
||||
if ((byte)checkSum == mass_read[mass_read[2] + 3] && (checkSum >> 8) == mass_read[mass_read[2] + 4])
|
||||
{
|
||||
// преобразуем результат во float
|
||||
|
||||
uint32_t x = mass_read[3] << 24 | mass_read[4] << 16 | mass_read[5] << 8 | mass_read[6];
|
||||
float y = *(float *)&x;
|
||||
res = y;
|
||||
|
||||
ret = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Serial.println("ERROR_CRC");
|
||||
ret = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Serial.println("ERROR_NO_RESPONSE");
|
||||
ret = 2;
|
||||
}
|
||||
|
||||
// очистка массива
|
||||
mass_read[0] = 0;
|
||||
mass_read[1] = 0;
|
||||
mass_read[2] = 0;
|
||||
mass_read[3] = 0;
|
||||
mass_read[4] = 0;
|
||||
mass_read[5] = 0;
|
||||
mass_read[6] = 0;
|
||||
|
||||
// очистка буфера serial
|
||||
while (_myUART_Gran->available())
|
||||
_myUART_Gran->read();
|
||||
return ret;
|
||||
}
|
||||
|
||||
class GranItem : public IoTItem
|
||||
{
|
||||
private:
|
||||
String _sensor;
|
||||
|
||||
public:
|
||||
GranItem(String parameters) : IoTItem(parameters)
|
||||
{
|
||||
_sensor = jsonReadStr(parameters, "sensor");
|
||||
}
|
||||
|
||||
void doByInterval()
|
||||
{
|
||||
|
||||
byte addr = 00;
|
||||
float val = 0;
|
||||
int8_t result = gran485_read(addr, _sensor, val);
|
||||
|
||||
if (result == 0) // OK
|
||||
regEvent(val, "Gran");
|
||||
else
|
||||
{
|
||||
regEvent(NAN, "Gran");
|
||||
if (result == 1) // ERROR_CRC
|
||||
SerialPrint("E", "Gran", "ERROR_CRC", _id);
|
||||
else if (result == 2) // ERROR_NO_RESPONSE
|
||||
SerialPrint("E", "Gran", "ERROR_NO_RESPONSE", _id);
|
||||
else if (result == 3) // (!_myUART_Gran)
|
||||
SerialPrint("E", "Gran", "gran_uart not found", _id);
|
||||
}
|
||||
}
|
||||
|
||||
~GranItem(){};
|
||||
};
|
||||
|
||||
class SDM120Item : public IoTItem
|
||||
{
|
||||
private:
|
||||
String _sensor;
|
||||
|
||||
public:
|
||||
SDM120Item(String parameters) : IoTItem(parameters)
|
||||
{
|
||||
_sensor = jsonReadStr(parameters, "sensor");
|
||||
}
|
||||
|
||||
void doByInterval()
|
||||
{
|
||||
|
||||
byte addr = 00;
|
||||
float val = 0;
|
||||
int8_t result = sdm120_read(addr, _sensor, val);
|
||||
|
||||
if (result == 0) // OK
|
||||
regEvent(val, "SDM120");
|
||||
else
|
||||
{
|
||||
regEvent(NAN, "SDM120");
|
||||
if (result == 1) // ERROR_CRC
|
||||
SerialPrint("E", "SDM120", "ERROR_CRC", _id);
|
||||
else if (result == 2) // ERROR_NO_RESPONSE
|
||||
SerialPrint("E", "SDM120", "ERROR_NO_RESPONSE", _id);
|
||||
else if (result == 3) // (!_myUART_Gran)
|
||||
SerialPrint("E", "SDM120", "gran_uart not found", _id);
|
||||
}
|
||||
}
|
||||
|
||||
~SDM120Item(){};
|
||||
};
|
||||
|
||||
class EnergyUART : public IoTUart
|
||||
{
|
||||
public:
|
||||
EnergyUART(String parameters) : IoTUart(parameters)
|
||||
{
|
||||
_myUART_Gran = _myUART;
|
||||
}
|
||||
|
||||
~EnergyUART(){};
|
||||
};
|
||||
}
|
||||
void *getAPI_EnergyMon485(String subtype, String param)
|
||||
{
|
||||
if (subtype == F("gran485"))
|
||||
{
|
||||
return new _Electro485::GranItem(param);
|
||||
}
|
||||
else if (subtype == F("sdm120"))
|
||||
{
|
||||
return new _Electro485::SDM120Item(param);
|
||||
}
|
||||
else if (subtype == F("energy_uart"))
|
||||
{
|
||||
return new _Electro485::EnergyUART(param);
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
83
src/modules/sensors/EnergyMon485/modinfo.json
Normal file
83
src/modules/sensors/EnergyMon485/modinfo.json
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"menuSection": "sensors",
|
||||
"configItem": [
|
||||
{
|
||||
"global": 0,
|
||||
"name": "Gran данные",
|
||||
"type": "Reading",
|
||||
"subtype": "gran485",
|
||||
"id": "gran",
|
||||
"widget": "anydataVlt",
|
||||
"page": "Гран-Электро",
|
||||
"descr": "Потребление",
|
||||
"sensor": "k",
|
||||
"int": 15,
|
||||
"round": 1
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"name": "SDM120 данные",
|
||||
"type": "Reading",
|
||||
"subtype": "sdm120",
|
||||
"id": "sdm120",
|
||||
"widget": "anydataVlt",
|
||||
"page": "Счётчик SDM120",
|
||||
"descr": "Потребление",
|
||||
"sensor": "k",
|
||||
"int": 15,
|
||||
"round": 1
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"name": "Energy UART",
|
||||
"type": "Reading",
|
||||
"subtype": "energy_uart",
|
||||
"id": "enrg_uart",
|
||||
"widget": "nil",
|
||||
"page": "",
|
||||
"descr": "",
|
||||
"tx": 17,
|
||||
"rx": 16,
|
||||
"line": 2,
|
||||
"speed": 9600
|
||||
}
|
||||
],
|
||||
"about": {
|
||||
"authorName": "Bubnov Mikhail",
|
||||
"authorContact": "https://t.me/Mit4bmw",
|
||||
"authorGit": "https://github.com/Mit4el",
|
||||
"specialThanks": "",
|
||||
"moduleName": "EnergyMon485",
|
||||
"moduleVersion": "1.0",
|
||||
"usedRam": {
|
||||
"esp32_4mb": 15,
|
||||
"esp8266_4mb": 15
|
||||
},
|
||||
"title": "Счетчик электроэнергии Гран-Электро или SDM120",
|
||||
"moduleDesc": "Счетчик электроэнергии Гран-Электро (Беларусь) или SDM120 (Китай) с интерфейсом rs-485. Energy UART - обязателен, настройки UART интерфейса модуля",
|
||||
"propInfo": {
|
||||
"tx": "TX пин",
|
||||
"rx": "RX пин",
|
||||
"speed": "Скорость UART",
|
||||
"line": "Актуально только для ESP32: номер линии hardUART. =2 rx=16 tx=17, для SoftwarwSerial в ESP32 line = -1",
|
||||
"sensor": "Тип данных: v - напряжение, a - ток, w - активная мощность, r - реактивная мощность, f - частота, k - общее потребление, p - косинус фи",
|
||||
"int": "Количество секунд между опросами датчика. Желателно устанавливать одинаковые интервалы для параметров (для одного адреса Pzem) что опрос происходил один раз, остальные из 1000мс буфера."
|
||||
}
|
||||
},
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": [],
|
||||
"esp32_4mb": [],
|
||||
"esp32_4mb3f": [],
|
||||
"esp32cam_4mb": [],
|
||||
"esp32c3m_4mb": [],
|
||||
"esp8266_4mb": [],
|
||||
"esp8266_1mb": [],
|
||||
"esp8266_1mb_ota": [],
|
||||
"esp8285_1mb": [],
|
||||
"esp8285_1mb_ota": [],
|
||||
"esp8266_2mb": [],
|
||||
"esp8266_2mb_ota": []
|
||||
}
|
||||
}
|
||||
165
src/modules/virtual/Benchmark/Benchmark.cpp
Normal file
165
src/modules/virtual/Benchmark/Benchmark.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
#include "Global.h"
|
||||
#include "classes/IoTBench.h"
|
||||
#include <ArduinoJson.h>
|
||||
// #include <map>
|
||||
|
||||
class BenchmarkLoad : public IoTBench
|
||||
{
|
||||
private:
|
||||
bool _log = false;
|
||||
uint32_t _loadP = 1; // период подсчета загруженности процессора
|
||||
|
||||
uint32_t startLoad = 0; // время начало цикла loop
|
||||
uint32_t loadPrev = 0; // время предыдущего подсчета benchmark
|
||||
uint32_t loadSum = 0; // время выполнния всех циклов loop за период _loadP
|
||||
float load = 0; // загруженность процессора в процентах за период _loadP (loadSum / 1000) / _loadP * 100
|
||||
uint32_t count = 0; // количестов циклов loop в сек в среднем за период _loadP
|
||||
|
||||
public:
|
||||
BenchmarkLoad(String parameters) : IoTBench(parameters)
|
||||
{
|
||||
// jsonRead(parameters, "log", _log);
|
||||
// jsonRead(parameters, "int", _loadP); // в минутах
|
||||
_loadP = _interval ; //* 1000
|
||||
// SerialPrint("i", "Benchmark",
|
||||
// "_interval: " + String(_interval) + " _loadP: " + String(_loadP));
|
||||
if (_loadP < 10000)
|
||||
_loadP = 10000;
|
||||
}
|
||||
|
||||
void doByInterval()
|
||||
{
|
||||
printBenchmarkLoad();
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
count++;
|
||||
IoTItem::loop();
|
||||
}
|
||||
|
||||
void preLoadFunction()
|
||||
{
|
||||
startLoad = micros(); // время начала выполнения одного цикла
|
||||
}
|
||||
void postLoadFunction()
|
||||
{
|
||||
loadSum += (micros() - startLoad); // высчитываем время выполнения одного цикла (после нагрузки) и прибавляем к сумме за вреям контроля _loadP
|
||||
}
|
||||
|
||||
void printBenchmarkLoad()
|
||||
{
|
||||
load = (loadSum / 10ul) / _loadP; // (loadSum / 1000) / _loadP * 100
|
||||
|
||||
SerialPrint("i", "Benchmark",
|
||||
"CPU load time: " + String(loadSum) + "us, in RealTime: " + String((micros() - loadPrev)) + "us");
|
||||
SerialPrint("i", "Benchmark",
|
||||
"CPU load in " + String(_loadP) + "ms :" + String((load)) + "%" +
|
||||
" loop/sec: " + String(count / (_loadP / 1000)));
|
||||
loadPrev = micros(); //+= _loadP;
|
||||
loadSum = 0;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
IoTBench *getBenchmarkLoad()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
~BenchmarkLoad(){
|
||||
// clearBenchConfig();
|
||||
};
|
||||
};
|
||||
|
||||
class BenchmarkTask : public IoTBench
|
||||
{
|
||||
private:
|
||||
uint32_t _loadP = 1;
|
||||
bool _log = false;
|
||||
|
||||
public:
|
||||
BenchmarkTask(String parameters) : IoTBench(parameters)
|
||||
{
|
||||
// jsonRead(parameters, "log", _log);
|
||||
// jsonRead(parameters, "int", _loadP); // в минутах
|
||||
_loadP = _interval;// * 1000;
|
||||
if (_loadP < 10000)
|
||||
_loadP = 10000;
|
||||
}
|
||||
|
||||
void doByInterval()
|
||||
{
|
||||
printBenchmarkTask();
|
||||
}
|
||||
|
||||
void preTaskFunction(const String &id)
|
||||
{
|
||||
if (banchItems.find(id) != banchItems.end())
|
||||
{
|
||||
banchItems[id]->loopTime = micros(); // micros();
|
||||
}
|
||||
else
|
||||
{
|
||||
banchItems[id] = new ItemBench;
|
||||
banchItems[id]->loopTime = micros(); // micros();
|
||||
}
|
||||
}
|
||||
void postTaskFunction(const String &id)
|
||||
{
|
||||
if (banchItems.find(id) != banchItems.end())
|
||||
{
|
||||
banchItems[id]->loopTime = micros() - banchItems[id]->loopTime;
|
||||
banchItems[id]->sumloopTime += banchItems[id]->loopTime;
|
||||
if (banchItems[id]->loopTime > banchItems[id]->loopTimeMax_glob)
|
||||
banchItems[id]->loopTimeMax_glob = banchItems[id]->loopTime;
|
||||
if (banchItems[id]->loopTime > banchItems[id]->loopTimeMax_p)
|
||||
banchItems[id]->loopTimeMax_p = banchItems[id]->loopTime;
|
||||
}
|
||||
}
|
||||
|
||||
void printBenchmarkTask()
|
||||
{
|
||||
for (auto it = banchItems.begin(); it != banchItems.end(); it++)
|
||||
{
|
||||
SerialPrint(
|
||||
"i", "Benchmark",
|
||||
" load (" + String((float)(it->second)->sumloopTime / 10ul / _loadP) + "%) " +
|
||||
" max: per (" + String((it->second)->loopTimeMax_p) + "us)" +
|
||||
" glob (" + String((it->second)->loopTimeMax_glob) + "us) - " + it->first);
|
||||
(it->second)->sumloopTime = 0;
|
||||
(it->second)->loopTimeMax_p = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void clearBenchConfig()
|
||||
{
|
||||
for (auto it = banchItems.begin(); it != banchItems.end(); it++)
|
||||
{
|
||||
delete it->second;
|
||||
}
|
||||
banchItems.clear();
|
||||
}
|
||||
IoTBench *getBenchmarkTask()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
~BenchmarkTask()
|
||||
{
|
||||
clearBenchConfig();
|
||||
};
|
||||
};
|
||||
|
||||
void *getAPI_Benchmark(String subtype, String param)
|
||||
{
|
||||
if (subtype == F("loadBench"))
|
||||
{
|
||||
return new BenchmarkTask(param);
|
||||
}
|
||||
else if (subtype == F("taskBench"))
|
||||
{
|
||||
return new BenchmarkLoad(param);
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
57
src/modules/virtual/Benchmark/modinfo.json
Normal file
57
src/modules/virtual/Benchmark/modinfo.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"menuSection": "virtual_elments",
|
||||
|
||||
"configItem": [
|
||||
{
|
||||
"global": 0,
|
||||
"name": "Load Processor",
|
||||
"type": "Reading",
|
||||
"subtype": "loadBench",
|
||||
"id": "bench",
|
||||
"needSave": 0,
|
||||
"widget": "nil",
|
||||
"page": "Benchmark",
|
||||
"descr": "Загруженность процессора",
|
||||
"int": 10,
|
||||
"log": 1
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"name": "Load Task",
|
||||
"type": "Reading",
|
||||
"subtype": "taskBench",
|
||||
"id": "bench",
|
||||
"needSave": 0,
|
||||
"widget": "nil",
|
||||
"page": "Benchmark",
|
||||
"descr": "Загруженность задач",
|
||||
"int": 10,
|
||||
"log": 1
|
||||
}
|
||||
],
|
||||
|
||||
"about": {
|
||||
"authorName": "Mikhail Bubnov",
|
||||
"authorContact": "https://t.me/Mit4bmw",
|
||||
"authorGit": "https://github.com/Mit4el",
|
||||
"specialThanks": "",
|
||||
"moduleName": "Benchmark",
|
||||
"moduleVersion": "1.0",
|
||||
"usedRam": {
|
||||
"esp32_4mb": 15,
|
||||
"esp8266_4mb": 15
|
||||
},
|
||||
"title": "Производительонсть системы",
|
||||
"moduleDesc": "Оценочные показатели производительности системы и выполнения модулей",
|
||||
"propInfo": {
|
||||
"int": "Интервал подсчета загруженности процессора в секундах"
|
||||
}
|
||||
},
|
||||
|
||||
"defActive": false,
|
||||
|
||||
"usedLibs": {
|
||||
"esp32*": [],
|
||||
"esp82*": []
|
||||
}
|
||||
}
|
||||
240
src/modules/virtual/Ping/Ping.cpp
Normal file
240
src/modules/virtual/Ping/Ping.cpp
Normal file
@@ -0,0 +1,240 @@
|
||||
#include "Global.h"
|
||||
#include "classes/IoTItem.h"
|
||||
#include <ArduinoJson.h>
|
||||
#ifdef ESP32
|
||||
#include "sdkconfig.h"
|
||||
#include "lwip/inet.h"
|
||||
#include "lwip/netdb.h"
|
||||
#include "lwip/sockets.h"
|
||||
#include "argtable3/argtable3.h"
|
||||
#include "ping/ping_sock.h"
|
||||
|
||||
#endif
|
||||
#ifdef ESP8266
|
||||
#include <ESP8266Ping.h>
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
bool pingState = false;
|
||||
/*
|
||||
static struct
|
||||
{
|
||||
uint32_t timeout;
|
||||
uint32_t interval;
|
||||
uint32_t data_size;
|
||||
uint32_t count;
|
||||
uint32_t tos;
|
||||
uint32_t ttl;
|
||||
//String host;
|
||||
// arg_end *end;
|
||||
} ping_args;
|
||||
*/
|
||||
esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG();
|
||||
|
||||
static void cmd_ping_on_ping_success(esp_ping_handle_t hdl, void *args)
|
||||
{
|
||||
uint8_t ttl;
|
||||
uint16_t seqno;
|
||||
uint32_t elapsed_time, recv_len;
|
||||
ip_addr_t target_addr;
|
||||
esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
|
||||
esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl));
|
||||
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
|
||||
esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len));
|
||||
esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time));
|
||||
// Serial.printf("%" PRIu32 " bytes from %s icmp_seq=%" PRIu16 " ttl=%" PRIu16 " time=%" PRIu32 " ms\n",
|
||||
// recv_len, ipaddr_ntoa((ip_addr_t*)&target_addr), seqno, ttl, elapsed_time);
|
||||
SerialPrint("i", "Ping", String(recv_len) + " bytes from " + String(ipaddr_ntoa((ip_addr_t *)&target_addr)) + " icmp_seq=" + String(seqno) + " ttl=" + String(ttl) + " time=" + String(elapsed_time) + " ms");
|
||||
pingState = true;
|
||||
}
|
||||
|
||||
static void cmd_ping_on_ping_timeout(esp_ping_handle_t hdl, void *args)
|
||||
{
|
||||
uint16_t seqno;
|
||||
ip_addr_t target_addr;
|
||||
esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
|
||||
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
|
||||
// Serial.printf("From %s icmp_seq=%d timeout\n",ipaddr_ntoa((ip_addr_t*)&target_addr), seqno);
|
||||
SerialPrint("i", "Ping", "From " + String(ipaddr_ntoa((ip_addr_t *)&target_addr)) + " icmp_seq=" + String(seqno) + " timeout");
|
||||
pingState = false;
|
||||
}
|
||||
|
||||
static void cmd_ping_on_ping_end(esp_ping_handle_t hdl, void *args)
|
||||
{
|
||||
ip_addr_t target_addr;
|
||||
uint32_t transmitted;
|
||||
uint32_t received;
|
||||
uint32_t total_time_ms;
|
||||
esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted));
|
||||
esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received));
|
||||
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
|
||||
esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms));
|
||||
uint32_t loss = (uint32_t)((1 - ((float)received) / transmitted) * 100);
|
||||
if (IP_IS_V4(&target_addr))
|
||||
{
|
||||
// Serial.printf("\n--- %s ping statistics ---\n", inet_ntoa(*ip_2_ip4(&target_addr)));
|
||||
SerialPrint("i", "Ping", "\n--- " + String(inet_ntoa(*ip_2_ip4(&target_addr))) + " ping statistics ---");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Serial.printf("\n--- %s ping statistics ---\n", inet6_ntoa(*ip_2_ip6(&target_addr)));
|
||||
SerialPrint("i", "Ping", "\n--- " + String(inet6_ntoa(*ip_2_ip6(&target_addr))) + " ping statistics ---");
|
||||
}
|
||||
// Serial.printf("%" PRIu32 " packets transmitted, %" PRIu32 " received, %" PRIu32 "%% packet loss, time %" PRIu32 "ms\n",
|
||||
// transmitted, received, loss, total_time_ms);
|
||||
SerialPrint("i", "Ping", String(transmitted) + " packets transmitted, " + String(received) + " received, " + String(loss) + "% packet loss, time " + String(total_time_ms) + "ms\n");
|
||||
// delete the ping sessions, so that we clean up all resources and can create a new ping session
|
||||
// we don't have to call delete function in the callback, instead we can call delete function from other tasks
|
||||
esp_ping_delete_session(hdl);
|
||||
}
|
||||
#endif
|
||||
class PingIoTM : public IoTItem
|
||||
{
|
||||
private:
|
||||
String _ip = "";
|
||||
int timeout = 0;
|
||||
int interval = 0;
|
||||
int data_size = 0;
|
||||
int count = 0;
|
||||
int tos = 0;
|
||||
// int ttl = 0; //Есть только в новых версиях framwork ПОКА УБРАЛ
|
||||
|
||||
public:
|
||||
PingIoTM(String parameters) : IoTItem(parameters)
|
||||
{
|
||||
jsonRead(parameters, "ip", _ip);
|
||||
jsonRead(parameters, "timeout", timeout);
|
||||
jsonRead(parameters, "interval", interval);
|
||||
jsonRead(parameters, "data_size", data_size);
|
||||
jsonRead(parameters, "count", count);
|
||||
jsonRead(parameters, "tos", tos);
|
||||
// jsonRead(parameters, "ttl", ttl);
|
||||
|
||||
#ifdef ESP32
|
||||
/*
|
||||
ping_args.timeout = 10; // arg_dbl0("W", "timeout", "<t>", "Time to wait for a response, in seconds");
|
||||
ping_args.interval = 1; // arg_dbl0("i", "interval", "<t>", "Wait interval seconds between sending each packet");
|
||||
ping_args.data_size = 0; // arg_int0("s", "size", "<n>", "Specify the number of data bytes to be sent");
|
||||
ping_args.count = 0; // arg_int0("c", "count", "<n>", "Stop after sending count packets");
|
||||
ping_args.tos = 0; // arg_int0("Q", "tos", "<n>", "Set Type of Service related bits in IP datagrams");
|
||||
ping_args.ttl = 0; // arg_int0("T", "ttl", "<n>", "Set Time to Live related bits in IP datagrams");
|
||||
// ping_args.end = arg_end(1);
|
||||
*/
|
||||
if (timeout > 0)
|
||||
config.timeout_ms = (uint32_t)(timeout * 1000);
|
||||
if (interval > 0)
|
||||
config.interval_ms = (uint32_t)(interval * 1000);
|
||||
if (data_size > 0)
|
||||
config.data_size = (uint32_t)(data_size);
|
||||
if (count > 0)
|
||||
config.count = (uint32_t)(count);
|
||||
if (tos > 0)
|
||||
config.tos = (uint32_t)(tos);
|
||||
// if (ttl > 0)
|
||||
// config.ttl = (uint32_t)(ttl);
|
||||
#endif
|
||||
}
|
||||
/*
|
||||
void doByInterval()
|
||||
{
|
||||
#ifdef ESP8266
|
||||
regEvent((float)Ping.ping(_ip.c_str()), "ping");
|
||||
#endif
|
||||
}
|
||||
*/
|
||||
// Основной цикл программы
|
||||
void loop()
|
||||
{
|
||||
#ifdef ESP32
|
||||
if (value.valD != (float)pingState)
|
||||
regEvent((float)pingState, "ping");
|
||||
|
||||
#endif
|
||||
IoTItem::loop();
|
||||
}
|
||||
|
||||
IoTValue execute(String command, std::vector<IoTValue> ¶m)
|
||||
{
|
||||
IoTValue val;
|
||||
if (command == "ping")
|
||||
{
|
||||
#ifdef ESP32
|
||||
if (param.size())
|
||||
{
|
||||
// parse IP address
|
||||
struct sockaddr_in6 sock_addr6;
|
||||
ip_addr_t target_addr;
|
||||
memset(&target_addr, 0, sizeof(target_addr));
|
||||
|
||||
if (inet_pton(AF_INET6, _ip.c_str(), &sock_addr6.sin6_addr) == 1)
|
||||
{
|
||||
/* convert ip6 string to ip6 address */
|
||||
ipaddr_aton(_ip.c_str(), &target_addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
struct addrinfo hint;
|
||||
struct addrinfo *res = NULL;
|
||||
memset(&hint, 0, sizeof(hint));
|
||||
/* convert ip4 string or hostname to ip4 or ip6 address */
|
||||
if (getaddrinfo(_ip.c_str(), NULL, &hint, &res) != 0)
|
||||
{
|
||||
// Serial.printf("ping: unknown host %s\n", ping_args.host.c_str());
|
||||
SerialPrint("E", "Ping", "ping: unknown host " + _ip);
|
||||
// return 1;
|
||||
}
|
||||
if (res->ai_family == AF_INET)
|
||||
{
|
||||
struct in_addr addr4 = ((struct sockaddr_in *)(res->ai_addr))->sin_addr;
|
||||
inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4);
|
||||
}
|
||||
else
|
||||
{
|
||||
struct in6_addr addr6 = ((struct sockaddr_in6 *)(res->ai_addr))->sin6_addr;
|
||||
inet6_addr_to_ip6addr(ip_2_ip6(&target_addr), &addr6);
|
||||
}
|
||||
freeaddrinfo(res);
|
||||
}
|
||||
config.target_addr = target_addr;
|
||||
/* set callback functions */
|
||||
esp_ping_callbacks_t cbs = {
|
||||
.cb_args = NULL,
|
||||
.on_ping_success = cmd_ping_on_ping_success,
|
||||
.on_ping_timeout = cmd_ping_on_ping_timeout,
|
||||
.on_ping_end = cmd_ping_on_ping_end};
|
||||
esp_ping_handle_t ping;
|
||||
|
||||
esp_ping_new_session(&config, &cbs, &ping);
|
||||
esp_ping_start(ping);
|
||||
}
|
||||
#endif
|
||||
#ifdef ESP8266
|
||||
if (param.size())
|
||||
{
|
||||
val.valD = Ping.ping(param[0].valS.c_str());
|
||||
if (val.valD)
|
||||
SerialPrint("I", "Ping", "Ping success");
|
||||
else
|
||||
SerialPrint("E", "Ping", "Ping error");
|
||||
regEvent(val.valD, "ping");
|
||||
return val;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
~PingIoTM(){};
|
||||
};
|
||||
|
||||
void *getAPI_Ping(String subtype, String param)
|
||||
{
|
||||
if (subtype == F("Ping"))
|
||||
{
|
||||
return new PingIoTM(param);
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
66
src/modules/virtual/Ping/modinfo.json
Normal file
66
src/modules/virtual/Ping/modinfo.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"menuSection": "virtual_elments",
|
||||
|
||||
"configItem": [
|
||||
{
|
||||
"global": 0,
|
||||
"name": "Ping",
|
||||
"type": "Reading",
|
||||
"subtype": "Ping",
|
||||
"id": "ping",
|
||||
"needSave": 0,
|
||||
"widget": "nil",
|
||||
"page": "",
|
||||
"descr": "",
|
||||
"ip": "8.8.8.8",
|
||||
"timeout": 5,
|
||||
"interval": 1,
|
||||
"data_size": 0,
|
||||
"count": 0,
|
||||
"tos": 0
|
||||
}
|
||||
],
|
||||
|
||||
"about": {
|
||||
"authorName": "Mikhail Bubnov",
|
||||
"authorContact": "https://t.me/Mit4bmw",
|
||||
"authorGit": "https://github.com/Mit4el",
|
||||
"specialThanks": "",
|
||||
"moduleName": "Ping",
|
||||
"moduleVersion": "1.2",
|
||||
"usedRam": {
|
||||
"esp32_4mb": 15,
|
||||
"esp8266_4mb": 15
|
||||
},
|
||||
"title": "Пинг",
|
||||
"moduleDesc": "Пинг - проверка доступности сетевого адреса",
|
||||
"propInfo": {
|
||||
"ip": "IP адрес (8.8.8.8) или имя хоста (www.google.com) кого пингуем",
|
||||
"timeout": "Тайм-аут. Только для ESP32. Если 0, то пингует со значением по умолчанию",
|
||||
"interval": "Интервал. Только для ESP32. Если 0, то пингует со значением по умолчанию",
|
||||
"data_size": "Размер пакета. Только для ESP32. Если 0, то пингует со значением по умолчанию",
|
||||
"count": "Количество пакетов. Только для ESP32. Если 0, то пингует со значением по умолчанию",
|
||||
"tos": "Type Of Service. Только для ESP32. Если 0, то пингует со значением по умолчанию"
|
||||
},
|
||||
"funcInfo": [
|
||||
{
|
||||
"name": "ping",
|
||||
"descr": "Проверить пинг. после вызова данной функции из сценария результат будет в значении самого модуля. if ping21==1 then ЕСТЬ_пинг else НЕТ_пинга",
|
||||
"params": [
|
||||
"IP адрес или имя хоста"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"defActive": true,
|
||||
|
||||
"usedLibs": {
|
||||
"esp32*": [
|
||||
|
||||
],
|
||||
"esp82*": [
|
||||
"https://github.com/dancol90/ESP8266Ping"
|
||||
]
|
||||
}
|
||||
}
|
||||
52
src/modules/virtual/Ping/ping_export.json
Normal file
52
src/modules/virtual/Ping/ping_export.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"mark": "iotm",
|
||||
"config": [
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "VButton",
|
||||
"id": "vbtn67",
|
||||
"needSave": 0,
|
||||
"widget": "toggle",
|
||||
"page": "Пинг",
|
||||
"descr": "ping",
|
||||
"int": "0",
|
||||
"val": "0"
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "Variable",
|
||||
"id": "vout96",
|
||||
"needSave": 0,
|
||||
"widget": "anydataDef",
|
||||
"page": "Пинг",
|
||||
"descr": "Состояние",
|
||||
"int": "0",
|
||||
"val": "...",
|
||||
"map": "1024,1024,1,100",
|
||||
"plus": 0,
|
||||
"multiply": 1,
|
||||
"round": 0
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "Ping",
|
||||
"id": "ping21",
|
||||
"needSave": 0,
|
||||
"widget": "anydataDef",
|
||||
"page": "Пинг",
|
||||
"descr": "Статус",
|
||||
"ip": "8.8.8.8",
|
||||
"timeout": 5,
|
||||
"interval": 1,
|
||||
"data_size": 0,
|
||||
"count": 0,
|
||||
"tos": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
scenario=>if vbtn67==1 then ping21.ping("8.8.8.8")
|
||||
if ping21==0 then vout96 = "нет интернета" else vout96 ="есть интернет"
|
||||
27
tools/patch32_ws.py
Normal file
27
tools/patch32_ws.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# правим %USERPROFILE%\.platformio\packages\framework-arduinoespressif32\libraries\WiFi\src\WiFiClient.cpp 27-28
|
||||
# для уменьшения тайм-аута ВебСокетов
|
||||
# #define WIFI_CLIENT_MAX_WRITE_RETRY (10)
|
||||
# #define WIFI_CLIENT_SELECT_TIMEOUT_US (1000000)
|
||||
# Прописать скрипт в platformio.ini внутри [env:esp32_4mb3f] написать extra_scripts = pre:tools/patch32_ws.py
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from sys import platform
|
||||
|
||||
if platform == "linux" or platform == "linux2":
|
||||
# linux
|
||||
mainPyPath = '~/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src/WiFiClient.cpp'
|
||||
else:
|
||||
# windows
|
||||
mainPyPath = os.environ['USERPROFILE'] + '\\.platformio\\packages\\framework-arduinoespressif32\\libraries\\WiFi\\src\\WiFiClient.cpp'
|
||||
|
||||
# print(mainPyPath)
|
||||
|
||||
with open(mainPyPath) as fr:
|
||||
oldData = fr.read()
|
||||
if not 'if WIFI_CLIENT_MAX_WRITE_RETRY (10)' in oldData:
|
||||
shutil.copyfile(mainPyPath, mainPyPath+'.bak')
|
||||
newData = oldData.replace('#define WIFI_CLIENT_MAX_WRITE_RETRY (10)', '#define WIFI_CLIENT_MAX_WRITE_RETRY (2)')
|
||||
newData = newData.replace('#define WIFI_CLIENT_SELECT_TIMEOUT_US (1000000)', '#define WIFI_CLIENT_SELECT_TIMEOUT_US (500000)')
|
||||
with open(mainPyPath, 'w') as fw:
|
||||
fw.write(newData)
|
||||
Reference in New Issue
Block a user