New Combo ESP8266 + Arduino Pro Mini via i2c

This commit is contained in:
Maik Hofmann 2021-03-06 13:59:08 +01:00
parent 5fb027b629
commit 29918f0cb8
24 changed files with 1260 additions and 0 deletions

5
HCPBridgeCombo/Arduino/.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,7 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
]
}

View File

@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

View File

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

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:pro8MHzatmega328]
platform = atmelavr
board = pro8MHzatmega328
; change microcontroller
board_build.mcu = atmega328p
; change MCU frequency
board_build.f_cpu = 8000000L
framework = arduino
lib_deps =

View File

@ -0,0 +1,415 @@
#include "hciemulator.h"
#define CHECKCHANGEDSET(Target,Value,Flag) if((Target)!=(Value)){Target=Value;Flag=true;}
int hciloglevel = DEFAULTLOGLEVEL;
#ifdef SOFTSERIAL
#define Log(Level,Message) LogCore(Level,Message)
#define Log3(Level,Message,Buffer, Len) LogCore(Level,Message,Buffer,Len)
//LOGLEVEL
void LogCore(int Level, const char* msg, const unsigned char * data=NULL, size_t datalen=0){
if(Level>hciloglevel){
return;
}
if(data!=NULL && datalen>0){
String newmsg(msg);
char str[4];
for (size_t i = 0; i < datalen; i++){
snprintf(str,sizeof(str),"%02x ", data[i]);
newmsg+=str;
}
Serial.println(newmsg);
}else{
Serial.println(msg);
}
}
#else
#define Log(Level,Message)
#define Log3(Level,Message,Buffer, Len)
#endif
int HCIEmulator::getLogLevel(){
return hciloglevel;
}
void HCIEmulator::setLogLevel(int level){
hciloglevel=level;
}
//modbus crc calculation borrowed from:
//https://github.com/yaacov/ArduinoModbusSlave
#define MODBUS_CRC_LENGTH 2
#define readCRC(arr, length) word(arr[(length - MODBUS_CRC_LENGTH) + 1], arr[length - MODBUS_CRC_LENGTH])
#define readUInt16(arr, index) word(arr[index], arr[index + 1])
/**
* Calculate the CRC of the passed byte array from zero up to the passed length.
*
* @param buffer The byte array containing the data.
* @param length The length of the byte array.
*
* @return The calculated CRC as an unsigned 16 bit integer.
*
* Calculate and add the CRC.
* uint16_t crc = Modbus::calculateCRC(_responseBuffer, _responseBufferLength - MODBUS_CRC_LENGTH);
* _responseBuffer[_responseBufferLength - MODBUS_CRC_LENGTH] = crc & 0xFF;
* _responseBuffer[(_responseBufferLength - MODBUS_CRC_LENGTH) + 1] = crc >> 8;
*
*
* #define MODBUS_FRAME_SIZE 4
* #define MODBUS_CRC_LENGTH 2
* uint16_t crc = readCRC(_requestBuffer, _requestBufferLength);
* #define readUInt16(arr, index) word(arr[index], arr[index + 1])
* #define readCRC(arr, length) word(arr[(length - MODBUS_CRC_LENGTH) + 1], arr[length - MODBUS_CRC_LENGTH])
*/
uint16_t calculateCRC(uint8_t *buffer, int length)
{
int i, j;
uint16_t crc = 0xFFFF;
uint16_t tmp;
// Calculate the CRC.
for (i = 0; i < length; i++)
{
crc = crc ^ buffer[i];
for (j = 0; j < 8; j++)
{
tmp = crc & 0x0001;
crc = crc >> 1;
if (tmp)
{
crc = crc ^ 0xA001;
}
}
}
return crc;
}
HCIEmulator::HCIEmulator(Stream * port) {
m_state.valid = false;
m_statemachine=WAITING;
m_rxlen = m_txlen = 0;
m_recvTime=m_lastStateTime=0;
m_skipFrame=false;
m_port = port;
setLogLevel(DEFAULTLOGLEVEL);
};
void HCIEmulator::poll(){
if(m_port==NULL) return;
// receive Data
if(m_port->available() >0)
{
m_rxlen+= m_port->readBytes((char*)(m_rxbuffer+m_rxlen), min((int)(255-m_rxlen),m_port->available()));
if(m_rxlen > 254)
{
Log(LL_ERROR,"RX Bufferoverflow, skip next Frame");
Log3(LL_DEBUG,"Buffer Data: ", m_rxbuffer, m_rxlen);
m_rxlen=0;
m_skipFrame = true;
}
m_recvTime = micros();
}
// check frame, process frame
if(m_rxlen>0 && (micros()-m_recvTime > T3_5))
{
// check last action timeout -> reset > then 2sec
if(m_statemachine!= WAITING && m_lastStateTime+2000<millis()){
m_statemachine = WAITING;
}
if(!m_skipFrame){
processFrame();
// send response
if(m_txlen > 0){
// fix crc
uint16_t crc = calculateCRC(m_txbuffer, m_txlen - MODBUS_CRC_LENGTH);
m_txbuffer[m_txlen - MODBUS_CRC_LENGTH] = crc & 0xFF;
m_txbuffer[(m_txlen - MODBUS_CRC_LENGTH) + 1] = crc >> 8;
// send data
m_lastSendTime = micros()-m_recvTime;
//Log(LL_DEBUG, ("ST:"+String(m_lastSendTime)).c_str());
m_port->write(m_txbuffer, m_txlen);
Log3(LL_DEBUG,"Response: ", m_txbuffer, m_txlen);
m_txlen = 0;
}
}
m_skipFrame = false;
m_rxlen=0;
}
}
void HCIEmulator::processFrame(){
m_txlen = 0; // clear send buffer
if(m_rxlen<5) {
Log(LL_ERROR,"Frame skipped, invalid frame len");
Log3(LL_ERROR,"Data:", m_rxbuffer,m_rxlen);
return;
}
// check device id, pass only device id 2 and 0 (broadcast)
if(m_rxbuffer[0] != BROADCASTID && m_rxbuffer[0] != DEVICEID){
Log(LL_DEBUG,"Frame skipped, unsupported device id");
Log3(LL_DEBUG,"Data:", m_rxbuffer,m_rxlen);
return;
}
// check crc
uint16_t crc = readCRC(m_rxbuffer, m_rxlen);
if(crc != calculateCRC(m_rxbuffer,m_rxlen-MODBUS_CRC_LENGTH)){
Log3(LL_ERROR,"Frame skipped, wrong crc", m_rxbuffer,m_rxlen);
return;
}
Log3(LL_DEBUG,"Incomming Data: ", m_rxbuffer, m_rxlen);
// dispatch modbus function
switch(m_rxbuffer[1]){
case 0x10:{ // Write Multiple registers
if(m_rxlen == 0x1b && m_rxbuffer[0] == BROADCASTID)
{
processBroadcastStatusFrame();
return;
}
break;
}
case 0x17:{ // Read/Write Multiple registers
if(m_rxbuffer[0] == DEVICEID){
switch(m_rxlen){
case 0x11:{
processDeviceStatusFrame();
return;
}
case 0x13:
processDeviceBusScanFrame();
return;;
}
}
break;
}
}
Log3(LL_ERROR,"Frame skipped, unexpected data: ", m_rxbuffer, m_rxlen);
}
const unsigned char ResponseTemplate_Fcn17_Cmd03_L08 []= {0x02,0x17,0x10,0x3E,0x00,0x03,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x74,0x1B};
const unsigned char ResponseTemplate_Fcn17_Cmd04_L02 []= {0x02,0x17,0x04,0x0F,0x00,0x04,0xFD,0x0A,0x72};
void HCIEmulator::processDeviceStatusFrame(){
if(m_rxlen==0x11){
unsigned char counter = m_rxbuffer[11];
unsigned char cmd = m_rxbuffer[12];
if(m_rxbuffer[5] == 0x08){
// expose internal state
// 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
//0011: 02 17 9C B9 00 08 9C 41 00 02 04 3E 03 00 00 EB CC
//res=> 02 17 10 3E 00 03 01 00 00 00 00 00 00 00 00 00 00 00 00 74 1B
memcpy(m_txbuffer, ResponseTemplate_Fcn17_Cmd03_L08, sizeof(ResponseTemplate_Fcn17_Cmd03_L08));
m_txbuffer[0] = m_rxbuffer[0];
m_txbuffer[3] = counter;
m_txbuffer[5] = cmd;
m_txlen = sizeof(ResponseTemplate_Fcn17_Cmd03_L08);
switch(m_statemachine)
{
// open Door
case STARTOPENDOOR:
m_txbuffer[7]= 0x02;
m_txbuffer[8]= 0x10;
m_statemachine = STARTOPENDOOR_RELEASE;
m_lastStateTime = millis();
break;
case STARTOPENDOOR_RELEASE:
if(m_lastStateTime+SIMULATEKEYPRESSDELAYMS<millis()){
m_txbuffer[7]= 0x01;
m_txbuffer[8]= 0x10;
m_statemachine = WAITING;
}
break;
// close Door
case STARTCLOSEDOOR:
m_txbuffer[7]= 0x02;
m_txbuffer[8]= 0x20;
m_statemachine = STARTCLOSEDOOR_RELEASE;
m_lastStateTime = millis();
break;
case STARTCLOSEDOOR_RELEASE:
if(m_lastStateTime+SIMULATEKEYPRESSDELAYMS<millis()){
m_txbuffer[7]= 0x01;
m_txbuffer[8]= 0x20;
m_statemachine = WAITING;
}
break;
// stop Door
case STARTSTOPDOOR:
m_txbuffer[7]= 0x02;
m_txbuffer[8]= 0x40;
m_statemachine = STARTSTOPDOOR_RELEASE;
m_lastStateTime = millis();
break;
case STARTSTOPDOOR_RELEASE:
if(m_lastStateTime+SIMULATEKEYPRESSDELAYMS<millis()){
m_txbuffer[7]= 0x01;
m_txbuffer[8]= 0x40;
m_statemachine = WAITING;
}
break;
// Ventilation
case STARTVENTPOSITION:
m_txbuffer[7]= 0x02;
m_txbuffer[9]= 0x40;
m_statemachine = STARTVENTPOSITION_RELEASE;
m_lastStateTime = millis();
break;
case STARTVENTPOSITION_RELEASE:
if(m_lastStateTime+SIMULATEKEYPRESSDELAYMS<millis()){
m_txbuffer[7]= 0x01;
m_txbuffer[9]= 0x40;
m_statemachine = WAITING;
}
break;
// Half Position
case STARTOPENDOORHALF:
m_txbuffer[7]= 0x02;
m_txbuffer[9]= 0x04;
m_statemachine = STARTOPENDOORHALF_RELEASE;
m_lastStateTime = millis();
break;
case STARTOPENDOORHALF_RELEASE:
if(m_lastStateTime+SIMULATEKEYPRESSDELAYMS<millis()){
m_txbuffer[7]= 0x01;
m_txbuffer[9]= 0x04;
m_statemachine = WAITING;
}
break;
// Toggle Lamp
case STARTTOGGLELAMP:
m_txbuffer[7]= 0x10;
m_txbuffer[9]= 0x02;
m_statemachine = STARTTOGGLELAMP_RELEASE;
m_lastStateTime = millis();
break;
case STARTTOGGLELAMP_RELEASE:
if(m_lastStateTime+SIMULATEKEYPRESSDELAYMS<millis()){
m_txbuffer[7]= 0x08;
m_txbuffer[9]= 0x02;
m_statemachine = WAITING;
}
break;
case WAITING:
break;
}
return;
}
else if(m_rxbuffer[5] == 0x02){
// 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
//0011: 02 17 9C B9 00 02 9C 41 00 02 04 0F 04 17 00 7B 21
//res=> 02 17 04 0F 00 04 FD 0A 72
memcpy(m_txbuffer, ResponseTemplate_Fcn17_Cmd04_L02, sizeof(ResponseTemplate_Fcn17_Cmd04_L02));
m_txbuffer[0] = m_rxbuffer[0];
m_txbuffer[3] = counter;
m_txbuffer[5] = cmd;
m_txlen = sizeof(ResponseTemplate_Fcn17_Cmd04_L02);
return;
}
}
Log3(LL_ERROR,"Frame skipped, unexpected data: ", m_rxbuffer, m_rxlen);
}
const unsigned char ResponseTemplate_Fcn17_Cmd02_L05 []= {0x02,0x17,0x0a,0x00,0x00,0x02,0x05,0x04,0x30,0x10,0xff,0xa8,0x45,0x0e,0xdf};
void HCIEmulator::processDeviceBusScanFrame(){
// 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
//0013: 02 17 9C B9 00 05 9C 41 00 03 06 00 02 00 00 01 02 f8 35
//res=> 02 17 0a 00 00 02 05 04 30 10 ff a8 45 0e df
unsigned char counter = m_rxbuffer[11];
unsigned char cmd = m_rxbuffer[12];
memcpy(m_txbuffer, ResponseTemplate_Fcn17_Cmd02_L05, sizeof(ResponseTemplate_Fcn17_Cmd02_L05));
m_txbuffer[0] = m_rxbuffer[0];
m_txbuffer[3] = counter;
m_txbuffer[5] = cmd;
m_txlen = sizeof(ResponseTemplate_Fcn17_Cmd02_L05);
Log(LL_INFO,"Busscan received");
}
void HCIEmulator::processBroadcastStatusFrame(){
//001B: 00 10 9D 31 00 09 12 64 00 00 00 40 60 00 00 00 00 00 00 00 00 00 01 00 00 CA 22
bool hasChanged = false;
CHECKCHANGEDSET(m_state.lampOn,m_rxbuffer[20] == 0x14,hasChanged);
CHECKCHANGEDSET(m_state.doorCurrentPosition,m_rxbuffer[10],hasChanged);
CHECKCHANGEDSET(m_state.doorTargetPosition, m_rxbuffer[9],hasChanged);
CHECKCHANGEDSET(m_state.doorState, m_rxbuffer[11],hasChanged);
CHECKCHANGEDSET(m_state.reserved, m_rxbuffer[17],hasChanged);
CHECKCHANGEDSET(m_state.valid, true,hasChanged);
m_state.cc = m_rxbuffer[7];
}
void HCIEmulator::openDoor(){
if(m_statemachine != WAITING){
return;
}
m_lastStateTime = millis();
m_statemachine = STARTOPENDOOR;
}
void HCIEmulator::openDoorHalf(){
if(m_statemachine != WAITING){
return;
}
m_lastStateTime = millis();
m_statemachine = STARTOPENDOORHALF;
}
void HCIEmulator::closeDoor(){
if(m_statemachine != WAITING){
return;
}
m_lastStateTime = millis();
m_statemachine = STARTCLOSEDOOR;
}
void HCIEmulator::stopDoor(){
if(m_statemachine != WAITING){
return;
}
m_lastStateTime = millis();
m_statemachine = STARTSTOPDOOR;
}
void HCIEmulator::toggleLamp(){
if(m_statemachine != WAITING){
return;
}
m_lastStateTime = millis();
m_statemachine = STARTTOGGLELAMP;
}
void HCIEmulator::ventilationPosition(){
if(m_statemachine != WAITING){
return;
}
m_lastStateTime = millis();
m_statemachine = STARTVENTPOSITION;
}

