стабилизация кода

This commit is contained in:
vpodberezsky
2026-01-10 11:04:15 +03:00
parent ec07c6ab7d
commit 2544275792
320 changed files with 9018 additions and 64119 deletions

5
Devices/main-module/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

View File

@@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

View File

@@ -0,0 +1,20 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:d1_mini]
platform = espressif8266
board = d1_mini
build_flags = -Wl,-Teagle.flash.4m1m.ld
framework = arduino
board_build.filesystem = littlefs
monitor_speed = 115200
upload_speed = 256000
;lib_deps = bblanchon/ArduinoJson@^6.19.4
lib_deps = bblanchon/ArduinoJson@^7.0.3

View File

@@ -0,0 +1,39 @@
#pragma once
#include <Arduino.h>
#include <ArduinoJson.h>
#include "HalConfigBase.h"
class Config : public HalConfigBase
{
private:
public:
int cnt = 123;
inline String getFileName()
{
return F("/config.json");
}
String convertToString()
{
String config = "";
StaticJsonDocument<1024> doc;
//JsonObject json = doc.createNestedObject();
_toJson(doc);
doc["cnt"] = cnt;
serializeJson(doc, config);
return config;
}
bool convertFromString(String json)
{
StaticJsonDocument<1024> doc;
DeserializationError error = deserializeJson(doc, json);
if (error)
return false;
cnt = doc[0]["cnt"];
_fromJson(doc);
return true;
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,46 @@
#pragma once
#include <Arduino.h>
#include <ArduinoJson.h>
class HalConfigBase
{
public:
String version;
String ssid;
String pass;
String ssidAP;
String passAP;
String updateUrl;
String token;
String chatId;
// void ToJson(JsonObject &json);
// void FromJson(JsonObject &json);
protected:
void _toJson(JsonDocument &json)
{
json["version"] = version;
json["ssid"] = ssid;
json["pass"] = pass;
json["ssidAP"] = ssidAP;
json["passAP"] = passAP;
json["updateUrl"] = updateUrl;
json["token"] = token;
json["chatId"] = chatId;
}
void _fromJson(JsonDocument &json)
{
version = json[0]["version"].as<String>();
ssid = json[0]["ssid"].as<String>();
pass = json[0]["pass"].as<String>();
ssidAP = json[0]["ssidAP"].as<String>();
passAP = json[0]["passAP"].as<String>();
updateUrl = json[0]["updateUrl"].as<String>();
token = json[0]["token"].as<String>();
chatId = json[0]["chatId"].as<String>();
}
private:
};

View File

@@ -0,0 +1,200 @@
#pragma once
#include <Arduino.h>
#include "pca9554.h"
#include "config.h"
#include <ArduinoJson.h>
#define i2c_address 0x20
#define CHANNELS 8
#define SWITCH_DELAY 500
struct RelItem
{
unsigned long Code = 0;
bool State = false;
u8 Mode = 0;
unsigned long Delay = 0;
};
class HalRelays
{
private:
PCA9554 io;
inline String _configFileName() { return F("/relay.json"); }
bool (*_saveFunction)(String, String){};
String (*_loadFunction)(String){};
String _convertToString()
{
String config = "";
StaticJsonDocument<512> doc;
for (byte i = 0; i < CHANNELS; i++)
{
JsonObject jo = doc.createNestedObject();
jo["Code"] = Channels[i].Code;
jo["Mode"] = Channels[i].Mode;
jo["State"] = Channels[i].State;
}
serializeJson(doc, config);
return config;
}
bool _convertFromString(String config_str)
{
// StaticJsonDocument<512> doc;
JsonDocument doc;
DeserializationError error = deserializeJson(doc, config_str);
if (error)
{
return false;
}
for (byte i = 0; i < CHANNELS; i++)
{
Channels[i].Code = doc[i]["Code"];
Channels[i].Mode = doc[i]["Mode"];
Channels[i].State = doc[i]["State"];
}
return true;
}
void _switch(u8 number)
{
if (millis() - Channels[number].Delay > SWITCH_DELAY)
{
Serial.print("RELAY [" + (String)number + "] set ");
// bool value = io.digitalRead(number);
if (Channels[number].State == true)
{
off(number);
Channels[number].State = false;
Serial.println("OFF");
}
else
{
on(number);
Channels[number].State = true;
Serial.println("ON");
}
Channels[number].Delay = millis();
}
}
public:
RelItem Channels[CHANNELS];
HalRelays(byte sda, byte scl, bool (*saveFunction)(String, String), String (*loadFunction)(String))
{
io = PCA9554(i2c_address, sda, scl);
_saveFunction = saveFunction;
_loadFunction = loadFunction;
}
bool cmd(unsigned long code)
{
for (byte i = 0; i < CHANNELS; i++)
{
if (code == Channels[i].Code)
{
_switch(i);
return true;
}
}
Serial.println("Code [" + (String)code + "] not found");
return false;
}
void switchState(u8 number)
{
_switch(number);
}
void printChannels()
{
Serial.println("CHANNELS");
for (byte i = 0; i < CHANNELS; i++)
{
Serial.println((String)i + " :: " + (String)Channels[i].Code);
}
}
String printChannelsToString()
{
String str = "CHANNELS";
for (byte i = 0; i < CHANNELS; i++)
{
str += ((String)i + " :: " + (String)Channels[i].Code);
}
return str;
}
void save()
{
String config_str = _convertToString();
if (!_saveFunction(config_str, _configFileName()))
{
Serial.println(F("RELAYS SAVE :: ERROR"));
return;
}
Serial.println(F("RELAYS SAVE :: OK"));
printChannels();
}
bool load()
{
String configStr = _loadFunction(_configFileName());
Serial.println("DBG");
Serial.println(configStr);
Serial.println("DBG");
if (!_convertFromString(configStr))
{
Serial.println(F("RELAYS LOAD :: ERROR"));
return false;
}
Serial.println(F("RELAYS LOAD :: OK"));
printChannels();
return true;
}
void init()
{
// io.portMode(ALLINPUT);
// offAll();
io.portMode(ALLOUTPUT);
offAll();
if (!load())
{
Serial.println(F("Relay ERROR: config not load."));
}
}
void on(u8 number)
{
io.digitalWrite(number, HIGH);
}
void onAll()
{
io.digitalWritePort(0xFF);
// for (u8 i = 0; i < 8; i++)
// io.digitalWrite(i, HIGH);
}
void off(u8 number)
{
io.digitalWrite(number, LOW);
}
void offAll()
{
io.digitalWritePort(0x0);
// for (u8 i = 0; i < 8; i++)
// io.digitalWrite(i, LOW);
}
};

View File

@@ -0,0 +1,79 @@
#pragma once
#include "Arduino.h"
class HalTimer
{
private:
unsigned long _period;
unsigned long _last_millis;
bool _init_first;
void (*_function)(){};
bool _isRun = false;
public:
HalTimer()
{
}
HalTimer(unsigned long period)
{
_period = period;
}
HalTimer(unsigned long period, void (*function)())
{
_period = period;
_function = function;
_init_first = true;
_last_millis = millis();
}
HalTimer(unsigned long period, void (*function)(), bool autostart)
{
_period = period;
_function = function;
_init_first = true;
_last_millis = millis();
if (autostart)
start();
}
void start()
{
_isRun = true;
}
void stop()
{
_isRun = false;
}
void tick()
{
if (!_isRun)
return;
if (millis() - _last_millis > _period)
{
if (_init_first)
_last_millis = millis();
_function();
if (!_init_first)
_last_millis = millis();
}
}
void begin(unsigned long period, void (*function)(), bool init_first)
{
_period = period;
begin(function, init_first);
}
void begin(void (*function)(), bool init_first)
{
_function = function;
_init_first = init_first;
_last_millis = millis();
}
};

View File

@@ -0,0 +1,701 @@
/*
RCSwitch - Arduino libary for remote control outlet switches
Copyright (c) 2011 Suat Özgür. All right reserved.
Contributors:
- Andre Koehler / info(at)tomate-online(dot)de
- Gordeev Andrey Vladimirovich / gordeev(at)openpyro(dot)com
- Skineffect / http://forum.ardumote.com/viewtopic.php?f=2&t=46
- Dominik Fischer / dom_fischer(at)web(dot)de
- Frank Oltmanns / <first name>.<last name>(at)gmail(dot)com
- Andreas Steinel / A.<lastname>(at)gmail(dot)com
- Max Horn / max(at)quendi(dot)de
- Robert ter Vehn / <first name>.<last name>(at)gmail(dot)com
- Johann Richard / <first name>.<last name>(at)gmail(dot)com
- Vlad Gheorghe / <first name>.<last name>(at)gmail(dot)com https://github.com/vgheo
Project home: https://github.com/sui77/rc-switch/
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "RCSwitch.h"
#ifdef RaspberryPi
// PROGMEM and _P functions are for AVR based microprocessors,
// so we must normalize these for the ARM processor:
#define PROGMEM
#define memcpy_P(dest, src, num) memcpy((dest), (src), (num))
#endif
#if defined(ESP8266) || defined(ESP32)
// interrupt handler and related code must be in RAM on ESP8266,
// according to issue #46.
#define RECEIVE_ATTR ICACHE_RAM_ATTR
#else
#define RECEIVE_ATTR
#endif
/* Format for protocol definitions:
* {pulselength, Sync bit, "0" bit, "1" bit}
*
* pulselength: pulse length in microseconds, e.g. 350
* Sync bit: {1, 31} means 1 high pulse and 31 low pulses
* (perceived as a 31*pulselength long pulse, total length of sync bit is
* 32*pulselength microseconds), i.e:
* _
* | |_______________________________ (don't count the vertical bars)
* "0" bit: waveform for a data bit of value "0", {1, 3} means 1 high pulse
* and 3 low pulses, total length (1+3)*pulselength, i.e:
* _
* | |___
* "1" bit: waveform for a data bit of value "1", e.g. {3,1}:
* ___
* | |_
*
* These are combined to form Tri-State bits when sending or receiving codes.
*/
#if defined(ESP8266) || defined(ESP32)
static const RCSwitch::Protocol proto[] = {
#else
static const RCSwitch::Protocol PROGMEM proto[] = {
#endif
{ 350, { 1, 31 }, { 1, 3 }, { 3, 1 }, false }, // protocol 1
{ 650, { 1, 10 }, { 1, 2 }, { 2, 1 }, false }, // protocol 2
{ 100, { 30, 71 }, { 4, 11 }, { 9, 6 }, false }, // protocol 3
{ 380, { 1, 6 }, { 1, 3 }, { 3, 1 }, false }, // protocol 4
{ 500, { 6, 14 }, { 1, 2 }, { 2, 1 }, false }, // protocol 5
{ 450, { 23, 1 }, { 1, 2 }, { 2, 1 }, true }, // protocol 6 (HT6P20B)
{ 150, { 2, 62 }, { 1, 6 }, { 6, 1 }, false } // protocol 7 (HS2303-PT, i. e. used in AUKEY Remote)
};
enum {
numProto = sizeof(proto) / sizeof(proto[0])
};
#if not defined( RCSwitchDisableReceiving )
volatile unsigned long RCSwitch::nReceivedValue = 0;
volatile unsigned int RCSwitch::nReceivedBitlength = 0;
volatile unsigned int RCSwitch::nReceivedDelay = 0;
volatile unsigned int RCSwitch::nReceivedProtocol = 0;
int RCSwitch::nReceiveTolerance = 60;
const unsigned int RCSwitch::nSeparationLimit = 4300;
// separationLimit: minimum microseconds between received codes, closer codes are ignored.
// according to discussion on issue #14 it might be more suitable to set the separation
// limit to the same time as the 'low' part of the sync signal for the current protocol.
unsigned int RCSwitch::timings[RCSWITCH_MAX_CHANGES];
#endif
RCSwitch::RCSwitch() {
this->nTransmitterPin = -1;
this->setRepeatTransmit(10);
this->setProtocol(1);
#if not defined( RCSwitchDisableReceiving )
this->nReceiverInterrupt = -1;
this->setReceiveTolerance(60);
RCSwitch::nReceivedValue = 0;
#endif
}
/**
* Sets the protocol to send.
*/
void RCSwitch::setProtocol(Protocol protocol) {
this->protocol = protocol;
}
/**
* Sets the protocol to send, from a list of predefined protocols
*/
void RCSwitch::setProtocol(int nProtocol) {
if (nProtocol < 1 || nProtocol > numProto) {
nProtocol = 1; // TODO: trigger an error, e.g. "bad protocol" ???
}
#if defined(ESP8266) || defined(ESP32)
this->protocol = proto[nProtocol-1];
#else
memcpy_P(&this->protocol, &proto[nProtocol-1], sizeof(Protocol));
#endif
}
/**
* Sets the protocol to send with pulse length in microseconds.
*/
void RCSwitch::setProtocol(int nProtocol, int nPulseLength) {
setProtocol(nProtocol);
this->setPulseLength(nPulseLength);
}
/**
* Sets pulse length in microseconds
*/
void RCSwitch::setPulseLength(int nPulseLength) {
this->protocol.pulseLength = nPulseLength;
}
/**
* Sets Repeat Transmits
*/
void RCSwitch::setRepeatTransmit(int nRepeatTransmit) {
this->nRepeatTransmit = nRepeatTransmit;
}
/**
* Set Receiving Tolerance
*/
#if not defined( RCSwitchDisableReceiving )
void RCSwitch::setReceiveTolerance(int nPercent) {
RCSwitch::nReceiveTolerance = nPercent;
}
#endif
/**
* Enable transmissions
*
* @param nTransmitterPin Arduino Pin to which the sender is connected to
*/
void RCSwitch::enableTransmit(int nTransmitterPin) {
this->nTransmitterPin = nTransmitterPin;
pinMode(this->nTransmitterPin, OUTPUT);
}
/**
* Disable transmissions
*/
void RCSwitch::disableTransmit() {
this->nTransmitterPin = -1;
}
/**
* Switch a remote switch on (Type D REV)
*
* @param sGroup Code of the switch group (A,B,C,D)
* @param nDevice Number of the switch itself (1..3)
*/
void RCSwitch::switchOn(char sGroup, int nDevice) {
this->sendTriState( this->getCodeWordD(sGroup, nDevice, true) );
}
/**
* Switch a remote switch off (Type D REV)
*
* @param sGroup Code of the switch group (A,B,C,D)
* @param nDevice Number of the switch itself (1..3)
*/
void RCSwitch::switchOff(char sGroup, int nDevice) {
this->sendTriState( this->getCodeWordD(sGroup, nDevice, false) );
}
/**
* Switch a remote switch on (Type C Intertechno)
*
* @param sFamily Familycode (a..f)
* @param nGroup Number of group (1..4)
* @param nDevice Number of device (1..4)
*/
void RCSwitch::switchOn(char sFamily, int nGroup, int nDevice) {
this->sendTriState( this->getCodeWordC(sFamily, nGroup, nDevice, true) );
}
/**
* Switch a remote switch off (Type C Intertechno)
*
* @param sFamily Familycode (a..f)
* @param nGroup Number of group (1..4)
* @param nDevice Number of device (1..4)
*/
void RCSwitch::switchOff(char sFamily, int nGroup, int nDevice) {
this->sendTriState( this->getCodeWordC(sFamily, nGroup, nDevice, false) );
}
/**
* Switch a remote switch on (Type B with two rotary/sliding switches)
*
* @param nAddressCode Number of the switch group (1..4)
* @param nChannelCode Number of the switch itself (1..4)
*/
void RCSwitch::switchOn(int nAddressCode, int nChannelCode) {
this->sendTriState( this->getCodeWordB(nAddressCode, nChannelCode, true) );
}
/**
* Switch a remote switch off (Type B with two rotary/sliding switches)
*
* @param nAddressCode Number of the switch group (1..4)
* @param nChannelCode Number of the switch itself (1..4)
*/
void RCSwitch::switchOff(int nAddressCode, int nChannelCode) {
this->sendTriState( this->getCodeWordB(nAddressCode, nChannelCode, false) );
}
/**
* Deprecated, use switchOn(const char* sGroup, const char* sDevice) instead!
* Switch a remote switch on (Type A with 10 pole DIP switches)
*
* @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111")
* @param nChannelCode Number of the switch itself (1..5)
*/
void RCSwitch::switchOn(const char* sGroup, int nChannel) {
const char* code[6] = { "00000", "10000", "01000", "00100", "00010", "00001" };
this->switchOn(sGroup, code[nChannel]);
}
/**
* Deprecated, use switchOff(const char* sGroup, const char* sDevice) instead!
* Switch a remote switch off (Type A with 10 pole DIP switches)
*
* @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111")
* @param nChannelCode Number of the switch itself (1..5)
*/
void RCSwitch::switchOff(const char* sGroup, int nChannel) {
const char* code[6] = { "00000", "10000", "01000", "00100", "00010", "00001" };
this->switchOff(sGroup, code[nChannel]);
}
/**
* Switch a remote switch on (Type A with 10 pole DIP switches)
*
* @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111")
* @param sDevice Code of the switch device (refers to DIP switches 6..10 (A..E) where "1" = on and "0" = off, if all DIP switches are on it's "11111")
*/
void RCSwitch::switchOn(const char* sGroup, const char* sDevice) {
this->sendTriState( this->getCodeWordA(sGroup, sDevice, true) );
}
/**
* Switch a remote switch off (Type A with 10 pole DIP switches)
*
* @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111")
* @param sDevice Code of the switch device (refers to DIP switches 6..10 (A..E) where "1" = on and "0" = off, if all DIP switches are on it's "11111")
*/
void RCSwitch::switchOff(const char* sGroup, const char* sDevice) {
this->sendTriState( this->getCodeWordA(sGroup, sDevice, false) );
}
/**
* Returns a char[13], representing the code word to be send.
*
*/
char* RCSwitch::getCodeWordA(const char* sGroup, const char* sDevice, bool bStatus) {
static char sReturn[13];
int nReturnPos = 0;
for (int i = 0; i < 5; i++) {
sReturn[nReturnPos++] = (sGroup[i] == '0') ? 'F' : '0';
}
for (int i = 0; i < 5; i++) {
sReturn[nReturnPos++] = (sDevice[i] == '0') ? 'F' : '0';
}
sReturn[nReturnPos++] = bStatus ? '0' : 'F';
sReturn[nReturnPos++] = bStatus ? 'F' : '0';
sReturn[nReturnPos] = '\0';
return sReturn;
}
/**
* Encoding for type B switches with two rotary/sliding switches.
*
* The code word is a tristate word and with following bit pattern:
*
* +-----------------------------+-----------------------------+----------+------------+
* | 4 bits address | 4 bits address | 3 bits | 1 bit |
* | switch group | switch number | not used | on / off |
* | 1=0FFF 2=F0FF 3=FF0F 4=FFF0 | 1=0FFF 2=F0FF 3=FF0F 4=FFF0 | FFF | on=F off=0 |
* +-----------------------------+-----------------------------+----------+------------+
*
* @param nAddressCode Number of the switch group (1..4)
* @param nChannelCode Number of the switch itself (1..4)
* @param bStatus Whether to switch on (true) or off (false)
*
* @return char[13], representing a tristate code word of length 12
*/
char* RCSwitch::getCodeWordB(int nAddressCode, int nChannelCode, bool bStatus) {
static char sReturn[13];
int nReturnPos = 0;
if (nAddressCode < 1 || nAddressCode > 4 || nChannelCode < 1 || nChannelCode > 4) {
return 0;
}
for (int i = 1; i <= 4; i++) {
sReturn[nReturnPos++] = (nAddressCode == i) ? '0' : 'F';
}
for (int i = 1; i <= 4; i++) {
sReturn[nReturnPos++] = (nChannelCode == i) ? '0' : 'F';
}
sReturn[nReturnPos++] = 'F';
sReturn[nReturnPos++] = 'F';
sReturn[nReturnPos++] = 'F';
sReturn[nReturnPos++] = bStatus ? 'F' : '0';
sReturn[nReturnPos] = '\0';
return sReturn;
}
/**
* Like getCodeWord (Type C = Intertechno)
*/
char* RCSwitch::getCodeWordC(char sFamily, int nGroup, int nDevice, bool bStatus) {
static char sReturn[13];
int nReturnPos = 0;
int nFamily = (int)sFamily - 'a';
if ( nFamily < 0 || nFamily > 15 || nGroup < 1 || nGroup > 4 || nDevice < 1 || nDevice > 4) {
return 0;
}
// encode the family into four bits
sReturn[nReturnPos++] = (nFamily & 1) ? 'F' : '0';
sReturn[nReturnPos++] = (nFamily & 2) ? 'F' : '0';
sReturn[nReturnPos++] = (nFamily & 4) ? 'F' : '0';
sReturn[nReturnPos++] = (nFamily & 8) ? 'F' : '0';
// encode the device and group
sReturn[nReturnPos++] = ((nDevice-1) & 1) ? 'F' : '0';
sReturn[nReturnPos++] = ((nDevice-1) & 2) ? 'F' : '0';
sReturn[nReturnPos++] = ((nGroup-1) & 1) ? 'F' : '0';
sReturn[nReturnPos++] = ((nGroup-1) & 2) ? 'F' : '0';
// encode the status code
sReturn[nReturnPos++] = '0';
sReturn[nReturnPos++] = 'F';
sReturn[nReturnPos++] = 'F';
sReturn[nReturnPos++] = bStatus ? 'F' : '0';
sReturn[nReturnPos] = '\0';
return sReturn;
}
/**
* Encoding for the REV Switch Type
*
* The code word is a tristate word and with following bit pattern:
*
* +-----------------------------+-------------------+----------+--------------+
* | 4 bits address | 3 bits address | 3 bits | 2 bits |
* | switch group | device number | not used | on / off |
* | A=1FFF B=F1FF C=FF1F D=FFF1 | 1=0FF 2=F0F 3=FF0 | 000 | on=10 off=01 |
* +-----------------------------+-------------------+----------+--------------+
*
* Source: http://www.the-intruder.net/funksteckdosen-von-rev-uber-arduino-ansteuern/
*
* @param sGroup Name of the switch group (A..D, resp. a..d)
* @param nDevice Number of the switch itself (1..3)
* @param bStatus Whether to switch on (true) or off (false)
*
* @return char[13], representing a tristate code word of length 12
*/
char* RCSwitch::getCodeWordD(char sGroup, int nDevice, bool bStatus) {
static char sReturn[13];
int nReturnPos = 0;
// sGroup must be one of the letters in "abcdABCD"
int nGroup = (sGroup >= 'a') ? (int)sGroup - 'a' : (int)sGroup - 'A';
if ( nGroup < 0 || nGroup > 3 || nDevice < 1 || nDevice > 3) {
return 0;
}
for (int i = 0; i < 4; i++) {
sReturn[nReturnPos++] = (nGroup == i) ? '1' : 'F';
}
for (int i = 1; i <= 3; i++) {
sReturn[nReturnPos++] = (nDevice == i) ? '1' : 'F';
}
sReturn[nReturnPos++] = '0';
sReturn[nReturnPos++] = '0';
sReturn[nReturnPos++] = '0';
sReturn[nReturnPos++] = bStatus ? '1' : '0';
sReturn[nReturnPos++] = bStatus ? '0' : '1';
sReturn[nReturnPos] = '\0';
return sReturn;
}
/**
* @param sCodeWord a tristate code word consisting of the letter 0, 1, F
*/
void RCSwitch::sendTriState(const char* sCodeWord) {
// turn the tristate code word into the corresponding bit pattern, then send it
unsigned long code = 0;
unsigned int length = 0;
for (const char* p = sCodeWord; *p; p++) {
code <<= 2L;
switch (*p) {
case '0':
// bit pattern 00
break;
case 'F':
// bit pattern 01
code |= 1L;
break;
case '1':
// bit pattern 11
code |= 3L;
break;
}
length += 2;
}
this->send(code, length);
}
/**
* @param sCodeWord a binary code word consisting of the letter 0, 1
*/
void RCSwitch::send(const char* sCodeWord) {
// turn the tristate code word into the corresponding bit pattern, then send it
unsigned long code = 0;
unsigned int length = 0;
for (const char* p = sCodeWord; *p; p++) {
code <<= 1L;
if (*p != '0')
code |= 1L;
length++;
}
this->send(code, length);
}
/**
* Transmit the first 'length' bits of the integer 'code'. The
* bits are sent from MSB to LSB, i.e., first the bit at position length-1,
* then the bit at position length-2, and so on, till finally the bit at position 0.
*/
void RCSwitch::send(unsigned long code, unsigned int length) {
if (this->nTransmitterPin == -1)
return;
#if not defined( RCSwitchDisableReceiving )
// make sure the receiver is disabled while we transmit
int nReceiverInterrupt_backup = nReceiverInterrupt;
if (nReceiverInterrupt_backup != -1) {
this->disableReceive();
}
#endif
for (int nRepeat = 0; nRepeat < nRepeatTransmit; nRepeat++) {
for (int i = length-1; i >= 0; i--) {
if (code & (1L << i))
this->transmit(protocol.one);
else
this->transmit(protocol.zero);
}
this->transmit(protocol.syncFactor);
}
// Disable transmit after sending (i.e., for inverted protocols)
digitalWrite(this->nTransmitterPin, LOW);
#if not defined( RCSwitchDisableReceiving )
// enable receiver again if we just disabled it
if (nReceiverInterrupt_backup != -1) {
this->enableReceive(nReceiverInterrupt_backup);
}
#endif
}
/**
* Transmit a single high-low pulse.
*/
void RCSwitch::transmit(HighLow pulses) {
uint8_t firstLogicLevel = (this->protocol.invertedSignal) ? LOW : HIGH;
uint8_t secondLogicLevel = (this->protocol.invertedSignal) ? HIGH : LOW;
digitalWrite(this->nTransmitterPin, firstLogicLevel);
delayMicroseconds( this->protocol.pulseLength * pulses.high);
digitalWrite(this->nTransmitterPin, secondLogicLevel);
delayMicroseconds( this->protocol.pulseLength * pulses.low);
}
#if not defined( RCSwitchDisableReceiving )
/**
* Enable receiving data
*/
void RCSwitch::enableReceive(int interrupt) {
this->nReceiverInterrupt = interrupt;
this->enableReceive();
}
void RCSwitch::enableReceive() {
if (this->nReceiverInterrupt != -1) {
RCSwitch::nReceivedValue = 0;
RCSwitch::nReceivedBitlength = 0;
#if defined(RaspberryPi) // Raspberry Pi
wiringPiISR(this->nReceiverInterrupt, INT_EDGE_BOTH, &handleInterrupt);
#else // Arduino
attachInterrupt(this->nReceiverInterrupt, handleInterrupt, CHANGE);
#endif
}
}
/**
* Disable receiving data
*/
void RCSwitch::disableReceive() {
#if not defined(RaspberryPi) // Arduino
detachInterrupt(this->nReceiverInterrupt);
#endif // For Raspberry Pi (wiringPi) you can't unregister the ISR
this->nReceiverInterrupt = -1;
}
bool RCSwitch::available() {
return RCSwitch::nReceivedValue != 0;
}
void RCSwitch::resetAvailable() {
RCSwitch::nReceivedValue = 0;
}
unsigned long RCSwitch::getReceivedValue() {
return RCSwitch::nReceivedValue;
}
unsigned int RCSwitch::getReceivedBitlength() {
return RCSwitch::nReceivedBitlength;
}
unsigned int RCSwitch::getReceivedDelay() {
return RCSwitch::nReceivedDelay;
}
unsigned int RCSwitch::getReceivedProtocol() {
return RCSwitch::nReceivedProtocol;
}
unsigned int* RCSwitch::getReceivedRawdata() {
return RCSwitch::timings;
}
/* helper function for the receiveProtocol method */
static inline unsigned int diff(int A, int B) {
return abs(A - B);
}
/**
*
*/
bool RECEIVE_ATTR RCSwitch::receiveProtocol(const int p, unsigned int changeCount) {
#if defined(ESP8266) || defined(ESP32)
const Protocol &pro = proto[p-1];
#else
Protocol pro;
memcpy_P(&pro, &proto[p-1], sizeof(Protocol));
#endif
unsigned long code = 0;
//Assuming the longer pulse length is the pulse captured in timings[0]
const unsigned int syncLengthInPulses = ((pro.syncFactor.low) > (pro.syncFactor.high)) ? (pro.syncFactor.low) : (pro.syncFactor.high);
const unsigned int delay = RCSwitch::timings[0] / syncLengthInPulses;
const unsigned int delayTolerance = delay * RCSwitch::nReceiveTolerance / 100;
/* For protocols that start low, the sync period looks like
* _________
* _____________| |XXXXXXXXXXXX|
*
* |--1st dur--|-2nd dur-|-Start data-|
*
* The 3rd saved duration starts the data.
*
* For protocols that start high, the sync period looks like
*
* ______________
* | |____________|XXXXXXXXXXXXX|
*
* |-filtered out-|--1st dur--|--Start data--|
*
* The 2nd saved duration starts the data
*/
const unsigned int firstDataTiming = (pro.invertedSignal) ? (2) : (1);
for (unsigned int i = firstDataTiming; i < changeCount - 1; i += 2) {
code <<= 1;
if (diff(RCSwitch::timings[i], delay * pro.zero.high) < delayTolerance &&
diff(RCSwitch::timings[i + 1], delay * pro.zero.low) < delayTolerance) {
// zero
} else if (diff(RCSwitch::timings[i], delay * pro.one.high) < delayTolerance &&
diff(RCSwitch::timings[i + 1], delay * pro.one.low) < delayTolerance) {
// one
code |= 1;
} else {
// Failed
return false;
}
}
if (changeCount > 7) { // ignore very short transmissions: no device sends them, so this must be noise
RCSwitch::nReceivedValue = code;
RCSwitch::nReceivedBitlength = (changeCount - 1) / 2;
RCSwitch::nReceivedDelay = delay;
RCSwitch::nReceivedProtocol = p;
return true;
}
return false;
}
void RECEIVE_ATTR RCSwitch::handleInterrupt() {
static unsigned int changeCount = 0;
static unsigned long lastTime = 0;
static unsigned int repeatCount = 0;
const long time = micros();
const unsigned int duration = time - lastTime;
if (duration > RCSwitch::nSeparationLimit) {
// A long stretch without signal level change occurred. This could
// be the gap between two transmission.
if (diff(duration, RCSwitch::timings[0]) < 200) {
// This long signal is close in length to the long signal which
// started the previously recorded timings; this suggests that
// it may indeed by a a gap between two transmissions (we assume
// here that a sender will send the signal multiple times,
// with roughly the same gap between them).
repeatCount++;
if (repeatCount == 2) {
for(unsigned int i = 1; i <= numProto; i++) {
if (receiveProtocol(i, changeCount)) {
// receive succeeded for protocol i
break;
}
}
repeatCount = 0;
}
}
changeCount = 0;
}
// detect overflow
if (changeCount >= RCSWITCH_MAX_CHANGES) {
changeCount = 0;
repeatCount = 0;
}
RCSwitch::timings[changeCount++] = duration;
lastTime = time;
}
#endif

View File

@@ -0,0 +1,184 @@
/*
RCSwitch - Arduino libary for remote control outlet switches
Copyright (c) 2011 Suat Özgür. All right reserved.
Contributors:
- Andre Koehler / info(at)tomate-online(dot)de
- Gordeev Andrey Vladimirovich / gordeev(at)openpyro(dot)com
- Skineffect / http://forum.ardumote.com/viewtopic.php?f=2&t=46
- Dominik Fischer / dom_fischer(at)web(dot)de
- Frank Oltmanns / <first name>.<last name>(at)gmail(dot)com
- Max Horn / max(at)quendi(dot)de
- Robert ter Vehn / <first name>.<last name>(at)gmail(dot)com
Project home: https://github.com/sui77/rc-switch/
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _RCSwitch_h
#define _RCSwitch_h
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#elif defined(ENERGIA) // LaunchPad, FraunchPad and StellarPad specific
#include "Energia.h"
#elif defined(RPI) // Raspberry Pi
#define RaspberryPi
// Include libraries for RPi:
#include <string.h> /* memcpy */
#include <stdlib.h> /* abs */
#include <wiringPi.h>
#elif defined(SPARK)
#include "application.h"
#else
#include "WProgram.h"
#endif
#include <stdint.h>
// At least for the ATTiny X4/X5, receiving has to be disabled due to
// missing libm depencies (udivmodhi4)
#if defined( __AVR_ATtinyX5__ ) or defined ( __AVR_ATtinyX4__ )
#define RCSwitchDisableReceiving
#endif
// Number of maximum high/Low changes per packet.
// We can handle up to (unsigned long) => 32 bit * 2 H/L changes per bit + 2 for sync
#define RCSWITCH_MAX_CHANGES 67
class RCSwitch {
public:
RCSwitch();
void switchOn(int nGroupNumber, int nSwitchNumber);
void switchOff(int nGroupNumber, int nSwitchNumber);
void switchOn(const char* sGroup, int nSwitchNumber);
void switchOff(const char* sGroup, int nSwitchNumber);
void switchOn(char sFamily, int nGroup, int nDevice);
void switchOff(char sFamily, int nGroup, int nDevice);
void switchOn(const char* sGroup, const char* sDevice);
void switchOff(const char* sGroup, const char* sDevice);
void switchOn(char sGroup, int nDevice);
void switchOff(char sGroup, int nDevice);
void sendTriState(const char* sCodeWord);
void send(unsigned long code, unsigned int length);
void send(const char* sCodeWord);
#if not defined( RCSwitchDisableReceiving )
void enableReceive(int interrupt);
void enableReceive();
void disableReceive();
bool available();
void resetAvailable();
unsigned long getReceivedValue();
unsigned int getReceivedBitlength();
unsigned int getReceivedDelay();
unsigned int getReceivedProtocol();
unsigned int* getReceivedRawdata();
#endif
void enableTransmit(int nTransmitterPin);
void disableTransmit();
void setPulseLength(int nPulseLength);
void setRepeatTransmit(int nRepeatTransmit);
#if not defined( RCSwitchDisableReceiving )
void setReceiveTolerance(int nPercent);
#endif
/**
* Description of a single pule, which consists of a high signal
* whose duration is "high" times the base pulse length, followed
* by a low signal lasting "low" times the base pulse length.
* Thus, the pulse overall lasts (high+low)*pulseLength
*/
struct HighLow {
uint8_t high;
uint8_t low;
};
/**
* A "protocol" describes how zero and one bits are encoded into high/low
* pulses.
*/
struct Protocol {
/** base pulse length in microseconds, e.g. 350 */
uint16_t pulseLength;
HighLow syncFactor;
HighLow zero;
HighLow one;
/**
* If true, interchange high and low logic levels in all transmissions.
*
* By default, RCSwitch assumes that any signals it sends or receives
* can be broken down into pulses which start with a high signal level,
* followed by a a low signal level. This is e.g. the case for the
* popular PT 2260 encoder chip, and thus many switches out there.
*
* But some devices do it the other way around, and start with a low
* signal level, followed by a high signal level, e.g. the HT6P20B. To
* accommodate this, one can set invertedSignal to true, which causes
* RCSwitch to change how it interprets any HighLow struct FOO: It will
* then assume transmissions start with a low signal lasting
* FOO.high*pulseLength microseconds, followed by a high signal lasting
* FOO.low*pulseLength microseconds.
*/
bool invertedSignal;
};
void setProtocol(Protocol protocol);
void setProtocol(int nProtocol);
void setProtocol(int nProtocol, int nPulseLength);
private:
char* getCodeWordA(const char* sGroup, const char* sDevice, bool bStatus);
char* getCodeWordB(int nGroupNumber, int nSwitchNumber, bool bStatus);
char* getCodeWordC(char sFamily, int nGroup, int nDevice, bool bStatus);
char* getCodeWordD(char group, int nDevice, bool bStatus);
void transmit(HighLow pulses);
#if not defined( RCSwitchDisableReceiving )
static void handleInterrupt();
static bool receiveProtocol(const int p, unsigned int changeCount);
int nReceiverInterrupt;
#endif
int nTransmitterPin;
int nRepeatTransmit;
Protocol protocol;
#if not defined( RCSwitchDisableReceiving )
static int nReceiveTolerance;
volatile static unsigned long nReceivedValue;
volatile static unsigned int nReceivedBitlength;
volatile static unsigned int nReceivedDelay;
volatile static unsigned int nReceivedProtocol;
const static unsigned int nSeparationLimit;
/*
* timings[0] contains sync timing, followed by a number of bits
*/
static unsigned int timings[RCSWITCH_MAX_CHANGES];
#endif
};
#endif

View File

@@ -0,0 +1,150 @@
#pragma once
#include <Arduino.h>
enum FB_FileType {
FB_PHOTO,
FB_AUDIO,
FB_DOC,
FB_VIDEO,
FB_GIF,
FB_VOICE,
};
struct FB_msg {
String& userID; // ID юзера
String& username; // ник (в API это first_name)
String& chatID; // ID чата
int32_t messageID; // ID сообщения
String& text; // текст
String& data; // callback дата
bool query; // запрос
bool& edited; // сообщение отредактировано
bool isBot; // сообщение от бота
bool OTA; // запрос на OTA обновление
uint32_t unix; // время сообщения
String& fileName; // имя файла
String& replyText; // текст ответа, если он есть
bool isFile; // это файл
String& fileUrl; // адрес файла для загрузки
// legacy
String& usrID; // ID юзера
String& first_name; // имя
String& last_name; // фамилия
int32_t ID; // ID сообщения
// вся информация одной строкой
String toString() {
String s;
s.reserve(200);
s += F("userID: ");
s += userID;
s += F(", username: ");
s += username;
s += F(", isBot: ");
s += isBot;
s += '\n';
s += F("chatID: ");
s += chatID;
s += F(", messageID: ");
s += messageID;
s += F(", edited: ");
s += edited;
s += '\n';
s += F("text: ");
s += text;
s += F(", replyText: ");
s += replyText;
s += '\n';
s += F("query: ");
s += query;
s += F(", data: ");
s += data;
s += '\n';
s += F("isFile: ");
s += isFile;
s += F(", fileName: ");
s += fileName;
s += F(", OTA: ");
s += OTA;
s += '\n';
s += F("fileUrl: ");
s += fileUrl;
s += '\n';
s += F("unix: ");
s += unix;
s += '\n';
return s;
}
};
struct FB_Time {
FB_Time(uint32_t unix = 0, int16_t gmt = 0) {
if (!unix) return;
if (abs(gmt) <= 12) gmt *= 60;
unix += gmt * 60L;
second = unix % 60ul;
unix /= 60ul;
minute = unix % 60ul;
unix /= 60ul;
hour = unix % 24ul;
unix /= 24ul;
dayWeek = (unix + 4) % 7;
if (!dayWeek) dayWeek = 7;
unix += 719468;
uint8_t era = unix / 146097ul;
uint16_t doe = unix - era * 146097ul;
uint16_t yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
year = yoe + era * 400;
uint16_t doy = doe - (yoe * 365 + yoe / 4 - yoe / 100);
uint16_t mp = (doy * 5 + 2) / 153;
day = doy - (mp * 153 + 2) / 5 + 1;
month = mp + (mp < 10 ? 3 : -9);
year += (month <= 2);
}
uint8_t second = 0;
uint8_t minute = 0;
uint8_t hour = 0;
uint8_t day = 0;
uint8_t month = 0;
uint8_t dayWeek = 0;
uint16_t year = 0;
// получить строку времени формата ЧЧ:ММ:СС
String timeString() {
String str;
if (!year) return str = F("Not sync");
str.reserve(8);
if (hour < 10) str += '0';
str += hour;
str += ':';
if (minute < 10) str += '0';
str += minute;
str += ':';
if (second < 10) str += '0';
str += second;
return str;
}
// получить строку даты формата ДД.ММ.ГГГГ
String dateString() {
String str;
if (!year) return str = F("Not sync");
str.reserve(10);
if (day < 10) str += '0';
str += day;
str += '.';
if (month < 10) str += '0';
str += month;
str += '.';
str += year;
return str;
}
};

View File

@@ -0,0 +1,551 @@
#include <Arduino.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <WiFiUdp.h>
#include <ESP8266HTTPClient.h>
#include "main.h"
#include "RCSwitch.h"
#include "pca9554.h"
#include "HalRelays.h"
#include "HalTimer.h"
#include "config.h"
#include "FastBot.h"
// Set i2c address
#define sda 12
#define scl 14
/*
Received 6277048 / 24bit Protocol: 1
Received 6277044 / 24bit Protocol: 1
Received 6277042 / 24bit Protocol: 1
Received 6277041 / 24bit Protocol: 1
*/
RCSwitch mySwitch = RCSwitch();
int rfPin = D1;
// PCA9554 io(0x38, sda, scl);
// PCA9554 io(0x20, sda, scl);
// HalRelays _rel(sda, scl, _rel_save, _rel_load);
HalRelays _rel(sda, scl, _fs_write, _fs_read);
Config _config;
FastBot _bot;
ESP8266WebServer _web(80);
unsigned long _radio_last_value;
void _timer1_handler()
{
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}
HalTimer timer1(1 * 1000, _timer1_handler, true);
void _timer2_handler()
{
if (WiFi.status() == WL_CONNECTED)
{
_bot.tick();
}
}
HalTimer timer2(3 * 1000, _timer2_handler, true);
void sendHello()
{
String hi = F("Power On ") + (String)_config.version;
uint8 status = _bot.sendMessage(hi, _config.chatId);
Serial.println("Power On [" + (String)status + "]");
}
void bot_handler(FB_msg &msg)
{
Serial.print(msg.chatID); // ID чата
Serial.print(", ");
Serial.print(msg.username); // логин
Serial.print(", ");
Serial.println(msg.text); // текст
if (msg.OTA && msg.text == "-update")
{
String upd = F("update...");
Serial.println(upd);
_bot.update();
Serial.println(upd + "OK");
}
if (msg.text == "-this")
{
_config.chatId = msg.chatID;
_config_save();
_bot.sendMessage(F("Set this OK"), msg.chatID);
}
if (msg.text == "-reset")
{
// ESP.reset();
}
if (msg.text == "/set")
{
String hi = F("Настройки [") + (String)_config.version + "]\r\n";
_bot.sendMessage(hi, _config.chatId);
_bot.sendMessage(_rel.printChannelsToString(), _config.chatId);
}
if (msg.text == "/radio")
{
Serial.print(F("Radio Last: "));
Serial.println((String)_radio_last_value);
_bot.sendMessage(((String)_radio_last_value), msg.chatID);
_rel.printChannels();
Serial.print(F("load"));
_rel.load();
_rel.printChannels();
}
if (msg.text.startsWith(F("/r-")))
{
int number = (msg.text.substring(msg.text.indexOf("-") + 1, msg.text.length())).toInt();
if (0 <= number && number <= 7)
{
_rel.switchState(number);
}
}
if (msg.text.startsWith(F("/radio rel=")))
{
int number = (msg.text.substring(msg.text.indexOf("=") + 1, msg.text.length())).toInt();
if (0 <= number && number <= 7)
{
_rel.Channels[number].Code = _radio_last_value;
_rel.save();
String str = F("Set radio [") + (String)_radio_last_value + F("] to channel: [") + (String)number + F("]");
Serial.println(str);
_bot.sendMessage(str, msg.chatID);
}
}
}
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
pinMode(rfPin, INPUT);
// delay(100);
_fs_init();
_rel.init();
Serial.begin(115200);
Serial.println();
Serial.println();
Serial.println(F("SYSTEM: HAL9000"));
Serial.println(F("UNIT: HOMEON"));
if (!_config_load())
{
Serial.println(F("setup DEFAULT config"));
_config.ssidAP = "HomeOn";
_config.passAP = "012345678";
_config.ssid = "paranetm";
_config.pass = "78515169";
_config.version = VERSION;
_config.updateUrl = "http://iot.paranet.ru/cloudupdate/getup/?platform=HomeOn";
_config.token = "314329238:AAHIQa9Jvae3bnfAQBiOoC6kJZ70jtkYLks";
// 314329238:AAHIQa9Jvae3bnfAQBiOoC6kJZ70jtkYLks
_config.chatId = "";
_config_save();
}
_config.version = VERSION;
// String _rel_conf = _rel_load();
_rel.load();
_wifi_check();
_web_init();
if (WiFi.status() == WL_CONNECTED)
{
Serial.println(F("set token : ") + _config.token);
_bot.setToken(_config.token);
_bot.detach();
_bot.attach(bot_handler);
sendHello();
}
mySwitch.enableReceive(rfPin);
}
bool radio()
{
if (mySwitch.available())
{
_radio_last_value = mySwitch.getReceivedValue();
Serial.print(F("Received: "));
Serial.println(_radio_last_value);
mySwitch.resetAvailable();
if (_rel.cmd(_radio_last_value))
{
String text = F("Переключение: ") + (String)_radio_last_value;
_bot.sendMessage(text, _config.chatId);
}
return true;
}
return false;
}
void loop()
{
radio();
timer1.tick();
radio();
timer2.tick();
// if (WiFi.status() == WL_CONNECTED)
// {
// _bot.tick();
// }
radio();
//_web.handleClient();
}
void loop2()
{
delay(5000);
Serial.println(F("ping"));
// io.digitalWrite(pin, LOW);
_rel.offAll();
delay(5000);
// io.digitalWrite(pin, HIGH);
_rel.onAll();
return;
if (mySwitch.available())
{
/*
Received 6277044 / 24bit Protocol: 1
Received 6277042 / 24bit Protocol: 1
Received 6277041 / 24bit Protocol: 1
Received 6277048 / 24bit Protocol: 1
*/
/**
unsigned long code = mySwitch.getReceivedValue();
int pin = -1;
int le = sizeof(codes) / sizeof(unsigned long);
Serial.println("RC: " + (String)code);
for (byte i = 0; i < le; i++)
{
if (code == codes[i])
{
pin = i + 1;
if (pin == 3)
{
Serial.println("set off-timer");
off_timer = 6;
}
}
}
**/
Serial.print("Received ");
Serial.print(mySwitch.getReceivedValue());
Serial.print(" / ");
Serial.print(mySwitch.getReceivedBitlength());
Serial.print("bit ");
Serial.print("Protocol: ");
Serial.println(mySwitch.getReceivedProtocol());
mySwitch.resetAvailable();
}
}
/** base rutine **/
void _wifi_init(void)
{
// Попытка подключения к точке доступа
WiFi.setAutoConnect(false);
WiFi.BSSID(0);
WiFi.mode(WIFI_STA);
byte tries = 17;
String _ssid = _config.ssid;
String _pass = _config.pass;
Serial.print("MAC address: ");
Serial.println(WiFi.macAddress());
Serial.println("SSID: " + _ssid);
Serial.println("PASS: " + _pass);
WiFi.begin(_ssid.c_str(), _pass.c_str());
// Делаем проверку подключения до тех пор пока счетчик tries не станет равен нулю или не получим подключение
while (--tries && WiFi.status() != WL_CONNECTED)
{
Serial.print(".");
delay(1000);
}
if (WiFi.status() != WL_CONNECTED)
{
// Если не удалось подключиться запускаем в режиме AP
Serial.println("");
Serial.println("WiFi up AP");
_wifi_apmode();
}
else
{
// Иначе удалось подключиться отправляем сообщение о подключении и выводим адрес IP
Serial.println("");
Serial.println("WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
WiFi.setAutoReconnect(true);
_isApMode = false;
}
//_config.ip = WiFi.localIP().toString();
}
bool _wifi_apmode()
{
String _ssidAP = _config.ssidAP;
String _passAP = _config.passAP;
Serial.println("AP SSID: " + _ssidAP);
Serial.println("AP PASS: " + _passAP);
// Отключаем WIFI
WiFi.disconnect();
// Меняем режим на режим точки доступа
WiFi.mode(WIFI_AP);
// Задаем настройки сети
Serial.print("Setting soft-AP configuration ... ");
Serial.println(WiFi.softAPConfig(IPAddress(192, 168, 10, 1), IPAddress(192, 168, 10, 1), IPAddress(255, 255, 255, 0)) ? "Ready" : "Failed!");
// Включаем WIFI в режиме точки доступа с именем и паролем
Serial.print("Setting soft-AP ... ");
Serial.println(WiFi.softAP(_ssidAP.c_str(), _passAP.c_str()) ? "AP Ready" : "AP Failed!");
// Serial.println(WiFi.softAP("test1", "12345678") ? "AP Ready" : "AP Failed!");
_isApMode = true;
return true;
}
void _wifi_check()
{
if (WiFi.status() != WL_CONNECTED)
{
_wifi_init();
return;
}
if (_isApMode)
{
_wifi_init();
}
}
bool _config_load(void)
{
String str = _fs_read(_config.getFileName());
if (!_config.convertFromString(str))
{
return false;
}
Serial.println(F("LOAD CONFING"));
Serial.println(str);
return true;
}
bool _config_save(void)
{
String str = _config.convertToString();
Serial.println(F("SAVE CONFING"));
return _fs_write(str, _config.getFileName());
}
void _web_init(void)
{
_web.on("/get", HTTP_GET, _handle_get);
_web.on("/gett", HTTP_GET, _handle_gett);
_web.on("/list", HTTP_GET, _handle_file_list);
_web.on("/set", HTTP_GET, _handle_set);
_web.onNotFound([]()
{
if (!_handle_file_read(_web.uri())) _web.send(404, "text/plain", "File Not Found: " + _web.uri()); });
_web.begin();
}
void _handle_set(void)
{
_config.ssid = _web.arg("ssid");
_config.pass = _web.arg("pass");
_config.token = _web.arg("bottoken");
_config.updateUrl = _web.arg("updateurl");
if (_config_save())
{
_web.send(200, "text/plain", "SUCCESS");
ESP.restart();
}
else
{
_web.send(500, "text/plain", "ERROR");
}
}
void _handle_get(void)
{
/***
String root = "{}";
DynamicJsonBuffer jsonBuffer;
JsonObject &json = jsonBuffer.parseObject(root);
_config.ToJson(json);
// Помещаем созданный json в переменную root
root = "";
json.printTo(root);
_web.send(200, "text/json", root);
***/
}
void _handle_gett(void)
{
/***
String root = "{}";
DynamicJsonBuffer jsonBuffer;
JsonObject &json = jsonBuffer.parseObject(root);
// Помещаем созданный json в переменную root
root = "";
json.printTo(root);
_web.send(200, "text/json", root);
***/
}
void _handle_file_list(void)
{
String path = "/";
if (_web.hasArg("dir"))
path += _web.arg("dir");
Dir dir = LittleFS.openDir(path);
path = String();
String output = "[";
while (dir.next())
{
File entry = dir.openFile("r");
if (output != "[")
output += ',';
bool isDir = false;
output += "{\"type\":\"";
output += (isDir) ? "dir" : "file";
output += "\",\"name\":\"";
output += String(entry.name()); //.substring(1);
output += "\"}";
entry.close();
}
output += "]";
_web.send(200, "text/json", output);
}
bool _handle_file_read(String path)
{
if (path.endsWith("/"))
path += "index.html";
String contentType = _getContentType(path);
String pathWithGz = path + ".gz";
if (LittleFS.exists(pathWithGz) || LittleFS.exists(path))
{
if (LittleFS.exists(pathWithGz))
path += ".gz";
File file = LittleFS.open(path, "r");
size_t sent = _web.streamFile(file, contentType);
file.close();
return true;
}
return false;
}
String _getContentType(String filename)
{
if (_web.hasArg("download"))
return "application/octet-stream";
else if (filename.endsWith(".htm"))
return "text/html";
else if (filename.endsWith(".html"))
return "text/html";
else if (filename.endsWith(".json"))
return "application/json";
else if (filename.endsWith(".css"))
return "text/css";
else if (filename.endsWith(".js"))
return "application/javascript";
else if (filename.endsWith(".png"))
return "image/png";
else if (filename.endsWith(".gif"))
return "image/gif";
else if (filename.endsWith(".jpg"))
return "image/jpeg";
else if (filename.endsWith(".ico"))
return "image/x-icon";
else if (filename.endsWith(".xml"))
return "text/xml";
else if (filename.endsWith(".pdf"))
return "application/x-pdf";
else if (filename.endsWith(".zip"))
return "application/x-zip";
else if (filename.endsWith(".gz"))
return "application/x-gzip";
return "text/plain";
}
void _fs_init(void)
{
LittleFS.begin();
{
Dir dir = LittleFS.openDir("/");
while (dir.next())
{
String fileName = dir.fileName();
size_t fileSize = dir.fileSize();
}
}
}
bool _fs_write(String str, String file_name)
{
File file = LittleFS.open(file_name, "w");
if (!file)
{
Serial.println(F("Failed to open file for writing"));
file.close();
return false;
}
file.print(str);
file.close();
Serial.println(F("SAVE FILE:"));
Serial.println(str);
return true;
}
String _fs_read(String file_name)
{
String str = "";
File file = LittleFS.open(file_name, "r");
if (!file)
return str;
str = file.readString();
file.close();
return str;
}

View File

@@ -0,0 +1,27 @@
#pragma once
#define VERSION "20260109:1"
bool _isApMode = false;
String _rel_load(void);
bool _rel_save(String config);
void _fs_init(void);
bool _fs_write(String str, String file_name);
String _fs_read(String file_name);
void _web_init(void);
void _wifi_init(void);
bool _handle_file_read(String path);
bool _wifi_apmode(void);
void _handle_get(void);
void _handle_gett(void);
void _handle_file_list(void);
void _handle_set(void);
bool _config_load(void);
String _getContentType(String filename);
bool _config_save(void);
int sensor_run(void);
void _termostat();
void _wifi_check();

View File

@@ -0,0 +1,150 @@
#pragma once
#include <Arduino.h>
#include <Wire.h>
// PCA9554 Addressing
// Address A2 A1 A0
// 0x20 L L L
// 0x21 L L H
// 0x22 L H L
// 0x23 L H H
// 0x24 H L L
// 0x25 H L H
// 0x26 H H L
// 0x27 H H H
#define INPUTPORT 0x00
#define OUTPUTPORT 0x01
#define POLINVPORT 0x02
#define CONFIGPORT 0x03
#define ALLOUTPUT 0x00
#define ALLINPUT 0xFF
class PCA9554
{
private:
int _address;
byte _sda = SDA;
byte _scl = SCL;
TwoWire *_wire;
bool twiRead(byte &registerAddress)
{
_wire->beginTransmission(_address);
_wire->write(registerAddress);
if (_wire->endTransmission() == 0)
{
delay(15);
_wire->requestFrom(_address, 1, true);
while (_wire->available() < 1)
;
registerAddress = _wire->read();
return true;
}
return false;
}
bool twiWrite(byte registerAddress, byte dataWrite)
{
_wire->beginTransmission(_address);
_wire->write(registerAddress);
_wire->write(dataWrite);
if (_wire->endTransmission() == 0)
return true;
return false;
}
public:
PCA9554() {}
PCA9554(byte address, byte sda, byte scl)
{
_wire = &Wire;
_address = address;
_sda = sda;
_scl = scl;
_wire->begin(_sda, _scl);
}
bool pinMode(byte pinNumber, bool state)
{
byte oldValue = CONFIGPORT;
if (this->twiRead(oldValue) && (pinNumber <= 7))
{
if (!state)
{
oldValue |= (1 << pinNumber);
if (this->portMode(oldValue))
return true;
return false;
}
else if (state)
{
oldValue &= ~(1 << pinNumber);
if (this->portMode(oldValue))
return true;
return false;
}
}
return false;
}
bool portMode(byte value)
{
if (this->twiWrite(CONFIGPORT, value))
return true;
return false;
}
bool digitalWrite(byte pinNumber, bool state)
{
byte oldValue = OUTPUTPORT;
if (this->twiRead(oldValue) && pinNumber <= 7)
{
if (state)
{
oldValue |= (1 << pinNumber);
if (this->digitalWritePort(oldValue))
return true;
return false;
}
else if (!state)
{
oldValue &= ~(1 << pinNumber);
if (this->digitalWritePort(oldValue))
return true;
return false;
}
}
return false;
}
bool digitalWritePort(byte value)
{
if (this->twiWrite(OUTPUTPORT, value))
return true;
return false;
}
bool digitalRead(byte &pinNumber)
{
byte oldValue = INPUTPORT;
if (this->twiRead(oldValue) && (pinNumber <= 7))
{
oldValue &= (1 << pinNumber);
if (oldValue > 0)
pinNumber = 1;
else
pinNumber = 0;
return true;
}
return false;
}
bool digitalReadPort(byte &value)
{
value = INPUTPORT;
if (this->twiRead(value))
return true;
return false;
}
};

View File

@@ -0,0 +1,109 @@
#include "utils.h"
void FB_escHTML(String& s) {
String out;
out.reserve(s.length());
for (uint16_t i = 0; i < s.length(); i++) {
switch (s[i]) {
case '<': out += F("&lt;"); break;
case '>': out += F("&gt;"); break;
case '&': out += F("&amp;"); break;
default: out += s[i]; break;
}
}
s = out;
}
static const char FB_escList[] = ">-={}.!";
void FB_escMarkdown(String& s) {
String out;
out.reserve(s.length());
for (uint16_t i = 0; i < s.length(); i++) {
if (strchr(FB_escList, s[i]) != 0) out += '\\';
out += s[i];
}
Serial.println(out);
s = out;
}
int64_t FB_str64(const String &s) {
return atoll(s.c_str());
}
String FB_64str(int64_t id) {
String s;
int32_t s1 = (int64_t)id % 1000000000;
int32_t s2 = (int64_t)id / 1000000000;
if (s2) {
s += s2;
s += abs(s1);
} else {
s += s1;
}
return s;
}
// упрощённый urlencode (до 38 ASCII + space)
// по хорошему нужно пропускать только a-Z, 0-9 и -_.!~*'()
void FB_urlencode(const String& s, String& dest) {
dest.reserve(s.length() + 10);
char c;
for (uint16_t i = 0; i < s.length(); i++) {
c = s[i];
if (c == ' ') dest += '+';
else if (c <= 38 || c == '+') {
dest += '%';
dest += (char)((c >> 4) + (((c >> 4) > 9) ? 87 : 48));
dest += (char)((c & 0xF) + (((c & 0xF) > 9) ? 87 : 48));
}
else dest += c;
}
}
// разработано Глебом Жуковым, допилено AlexGyver
// https://vk.com/wall-97877471_783011
void FB_unicode(String &uStr) {
if (!uStr.length()) return;
String out;
out.reserve(uStr.length() / 3);
int32_t uBytes, buf = 0;
char x0[] = "\0x";
for (uint16_t i = 0; i < uStr.length(); i++) {
if (uStr[i] != '\\') out += uStr[i];
else {
switch (uStr[++i]) {
case '0': break;
case 'n': out += '\n'; break;
case 'r': out += '\r'; break;
case 't': out += '\t'; break;
case 'u':
uBytes = strtol(uStr.c_str() + i + 1, NULL, HEX);
i += 4;
if ((uBytes >= 0xD800) && (uBytes <= 0xDBFF)) buf = uBytes;
else if ((uBytes >= 0xDC00) && (uBytes <= 0xDFFF)) {
uBytes = (0x10000 + ((buf - 0xD800) * 0x0400) + (uBytes - 0xDC00));
out += (char)(0b11110000 | ((uBytes >> 18) & 0b111));
out += x0;
out += (char)(0b10000000 | ((uBytes >> 12) & 0b111111));
out += x0;
out += (char)(0b10000000 | ((uBytes >> 6) & 0b111111));
out += x0;
out += (char)(0b10000000 | (uBytes & 0b111111));
} else if (uBytes < 0x800) {
out += (char)(0b11000000 | ((uBytes >> 6) & 0b11111));
out += x0;
out += (char)(0b10000000 | (uBytes & 0b111111));
} else if (uBytes >= 0x800) {
out += (char)(0b11100000 | ((uBytes >> 12) & 0b1111));
out += x0;
out += (char)(0b10000000 | ((uBytes >> 6) & 0b111111));
out += x0;
out += (char)(0b10000000 | (uBytes & 0b111111));
}
break;
default: out += uStr[i]; break;
}
}
}
uStr = out;
}

View File

@@ -0,0 +1,49 @@
#pragma once
#include <Arduino.h>
void FB_unicode(String &uStr);
void FB_urlencode(const String& s, String& dest);
void FB_escHTML(String& s);
void FB_escMarkdown(String& s);
int64_t FB_str64(const String &s);
String FB_64str(int64_t id);
struct FB_Parser {
FB_Parser() {
str.reserve(20);
}
bool parseNT(const String& s) {
while (!end) {
char c1 = s[++i];
if (c1 == '\t' || c1 == '\n' || c1 == '\0') {
int to = i;
if (s[to - 1] == ' ') to--;
if (s[from] == ' ') from++;
str = s.substring(from, to);
from = i + 1;
end = (c1 == '\0');
div = c1;
return 1;
}
}
return 0;
}
bool parse(const String& s) {
if (i >= (int)s.length() - 1) return 0;
i = s.indexOf(',', from);
if (i < 0) i = s.length();
int to = i;
if (s[to - 1] == ' ') to--;
if (s[from] == ' ') from++;
str = s.substring(from, to);
from = i + 1;
return 1;
}
int i = 0, from = 0;
char div;
bool end = false;
String str;
};