initial commit

This commit is contained in:
Maik Hofmann
2021-02-14 13:07:08 +01:00
commit 10894d2a6e
25 changed files with 1370 additions and 0 deletions

View File

@ -0,0 +1,425 @@
#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;
m_statusCallback = NULL;
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_P(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_P(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_P(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);
if(hasChanged){
Log3(LL_INFO,"New State: ",m_rxbuffer,m_rxlen);
if(m_statusCallback != NULL){
m_statusCallback(m_state);
}
}
}
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;
}
void HCIEmulator::onStatusChanged(callback_function_t handler) {
m_statusCallback = handler;
}

130
HCPBridge/src/hciemulator.h Normal file
View File

@ -0,0 +1,130 @@
#ifndef __hciemulator_h
#define __hciemulator_h
#include <Stream.h>
#include "SoftwareSerial.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,
};
struct SHCIState{
bool valid;
bool lampOn;
uint8_t doorState; // see DoorState
uint8_t doorCurrentPosition;
uint8_t doorTargetPosition;
uint8_t reserved;
};
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:
typedef std::function<void(const SHCIState&)> callback_function_t;
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);
void onStatusChanged(callback_function_t handler);
protected:
void processFrame();
void processDeviceStatusFrame();
void processDeviceBusScanFrame();
void processBroadcastStatusFrame();
private:
callback_function_t m_statusCallback;
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

File diff suppressed because one or more lines are too long

163
HCPBridge/src/main.cpp Normal file
View File

@ -0,0 +1,163 @@
#include <Arduino.h>
#include <ESPAsyncWebServer.h>
#include "AsyncJson.h"
#include "ArduinoJson.h"
#include "hciemulator.h"
#include "index_html.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
// use alternative uart pins
//#define SWAPUART
#define RS485 Serial
// 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
// Hörmann HCP2 based on modbus rtu @57.6kB 8E1
HCIEmulator emulator(&RS485);
// webserver on port 80
AsyncWebServer server(80);
// switch GPIO4 und GPIO2 sync to the lamp
void onStatusChanged(const SHCIState& state){
//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
if(state.valid){
digitalWrite( ESP8266_GPIO4, state.lampOn );
digitalWrite(LED_PIN, state.lampOn);
}else
{
digitalWrite( ESP8266_GPIO4, false );
digitalWrite(LED_PIN, false);
}
}
// toggle lamp to expected state
void switchLamp(bool on){
bool toggle = (on && !emulator.getState().lampOn) || (!on && emulator.getState().lampOn);
if(toggle){
emulator.toggleLamp();
}
}
// setup mcu
void setup(){
//setup modbus
RS485.begin(57600,SERIAL_8E1);
#ifdef SWAPUART
RS485.swap();
#endif
//setup wifi
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
WiFi.setAutoReconnect(true);
while (WiFi.status() != WL_CONNECTED) {
emulator.poll();
}
// 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){
const SHCIState& doorstate = emulator.getState();
AsyncResponseStream *response = request->beginResponseStream("application/json");
DynamicJsonDocument root(1024);
root["valid"] = doorstate.valid;
root["doorstate"] = doorstate.doorState;
root["doorposition"] = doorstate.doorCurrentPosition;
root["doortarget"] = doorstate.doorTargetPosition;
root["lamp"] = doorstate.lampOn;
root["debug"] = doorstate.reserved;
root["lastresponse"] = emulator.getMessageAge()/1000;
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 0:
emulator.closeDoor();
break;
case 1:
emulator.openDoor();
break;
case 2:
emulator.stopDoor();
break;
case 3:
emulator.ventilationPosition();
break;
case 4:
emulator.openDoorHalf();
break;
case 5:
emulator.toggleLamp();
break;
default:
break;
}
}
request->send(200, "text/plain", "OK");
});
server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
if (request->hasParam("channel") && request->hasParam("state")) {
String channel = request->getParam("channel")->value();
String state = request->getParam("state")->value();
if(channel.equals("door")){
if(state=="1"){
emulator.openDoor();
}else{
emulator.closeDoor();
}
}
if(channel.equals("light")){
switchLamp(state=="1");
}
}
request->send(200, "text/plain", "OK");
});
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);
emulator.onStatusChanged(onStatusChanged);
#endif
}
// mainloop
void loop(){
emulator.poll();
}

View File

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

View File

View File

@ -0,0 +1,42 @@
#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') 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)
#with gzip.open('htmltest.html.gz', 'wb') as f:
# f.write(content.encode("UTF-8"))
#with open('htmltest.html.z','wb') as f:
# f.write(zlib.compress(content.encode("UTF-8"),9))
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
}