View File

@ -0,0 +1,117 @@
#ifndef __hciemulator_h
#define __hciemulator_h
#include <Arduino.h>
#include <Stream.h>
#include "i2cshare.h"
#define LL_OFF 0
#define LL_ERROR 1
#define LL_WARN 2
#define LL_INFO 3
#define LL_DEBUG 4
#define DEFAULTLOGLEVEL LL_WARN
#define DEVICEID 0x02
#define BROADCASTID 0x00
#define SIMULATEKEYPRESSDELAYMS 100
// Modbus states that a baud rate higher than 19200 must use a fixed 750 us
// for inter character time out and 1.75 ms for a frame delay.
// For baud rates below 19200 the timeing is more critical and has to be calculated.
// E.g. 9600 baud in a 10 bit packet is 960 characters per second
// In milliseconds this will be 960characters per 1000ms. So for 1 character
// 1000ms/960characters is 1.04167ms per character and finaly modbus states an
// intercharacter must be 1.5T or 1.5 times longer than a normal character and thus
// 1.5T = 1.04167ms * 1.5 = 1.5625ms. A frame delay is 3.5T.
#define T1_5 750
#define T3_5 4800 //1750
enum DoorState : uint8_t {
DOOR_OPEN_POSITION = 0x20,
DOOR_CLOSE_POSITION = 0x40,
DOOR_HALF_POSITION = 0x80,
DOOR_MOVE_CLOSEPOSITION = 0x02,
DOOR_MOVE_OPENPOSITION = 0x01,
};
enum StateMachine: uint8_t{
WAITING,
STARTOPENDOOR,
STARTOPENDOOR_RELEASE,
STARTOPENDOORHALF,
STARTOPENDOORHALF_RELEASE,
STARTCLOSEDOOR,
STARTCLOSEDOOR_RELEASE,
STARTSTOPDOOR,
STARTSTOPDOOR_RELEASE,
STARTTOGGLELAMP,
STARTTOGGLELAMP_RELEASE,
STARTVENTPOSITION,
STARTVENTPOSITION_RELEASE
};
class HCIEmulator {
public:
HCIEmulator(Stream * port);
void poll();
void openDoor();
void openDoorHalf();
void closeDoor();
void stopDoor();
void toggleLamp();
void ventilationPosition();
const SHCIState& getState() {
if(micros()-m_recvTime > 2000000){
// 2 sec without statusmessage
m_state.valid = false;
}
return m_state;
};
unsigned long getMessageAge(){
return micros()-m_recvTime;
}
int getLogLevel();
void setLogLevel(int level);
protected:
void processFrame();
void processDeviceStatusFrame();
void processDeviceBusScanFrame();
void processBroadcastStatusFrame();
private:
Stream *m_port;
SHCIState m_state;
StateMachine m_statemachine;
unsigned long m_recvTime;
unsigned long m_lastStateTime;
unsigned long m_lastSendTime;
size_t m_rxlen;
size_t m_txlen;
unsigned char m_rxbuffer[255];
unsigned char m_txbuffer[255];
bool m_skipFrame;
};
#endif

View File

@ -0,0 +1,26 @@
#ifndef __i2cshare_h
#define __i2cshare_h
#include <Arduino.h>
#define I2CADDR 7
#define I2C_CMD_CLOSEDOOR 0
#define I2C_CMD_OPENDOOR 1
#define I2C_CMD_OPENDOORHALF 4
#define I2C_CMD_STOPDOOR 2
#define I2C_CMD_VENTPOS 3
#define I2C_CMD_TOGGLELAMP 5
struct SHCIState{
bool valid : 1;
bool lampOn : 1;
uint8_t doorState; // see DoorState
uint8_t doorCurrentPosition;
uint8_t doorTargetPosition;
uint8_t reserved;
uint8_t cc;
};
#endif //__i2cshare_h

View File

@ -0,0 +1,61 @@
#include <Arduino.h>
#include <Wire.h>
#include "hciemulator.h"
#include "i2cshare.h"
#define RS485 Serial
// Hörmann HCP2 based on modbus rtu @57.6kB 8E1
HCIEmulator emulator(&RS485);
// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent() {
Wire.write((unsigned char*)&(emulator.getState()), sizeof(SHCIState));
}
// function that executes whenever data is available by master
// this function is registered as an event, see setup()
void receiveEvent(int numBytes) {
while (Wire.available()){
switch(Wire.read()){
case I2C_CMD_CLOSEDOOR:
emulator.closeDoor();
break;
case I2C_CMD_OPENDOOR:
emulator.openDoor();
break;
case I2C_CMD_OPENDOORHALF:
emulator.openDoorHalf();
break;
case I2C_CMD_STOPDOOR:
emulator.stopDoor();
break;
case I2C_CMD_VENTPOS:
emulator.ventilationPosition();
break;
case I2C_CMD_TOGGLELAMP:
emulator.toggleLamp();
break;
}
}
}
// setup mcu
void setup(){
//setup modbus
RS485.begin(57600,SERIAL_8E1);
//setup I2C
Wire.begin(I2CADDR);
Wire.onRequest(requestEvent);
Wire.onReceive(receiveEvent);
}
// mainloop
void loop(){
emulator.poll();
}

View File

@ -0,0 +1,11 @@
This directory is intended for PlatformIO Unit Testing and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/page/plus/unit-testing.html

5
HCPBridgeCombo/ESP/.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,7 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
]
}

View File

@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

View File

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

View File

@ -0,0 +1,19 @@
; 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:esp12e]
platform = espressif8266
board = esp12e
framework = arduino
lib_deps =
ottowinter/ESPAsyncWebServer-esphome@^1.2.7
bblanchon/ArduinoJson@^6.17.2
ayushsharma82/AsyncElegantOTA@^2.2.5
krzychb/EspSaveCrash@^1.2.0

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,180 @@
#include <Arduino.h>
#include "EspSaveCrash.h"
#include <ESPAsyncWebServer.h>
#include <AsyncElegantOTA.h>
#include <AsyncJson.h>
#include <ArduinoJson.h>
#include <Wire.h>
#include "index_html.h"
#include "../../Arduino/src/i2cshare.h"
/* create this file and add your wlan credentials
const char* ssid = "MyWLANSID";
const char* password = "MYPASSWORD";
*/
#include "../../../../private/credentials.h"
// switch relay sync to the lamp
// e.g. the Wifi Relay Board U4648
#define USERELAY
// Relay Board parameters
#define ESP8266_GPIO2 2 // Blue LED.
#define ESP8266_GPIO4 4 // Relay control.
#define ESP8266_GPIO5 5 // Optocoupler input.
#define LED_PIN ESP8266_GPIO2
EspSaveCrash SaveCrash;
// the buffer to put the Crash log to
char *_debugOutputBuffer;
SHCIState lastState;
// webserver on port 80
AsyncWebServer server(80);
unsigned long nextStateCall = millis();
unsigned long lasti2cmsgtime = millis();
// switch GPIO4 und GPIO2 sync to the lamp
void onStatusChanged(){
//see https://ucexperiment.wordpress.com/2016/12/18/yunshan-esp8266-250v-15a-acdc-network-wifi-relay-module/
//Setting GPIO4 high, causes the relay to close the NO contact with
#ifdef USERELAY
bool lamp = lastState.valid && lastState.lampOn;
digitalWrite( ESP8266_GPIO4, lamp );
digitalWrite(LED_PIN, lamp);
#endif
}
// setup mcu
void setup(){
lastState.valid = false;
lastState.reserved = 0xAB;
// Setup I2C Master on UART Pins to use relayboard
//ESP 8266 PIN 1(TX0) and PIN 3 (RX0)
Wire.begin(1,3);
_debugOutputBuffer = (char *) calloc(2048, sizeof(char));
//setup wifi
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
WiFi.setAutoReconnect(true);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
}
// setup http server
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
AsyncWebServerResponse *response = request->beginResponse_P( 200, "text/html", index_html,sizeof(index_html));
response->addHeader("Content-Encoding","deflate");
request->send(response);
});
server.on("/status", HTTP_GET, [](AsyncWebServerRequest *request){
AsyncResponseStream *response = request->beginResponseStream("application/json");
DynamicJsonDocument root(1024);
root["valid"] = lastState.valid;
root["doorstate"] = lastState.doorState;
root["doorposition"] = lastState.doorCurrentPosition;
root["doortarget"] = lastState.doorTargetPosition;
root["lamp"] = lastState.lampOn;
root["debug"] = lastState.reserved;
root["cc"] = lastState.cc;
root["lastresponse"] = millis()-lasti2cmsgtime;
serializeJson(root,*response);
request->send(response);
});
server.on("/command", HTTP_GET, [] (AsyncWebServerRequest *request) {
if (request->hasParam("action")) {
int actionid = request->getParam("action")->value().toInt();
switch (actionid){
case I2C_CMD_CLOSEDOOR:
case I2C_CMD_OPENDOOR:
case I2C_CMD_OPENDOORHALF:
case I2C_CMD_STOPDOOR:
case I2C_CMD_VENTPOS:
case I2C_CMD_TOGGLELAMP:
Wire.beginTransmission(I2CADDR);
Wire.write((unsigned char)actionid);
Wire.endTransmission();
break;
default:
break;
}
}
request->send(200, "text/plain", "OK");
});
server.on("/crashinfo", HTTP_GET, [] (AsyncWebServerRequest *request){
strcpy(_debugOutputBuffer, "");
SaveCrash.print(_debugOutputBuffer,2048);
if (request->hasParam("clear")) {
SaveCrash.clear();
}
request->send(200, "text/plain", _debugOutputBuffer);
});
server.on("/sysinfo", HTTP_GET, [] (AsyncWebServerRequest *request) {
char buffer[150];
rst_info* rinfo = ESP.getResetInfoPtr();
AsyncResponseStream *response = request->beginResponseStream("application/json");
DynamicJsonDocument root(1024);
root["freemem"] = ESP.getFreeHeap();
root["hostname"] = WiFi.hostname();
root["ip"] = WiFi.localIP().toString();
root["ssid"] = String(ssid);
root["wifistatus"] = WiFi.status();
root["resetreason"] =ESP.getResetReason();
root["errors"] = rinfo->exccause;
//The address of the last crash is printed, which is used to
sprintf(buffer, "epc1=0x%08x, epc2=0x%08x, epc3=0x%08x, excvaddr=0x%08x, depc=0x%08x, exccause=0x%x, reason=0x%x",
rinfo->epc1, rinfo->epc2, rinfo->epc3, rinfo->excvaddr, rinfo->depc, rinfo->exccause, rinfo->reason);
root["rstinfo"] = buffer;
serializeJson(root,*response);
request->send(response);
});
AsyncElegantOTA.begin(&server);
server.begin();
//setup relay board
#ifdef USERELAY
pinMode( ESP8266_GPIO4, OUTPUT ); // Relay control pin.
pinMode( ESP8266_GPIO5, INPUT_PULLUP ); // Input pin.
pinMode( LED_PIN, OUTPUT ); // ESP8266 module blue L
digitalWrite( ESP8266_GPIO4, 0 );
digitalWrite(LED_PIN,0);
#endif
}
void loop(){
if(nextStateCall < millis()){
Wire.requestFrom(I2CADDR, sizeof(SHCIState));
if(sizeof(SHCIState) != Wire.readBytes((unsigned char*) &lastState, sizeof(SHCIState))){
lasti2cmsgtime = millis();
onStatusChanged();
}
nextStateCall = millis()+200;
}
AsyncElegantOTA.loop();
}

View File

@ -0,0 +1 @@
python compress.py

View File

View File

@ -0,0 +1,35 @@
#pip install htmlmin
#or python -m pip install htmlmin
#pip install jsmin
#or python -m pip install jsmin
import gzip
import zlib
import htmlmin
from jsmin import jsmin
content = ""
with open('index.html','rt',encoding="utf-8") as f:
content=f.read()
content = htmlmin.minify(content, remove_comments=True, remove_empty_space=True, remove_all_empty_space=True, reduce_empty_attributes=True, reduce_boolean_attributes=False, remove_optional_attribute_quotes=True, convert_charrefs=True, keep_pre=False)
import re
regex = r"<script>(.+?)<\/script>"
content = re.sub(regex, lambda x: "<script>"+jsmin(x.group(1))+"</script>" ,content, 0, re.DOTALL)
result =""
for c in zlib.compress(content.encode("UTF-8"),9):
result= result + ("0x%02X" %c)
if len(result)> 0:
result=result + ","
with open('../index_html.h',"wt") as f:
f.write("const uint8_t index_html[] PROGMEM = {");
f.write(result.strip(","))
f.write("};");

View File

@ -0,0 +1,159 @@
<!DOCTYPE HTML><html>
<head>
<title>Garagentor Steuerung</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<link rel="icon" href="data:,">
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
h2 {font-size: 3.0rem;}
p {font-size: 3.0rem;}
body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
.switch {position: relative; display: inline-block; width: 80px; height: 48px}
.switch input {display: none}
.slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 6px}
.slider:before {position: absolute; content: ""; height: 32px; width: 32px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 3px}
input:checked+.slider {background-color: #b30000}
input:checked+.slider:before {-webkit-transform: translateX(32px); -ms-transform: translateX(32px); transform: translateX(32px)}
.button {
border: 4px;
border-style: solid;
color: black;
width: 100px;
height: 100px;
text-align: center;
display: inline;
text-decoration: none;
display: inline-block;
font-weight: bold;
font-size: 20px;
margin: 4px 2px;
cursor: pointer;
border-radius: 50px;
}
.buttonred {border-color: #ED5961;}
.buttonyellow {border-color: #fcca00;}
.buttongreen {border-color: #48bd81;}
</style>
</head>
<body>
<h2>Garagentor</h2>
<canvas width="250px" height="250px" id="dc"></canvas>
<h3 id="status">warte auf Verbindung.</h3>
<hr/>
<button class="button buttonred" onclick="doCommand(1)">Auf</button> <button class="button buttonyellow" onclick="doCommand(2)">Stop</button> <button class="button buttongreen" onclick="doCommand(0)">Zu</button>
<hr/>
<h4>Licht</h4><label class="switch"><input type="checkbox" onchange="doCommand(5)" id="light"><span class="slider"></span></label>
<hr/>
<script>
doorpos = 10;
dir = -1;
isconnected = false;
var animation;
function startDoorAnimation() {
if(!animation){
animation = setInterval(Draw, 200);
}
}
function stopDoorAnimation() {
if(animation){
clearInterval(animation);
}
animation = null;
}
function Draw(){
var svg =document.getElementById("svg");
var dc = document.getElementById("dc");
var ctx = dc.getContext("2d");
width = dc.width;
height = dc.height;
ctx.clearRect(0, 0, width, height);
xa= [18,18,10,3,174,347,340,332,332,297,297,52,52,18];
ya= [327,114,117,100,22,100,117,114,327,327,112,112,327,327];
ctx.fillStyle = "black";
ctx.beginPath();
for (i =0;i<xa.length;i++){
if(i==0){
ctx.moveTo((xa[i]/350) * width , (ya[i]/350)*width);
}else{
ctx.lineTo((xa[i]/350) * width, (ya[i]/350)*width);
}
}
ctx.fill();
for(i=0;i<doorpos;i++){
ctx.fillRect((62/350) * width, ((120+21*i)/350) * width, (225/350) * width, (18/350) * width);
}
doorpos+=dir;
if(dir<0 && doorpos<=0){
dir=dir*-1;
}
if(dir>0 &&doorpos>=10){
dir=dir*-1;
}
}
function doCommand(action) {
if(!isconnected){return;}
var xhr = new XMLHttpRequest();
xhr.open("GET", "/command?action="+action, true);
xhr.send();
}
function setDoorState(state){
stopDoorAnimation();
if(state == 0) {doorpos = 10;dir = -1};
if(state == 1) {doorpos = 0; dir = 1};
if(state == 2) {doorpos = 5; dir = 1};
Draw();
if(state == 3) startDoorAnimation();
}
function getStatusText(id){
result = "Das Tor "
switch(id)
{
case 0x20: setDoorState(1); return result+"ist geöffnet.";
case 0x40: setDoorState(0); return result+"ist geschlossen.";
case 0x80: setDoorState(2); return result+"ist teilgeöffnet.";
case 0x00: setDoorState(2); return result+"ist teilgeöffnet.";
case 0x02: setDoorState(3); return result+"schließt.";
case 0x01: setDoorState(3); return result+"öffnet.";
case -1: setDoorState(0); return "keine Verbindung zum Tor.";
default: return "unbekanter Status: " + id;
}
}
function updateData(){
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var status = JSON.parse(this.responseText);
isconnected = status.valid;
document.getElementById("status").innerHTML = getStatusText(status.valid?status.doorstate:-1);
document.getElementById("light").checked = status.lamp & status.valid;
return;
}
isconnected= false;
};
xmlhttp.open("GET", "/status", true);
xmlhttp.send();
}
Draw();
updateData();
setInterval(updateData, 3000);
</script>
</body>
</html>

View File

@ -0,0 +1 @@
python -m http.server

View File

@ -0,0 +1,9 @@
{
"valid" : true,
"doorstate" : 1,
"doorposition" : 0,
"doortarget" : 0,
"lamp" : true,
"debug" : 0,
"lastresponse" : 0
}

View File

@ -0,0 +1,11 @@
This directory is intended for PlatformIO Unit Testing and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/page/plus/unit-testing.html